brunner56's picture
implement app
0bfe2e3
import { cva, VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling';
/* -------------------------------------------------------------------------------------------------
* Anatomy
* -----------------------------------------------------------------------------------------------*/
export const InputAnatomy = defineStyleAnatomy({
root: cva(
[
'UI-Input__root',
'flex items-center',
'w-full rounded-[--radius]',
'bg-[--paper] border border-[--border] placeholder-gray-400 dark:placeholder-gray-500',
'disabled:cursor-not-allowed',
'data-[disable=true]:shadow-none data-[disable=true]:opacity-50',
'focus:border-brand focus:ring-1 focus:ring-[--ring]',
'outline-0',
'transition duration-150',
'shadow-sm',
],
{
variants: {
size: {
sm: 'h-8 px-2 py-1 text-sm',
md: 'h-10 px-3',
lg: 'h-12 px-4 py-3 text-md',
},
intent: {
basic: 'hover:border-gray-300 dark:hover:border-gray-600',
filled:
'bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 border-transparent focus:bg-white dark:focus:bg-gray-900 shadow-none',
unstyled:
'bg-transparent hover:bg-transparent border-0 shadow-none focus:ring-0 rounded-none p-0 text-base',
},
hasError: {
false: null,
true: 'border-red-500 hover:border-red-200 dark:border-red-500',
},
isDisabled: {
false: null,
true: 'shadow-none pointer-events-none opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800',
},
isReadonly: {
false: null,
true: 'pointer-events-none cursor-not-allowed shadow-sm',
},
hasLeftAddon: { true: null, false: null },
hasRightAddon: { true: null, false: null },
hasLeftIcon: { true: null, false: null },
hasRightIcon: { true: null, false: null },
},
compoundVariants: [
{
hasLeftAddon: true,
className:
'border-l-transparent hover:border-l-transparent rounded-l-none',
},
/**/
{
hasRightAddon: true,
className:
'border-r-transparent hover:border-r-transparent rounded-r-none',
},
/**/
{
hasLeftAddon: false,
hasLeftIcon: true,
size: 'sm',
className: 'pl-10',
},
{
hasLeftAddon: false,
hasLeftIcon: true,
size: 'md',
className: 'pl-10',
},
{
hasLeftAddon: false,
hasLeftIcon: true,
size: 'lg',
className: 'pl-12',
},
/**/
{
hasRightAddon: false,
hasRightIcon: true,
size: 'sm',
className: 'pr-10',
},
{
hasRightAddon: false,
hasRightIcon: true,
size: 'md',
className: 'pr-10',
},
{
hasRightAddon: false,
hasRightIcon: true,
size: 'lg',
className: 'pr-12',
},
],
defaultVariants: {
size: 'md',
intent: 'basic',
hasError: false,
isDisabled: false,
hasLeftIcon: false,
hasRightIcon: false,
hasLeftAddon: false,
hasRightAddon: false,
},
}
),
});
export const hiddenInputStyles = cn(
'appearance-none absolute bottom-0 border-0 w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap [clip:rect(0px,0px,0px,0px)] [overflow-wrap:normal]'
);
/* -------------------------------------------------------------------------------------------------
* InputContainer
* -----------------------------------------------------------------------------------------------*/
export const InputContainerAnatomy = defineStyleAnatomy({
inputContainer: cva(['UI-Input__inputContainer', 'flex relative']),
});
export type InputContainerProps = {
className: React.HTMLAttributes<HTMLDivElement>['className'];
children?: React.ReactNode;
};
export const InputContainer = ({
className,
children,
}: InputContainerProps) => {
return (
<div className={cn('UI-Input__inputContainer flex relative', className)}>
{children}
</div>
);
};
/* -------------------------------------------------------------------------------------------------
* InputStyling
* -----------------------------------------------------------------------------------------------*/
export type InputStyling = Omit<
VariantProps<typeof InputAnatomy.root>,
| 'isDisabled'
| 'hasError'
| 'hasLeftAddon'
| 'hasRightAddon'
| 'hasLeftIcon'
| 'hasRightIcon'
> &
ComponentAnatomy<typeof InputAddonsAnatomy> &
ComponentAnatomy<typeof InputContainerAnatomy> & {
leftAddon?: React.ReactNode;
leftIcon?: React.ReactNode;
rightAddon?: React.ReactNode;
rightIcon?: React.ReactNode;
};
/* -------------------------------------------------------------------------------------------------
* Addons Anatomy
* -----------------------------------------------------------------------------------------------*/
export const InputAddonsAnatomy = defineStyleAnatomy({
icon: cva(
[
'UI-Input__addons--icon',
'pointer-events-none absolute inset-y-0 grid place-content-center text-gray-500',
'dark:text-gray-300 !z-[1]',
],
{
variants: {
size: { sm: 'w-10 text-md', md: 'w-12 text-lg', lg: 'w-14 text-2xl' },
isLeftIcon: { true: 'left-0', false: null },
isRightIcon: { true: 'right-0', false: null },
},
defaultVariants: {
size: 'md',
isLeftIcon: false,
isRightIcon: false,
},
}
),
addon: cva(
[
'UI-Input__addons--addon',
'bg-gray-50 inline-flex items-center flex-none px-3 border border-gray-300 text-gray-800 shadow-sm text-sm sm:text-md',
'dark:bg-[--paper] dark:border-[--border] dark:text-gray-300',
],
{
variants: {
size: { sm: 'text-sm', md: 'text-md', lg: 'text-lg' },
isLeftAddon: { true: 'rounded-l-md border-r-0', false: null },
isRightAddon: { true: 'rounded-r-md border-l-0', false: null },
hasLeftIcon: { true: null, false: null },
hasRightIcon: { true: null, false: null },
},
compoundVariants: [
{
size: 'sm',
hasLeftIcon: true,
isLeftAddon: true,
className: 'pl-10',
},
{
size: 'sm',
hasRightIcon: true,
isRightAddon: true,
className: 'pr-10',
},
{
size: 'md',
hasLeftIcon: true,
isLeftAddon: true,
className: 'pl-10',
},
{
size: 'md',
hasRightIcon: true,
isRightAddon: true,
className: 'pr-10',
},
{
size: 'lg',
hasLeftIcon: true,
isLeftAddon: true,
className: 'pl-12',
},
{
size: 'lg',
hasRightIcon: true,
isRightAddon: true,
className: 'pr-12',
},
],
defaultVariants: {
size: 'md',
isLeftAddon: false,
isRightAddon: false,
hasLeftIcon: false,
hasRightIcon: false,
},
}
),
});
/* -------------------------------------------------------------------------------------------------
* InputIcon
* -----------------------------------------------------------------------------------------------*/
export type InputIconProps = {
icon: InputStyling['leftIcon'] | undefined;
size: InputStyling['size'];
side: 'right' | 'left';
props?: Omit<React.ComponentPropsWithoutRef<'span'>, 'className'>;
className?: string;
};
export const InputIcon = ({
icon,
size = 'md',
side,
props,
className,
}: InputIconProps) => {
if (!!icon)
return (
<span
className={cn(
InputAddonsAnatomy.icon({
isRightIcon: side === 'right',
isLeftIcon: side === 'left',
size,
}),
className
)}
{...props}
>
{icon}
</span>
);
return null;
};
/* -------------------------------------------------------------------------------------------------
* InputAddon
* -----------------------------------------------------------------------------------------------*/
export type InputAddonProps = {
addon: InputStyling['rightAddon'] | InputStyling['leftAddon'] | undefined;
rightIcon: InputStyling['leftIcon'] | undefined;
leftIcon: InputStyling['rightIcon'] | undefined;
size: InputStyling['size'];
side: 'right' | 'left';
props?: Omit<React.ComponentPropsWithoutRef<'span'>, 'className'>;
className?: string;
};
export const InputAddon = ({
addon,
leftIcon,
rightIcon,
size = 'md',
side,
props,
className,
}: InputAddonProps) => {
if (!!addon)
return (
<span
className={cn(
InputAddonsAnatomy.addon({
isRightAddon: side === 'right',
isLeftAddon: side === 'left',
hasRightIcon: !!rightIcon,
hasLeftIcon: !!leftIcon,
size,
}),
className
)}
{...props}
>
{addon}
</span>
);
return null;
};
/* -------------------------------------------------------------------------------------------------
* Utils
* -----------------------------------------------------------------------------------------------*/
export function extractInputPartProps<T extends InputStyling>(props: T) {
const {
size,
leftAddon,
leftIcon,
rightAddon,
rightIcon,
inputContainerClass, // class
iconClass, // class
addonClass, // class
...rest
} = props;
return [
{
size,
leftAddon,
leftIcon,
rightAddon,
rightIcon,
...rest,
},
{
inputContainerProps: {
className: inputContainerClass,
},
leftAddonProps: {
addon: leftAddon,
leftIcon,
rightIcon,
size,
side: 'left',
className: addonClass,
},
rightAddonProps: {
addon: rightAddon,
leftIcon,
rightIcon,
size,
side: 'right',
className: addonClass,
},
leftIconProps: {
icon: leftIcon,
size,
side: 'left',
className: iconClass,
},
rightIconProps: {
icon: rightIcon,
size,
side: 'right',
className: iconClass,
},
},
] as [
Omit<T, 'iconClass' | 'addonClass' | 'inputContainerClass'>,
{
inputContainerProps: InputContainerProps;
leftAddonProps: InputAddonProps;
rightAddonProps: InputAddonProps;
leftIconProps: InputIconProps;
rightIconProps: InputIconProps;
},
];
}