Browse Source

Make components consistent

Manage props directly and fix some linting errors across all categories.
pull/1/head
TheoryOfNekomata 1 year ago
parent
commit
a73d093533
33 changed files with 1937 additions and 1508 deletions
  1. +2
    -0
      base/src/text-control.ts
  2. +5
    -0
      categories/action/react/.eslintrc
  3. +30
    -16
      categories/action/react/src/components/ActionButton/index.tsx
  4. +4
    -0
      categories/choice/react/.eslintrc
  5. +170
    -141
      categories/choice/react/src/components/ComboBox/index.tsx
  6. +21
    -5
      categories/choice/react/src/components/DropdownSelect/index.tsx
  7. +23
    -3
      categories/choice/react/src/components/MenuSelect/index.tsx
  8. +29
    -17
      categories/choice/react/src/components/RadioButton/index.tsx
  9. +18
    -12
      categories/choice/react/src/components/RadioTickBox/index.tsx
  10. +3
    -0
      categories/color/react/.eslintrc
  11. +22
    -19
      categories/color/react/src/components/Swatch/index.tsx
  12. +4
    -0
      categories/formatted/react/.eslintrc
  13. +24
    -6
      categories/formatted/react/src/components/EmailInput/index.tsx
  14. +149
    -130
      categories/formatted/react/src/components/PhoneNumberInput/index.tsx
  15. +146
    -130
      categories/formatted/react/src/components/UrlInput/index.tsx
  16. +4
    -0
      categories/freeform/react/.eslintrc
  17. +148
    -129
      categories/freeform/react/src/components/MaskedTextInput/index.tsx
  18. +162
    -143
      categories/freeform/react/src/components/MultilineTextInput/index.tsx
  19. +162
    -131
      categories/freeform/react/src/components/TextInput/index.tsx
  20. +3
    -0
      categories/information/react/.eslintrc
  21. +16
    -9
      categories/information/react/src/components/Badge/index.tsx
  22. +13
    -5
      categories/information/react/src/components/KeyValueTable/index.tsx
  23. +4
    -0
      categories/multichoice/react/.eslintrc
  24. +166
    -145
      categories/multichoice/react/src/components/MenuMultiSelect/index.tsx
  25. +244
    -213
      categories/multichoice/react/src/components/TagInput/index.tsx
  26. +32
    -19
      categories/multichoice/react/src/components/ToggleButton/index.tsx
  27. +23
    -14
      categories/multichoice/react/src/components/ToggleSwitch/index.tsx
  28. +20
    -13
      categories/multichoice/react/src/components/ToggleTickBox/index.tsx
  29. +3
    -0
      categories/navigation/react/.eslintrc
  30. +32
    -18
      categories/navigation/react/src/components/LinkButton/index.tsx
  31. +4
    -0
      categories/number/react/.eslintrc
  32. +107
    -60
      categories/number/react/src/components/Slider/index.tsx
  33. +144
    -130
      categories/number/react/src/components/Spinner/index.tsx

+ 2
- 0
base/src/text-control.ts View File

@@ -3,3 +3,5 @@ export type Size = 'small' | 'medium' | 'large';
export type Variant = 'default' | 'alternate'; export type Variant = 'default' | 'alternate';


export type InputType = 'text' | 'search'; export type InputType = 'text' | 'search';

export type InputMode = 'none' | 'numeric' | 'decimal' | InputType;

+ 5
- 0
categories/action/react/.eslintrc View File

@@ -1,5 +1,10 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off",
"react/button-has-type": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 30
- 16
categories/action/react/src/components/ActionButton/index.tsx View File

@@ -6,7 +6,7 @@ export type ActionButtonDerivedElement = HTMLButtonElement;


export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDerivedElement>, 'type' | 'size'> { export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDerivedElement>, 'type' | 'size'> {
type?: Button.Type; type?: Button.Type;
variant: Button.Variant;
variant?: Button.Variant;
block?: boolean; block?: boolean;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode; badge?: React.ReactNode;
@@ -15,19 +15,22 @@ export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDeri
compact?: boolean; compact?: boolean;
} }


export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionButtonProps>(({
type = 'button' as const,
variant,
subtext,
badge,
menuItem = false,
children,
size = 'medium' as const,
compact = false,
className,
block = false,
...etcProps
}, forwardedRef) => (
export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionButtonProps>((
{
type = 'button' as const,
variant = 'bare' as const,
subtext,
badge,
menuItem = false,
children,
size = 'medium' as const,
compact = false,
className,
block = false,
...etcProps
},
forwardedRef,
) => (
<button <button
{...etcProps} {...etcProps}
type={type} type={type}
@@ -48,7 +51,7 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB
'pr-2': compact || menuItem, 'pr-2': compact || menuItem,
}, },
{ {
'border-2 border-primary disabled:border-primary focus:border-secondary active:border-tertiary' : variant !== 'bare',
'border-2 border-primary disabled:border-primary focus:border-secondary active:border-tertiary': variant !== 'bare',
'bg-negative text-primary disabled:text-primary focus:text-secondary active:text-tertiary': variant !== 'filled', 'bg-negative text-primary disabled:text-primary focus:text-secondary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative disabled:bg-primary focus:bg-secondary active:bg-tertiary': variant === 'filled', 'bg-primary text-negative disabled:bg-primary focus:bg-secondary active:bg-tertiary': variant === 'filled',
}, },
@@ -110,7 +113,7 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB
viewBox="0 0 24 24" viewBox="0 0 24 24"
role="presentation" role="presentation"
> >
<polyline points="9 18 15 12 9 6"/>
<polyline points="9 18 15 12 9 6" />
</svg> </svg>
</span> </span>
)} )}
@@ -118,3 +121,14 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB
)); ));


ActionButton.displayName = 'ActionButton'; ActionButton.displayName = 'ActionButton';

ActionButton.defaultProps = {
type: 'button',
variant: 'bare',
block: false,
subtext: undefined,
badge: undefined,
menuItem: false,
size: 'medium',
compact: false,
};

+ 4
- 0
categories/choice/react/.eslintrc View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 170
- 141
categories/choice/react/src/components/ComboBox/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type ComboBoxDerivedElement = HTMLInputElement; export type ComboBoxDerivedElement = HTMLInputElement;


export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -41,6 +41,10 @@ export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedEleme
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Input mode of the component.
*/
inputMode?: TextControl.InputMode,
} }


/** /**
@@ -48,166 +52,191 @@ export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedEleme
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>(
(
{
label = '',
hint = '',
indicator,
size = 'medium' as const,
border = false,
block = false,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
className,
children,
...etcProps
}: ComboBoxProps,
ref,
) => {
const labelId = React.useId();
const datalistId = React.useId();
export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
className,
children,
inputMode = 'text' as const,
id: idProp,
...etcProps
}: ComboBoxProps,
forwardedRef,
) => {
const labelId = React.useId();
const datalistId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

let resultInputMode = inputMode as React.HTMLProps<ComboBoxDerivedElement>['inputMode'];
if (type === 'text' && resultInputMode === 'search') {
resultInputMode = 'text';
} else if (type === 'search' && resultInputMode === 'text') {
resultInputMode = 'search';
}


return (
<>
<datalist
id={datalistId}
>
{children}
</datalist>
<div
return (
<>
<datalist
id={datalistId}
>
{children}
</datalist>
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type={type}
inputMode={resultInputMode}
list={datalistId}
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{ {
'block': block,
'inline-block align-middle': !block,
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type={type}
list={datalistId}
data-testid="input"
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{ {
'text-xxs': size === 'small',
'text-xs': size === 'medium',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
>
<div <div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
</>
);
}
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
</>
);
});


ComboBox.displayName = 'ComboBox'; ComboBox.displayName = 'ComboBox';

ComboBox.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
type: 'text',
variant: 'default',
hiddenLabel: false,
inputMode: 'text',
};

+ 21
- 5
categories/choice/react/src/components/DropdownSelect/index.tsx View File

@@ -47,8 +47,8 @@ export interface DropdownSelectProps extends Omit<React.HTMLProps<DropdownSelect
export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, DropdownSelectProps>( export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, DropdownSelectProps>(
( (
{ {
label = '',
hint = '',
label,
hint,
indicator, indicator,
size = 'medium' as const, size = 'medium' as const,
border = false, border = false,
@@ -57,11 +57,14 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro
hiddenLabel = false, hiddenLabel = false,
className, className,
children, children,
id: idProp,
...etcProps ...etcProps
}: DropdownSelectProps, }: DropdownSelectProps,
ref,
forwardedRef,
) => { ) => {
const labelId = React.useId(); const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return ( return (
<div <div
@@ -77,7 +80,8 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro
> >
<select <select
{...etcProps} {...etcProps}
ref={ref}
ref={forwardedRef}
id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
data-testid="input" data-testid="input"
className={clsx( className={clsx(
@@ -116,6 +120,7 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro
{ {
label && ( label && (
<label <label
htmlFor={id}
data-testid="label" data-testid="label"
id={labelId} id={labelId}
className={clsx( className={clsx(
@@ -194,7 +199,18 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro
} }
</div> </div>
); );
}
},
); );


DropdownSelect.displayName = 'DropdownSelect'; DropdownSelect.displayName = 'DropdownSelect';

DropdownSelect.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 23
- 3
categories/choice/react/src/components/MenuSelect/index.tsx View File

@@ -37,6 +37,9 @@ export interface MenuSelectProps extends Omit<React.HTMLProps<MenuSelectDerivedE
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Starting height of the component.
*/
startingHeight?: number | string, startingHeight?: number | string,
} }


@@ -58,11 +61,14 @@ export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectP
hiddenLabel = false, hiddenLabel = false,
className, className,
startingHeight = '15rem', startingHeight = '15rem',
id: idProp,
...etcProps ...etcProps
}: MenuSelectProps, }: MenuSelectProps,
ref,
forwardedRef,
) => { ) => {
const labelId = React.useId(); const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return ( return (
<div <div
@@ -78,7 +84,8 @@ export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectP
> >
<select <select
{...etcProps} {...etcProps}
ref={ref}
ref={forwardedRef}
id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
data-testid="input" data-testid="input"
size={2} size={2}
@@ -131,6 +138,7 @@ export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectP
label && ( label && (
<label <label
data-testid="label" data-testid="label"
htmlFor={id}
id={labelId} id={labelId}
className={clsx( className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative', 'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
@@ -208,7 +216,19 @@ export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectP
} }
</div> </div>
); );
}
},
); );


MenuSelect.displayName = 'MenuSelect'; MenuSelect.displayName = 'MenuSelect';

MenuSelect.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
startingHeight: '15rem',
};

+ 29
- 17
categories/choice/react/src/components/RadioButton/index.tsx View File

@@ -10,22 +10,25 @@ export interface RadioButtonProps extends Omit<React.InputHTMLAttributes<RadioBu
size?: Button.Size; size?: Button.Size;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode; badge?: React.ReactNode;
variant: Button.Variant;
variant?: Button.Variant;
} }


export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButtonProps>(({
children,
block = false,
compact = false,
size = 'medium',
id: idProp,
className,
subtext,
badge,
variant,
style,
...etcProps
}, forwardedRef) => {
export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButtonProps>((
{
children,
block = false,
compact = false,
size = 'medium' as const,
id: idProp,
className,
subtext,
badge,
variant = 'bare' as const,
style,
...etcProps
},
forwardedRef,
) => {
const defaultId = React.useId(); const defaultId = React.useId();
const id = idProp ?? defaultId; const id = idProp ?? defaultId;
return ( return (
@@ -55,7 +58,7 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
'pl-4 pr-4': !compact, 'pl-4 pr-4': !compact,
}, },
{ {
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary' : variant !== 'bare',
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled', 'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
}, },
{ {
@@ -73,7 +76,7 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
'-mr-2': compact, '-mr-2': compact,
'border-current': variant !== 'filled', 'border-current': variant !== 'filled',
'border-negative': variant === 'filled', 'border-negative': variant === 'filled',
}
},
)} )}
> >
<span <span
@@ -135,7 +138,16 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
</span> </span>
</label> </label>
</> </>
)
);
}); });


RadioButton.displayName = 'RadioButton'; RadioButton.displayName = 'RadioButton';

RadioButton.defaultProps = {
badge: undefined,
block: false,
compact: false,
subtext: undefined,
size: 'medium',
variant: 'filled',
};

+ 18
- 12
categories/choice/react/src/components/RadioTickBox/index.tsx View File

@@ -6,19 +6,20 @@ export type RadioTickBoxDerivedElement = HTMLInputElement;
export interface RadioTickBoxProps extends Omit<React.InputHTMLAttributes<RadioTickBoxDerivedElement>, 'type' | 'size'> { export interface RadioTickBoxProps extends Omit<React.InputHTMLAttributes<RadioTickBoxDerivedElement>, 'type' | 'size'> {
block?: boolean; block?: boolean;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode;
} }


export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTickBoxProps>(({
children,
block = false,
id: idProp,
className,
subtext,
badge,
style,
...etcProps
}, forwardedRef) => {
export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTickBoxProps>((
{
children,
block = false,
id: idProp,
className,
subtext,
style,
...etcProps
},
forwardedRef,
) => {
const defaultId = React.useId(); const defaultId = React.useId();
const id = idProp ?? defaultId; const id = idProp ?? defaultId;
return ( return (
@@ -80,7 +81,12 @@ export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTi
</div> </div>
)} )}
</div> </div>
)
);
}); });


RadioTickBox.displayName = 'RadioTickBox'; RadioTickBox.displayName = 'RadioTickBox';

RadioTickBox.defaultProps = {
block: false,
subtext: undefined,
};

+ 3
- 0
categories/color/react/.eslintrc View File

@@ -1,5 +1,8 @@
{ {
"root": true, "root": true,
"rules": {
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 22
- 19
categories/color/react/src/components/Swatch/index.tsx View File

@@ -12,9 +12,10 @@ export interface SwatchProps extends Omit<React.HTMLProps<SwatchDerivedElement>,


export const useSwatchControls = () => { export const useSwatchControls = () => {
const id = React.useId(); const id = React.useId();
const copyColor: React.ReactEventHandler<SwatchDerivedElement> = React.useCallback(async (e) => {
const copyColor: React.ReactEventHandler<SwatchDerivedElement> = React.useCallback((e) => {
const { value } = e.currentTarget; const { value } = e.currentTarget;
await window.navigator.clipboard.writeText(value);
// eslint-disable-next-line no-void
void window.navigator.clipboard.writeText(value);
}, []); }, []);
return React.useMemo(() => ({ return React.useMemo(() => ({
id, id,
@@ -72,23 +73,21 @@ export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({
</span> </span>
<span className="tabular-nums text-xs sr-only"> <span className="tabular-nums text-xs sr-only">
{ {
color
.map((c) => c
.toString()
.padStart(4, ' ')
.split('')
.map((cc, j) => (
<span
key={`${cc}:${j}`}
className={clsx({
'opacity-0': cc === ' ',
})}
>
{j === 0 && ' '}
{cc === ' ' && j > 0 ? '0' : cc}
</span>
))
)
color.map((c) => c
.toString()
.padStart(4, ' ')
.split('')
.map((cc, j) => (
<span
key={`${cc}:${j}`}
className={clsx({
'opacity-0': cc === ' ',
})}
>
{j === 0 && ' '}
{cc === ' ' && j > 0 ? '0' : cc}
</span>
)))
} }
</span> </span>
</label> </label>
@@ -97,3 +96,7 @@ export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({
}); });


Swatch.displayName = 'Swatch'; Swatch.displayName = 'Swatch';

Swatch.defaultProps = {
mode: 'rgb',
};

+ 4
- 0
categories/formatted/react/.eslintrc View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 24
- 6
categories/formatted/react/src/components/EmailInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type EmailInputDerivedElement = HTMLInputElement; export type EmailInputDerivedElement = HTMLInputElement;


export interface EmailInputProps extends Omit<React.HTMLProps<EmailInputDerivedElement>, 'size' | 'type' | 'label'> {
export interface EmailInputProps extends Omit<React.HTMLProps<EmailInputDerivedElement>, 'size' | 'type' | 'label' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -54,11 +54,15 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
variant = 'default' as const, variant = 'default' as const,
hiddenLabel = false, hiddenLabel = false,
className, className,
id: idProp,
style,
...etcProps ...etcProps
}: EmailInputProps, }: EmailInputProps,
ref,
forwardedRef,
) => { ) => {
const labelId = React.useId(); const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return ( return (
<div <div
@@ -71,10 +75,12 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
}, },
className, className,
)} )}
style={style}
> >
<input <input
{...etcProps} {...etcProps}
ref={ref}
ref={forwardedRef}
id={id}
aria-labelledby={labelId} aria-labelledby={labelId}
type="email" type="email"
data-testid="input" data-testid="input"
@@ -114,8 +120,9 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
<label <label
data-testid="label" data-testid="label"
id={labelId} id={labelId}
htmlFor={id}
className={clsx( className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{ {
'sr-only': hiddenLabel, 'sr-only': hiddenLabel,
}, },
@@ -139,7 +146,7 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
<div <div
data-testid="hint" data-testid="hint"
className={clsx( className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'bottom-0 pl-4 pb-1': variant === 'default', 'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate', 'top-0.5': variant === 'alternate',
@@ -169,7 +176,7 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
{indicator && ( {indicator && (
<div <div
className={clsx( className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{ {
'w-10': size === 'small', 'w-10': size === 'small',
'w-12': size === 'medium', 'w-12': size === 'medium',
@@ -194,3 +201,14 @@ export const EmailInput = React.forwardRef<EmailInputDerivedElement, EmailInputP
); );


EmailInput.displayName = 'EmailInput'; EmailInput.displayName = 'EmailInput';

EmailInput.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 149
- 130
categories/formatted/react/src/components/PhoneNumberInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type PhoneNumberInputDerivedElement = HTMLInputElement; export type PhoneNumberInputDerivedElement = HTMLInputElement;


export interface PhoneNumberInputProps extends Omit<React.HTMLProps<PhoneNumberInputDerivedElement>, 'size' | 'type' | 'label'> {
export interface PhoneNumberInputProps extends Omit<React.HTMLProps<PhoneNumberInputDerivedElement>, 'size' | 'type' | 'label' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -42,155 +42,174 @@ export interface PhoneNumberInputProps extends Omit<React.HTMLProps<PhoneNumberI
/** /**
* Component for inputting textual values. * Component for inputting textual values.
*/ */
export const PhoneNumberInput = React.forwardRef<PhoneNumberInputDerivedElement, PhoneNumberInputProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: PhoneNumberInputProps,
ref,
) => {
const labelId = React.useId();
export const PhoneNumberInput = React.forwardRef<
PhoneNumberInputDerivedElement,
PhoneNumberInputProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
...etcProps
}: PhoneNumberInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
>
<input
{...etcProps}
ref={forwardedRef}
aria-labelledby={labelId}
type="tel"
id={id}
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{ {
'block': block,
'inline-block align-middle': !block,
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type="tel"
data-testid="input"
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pt-4': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div <div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


PhoneNumberInput.displayName = 'PhoneNumberInput'; PhoneNumberInput.displayName = 'PhoneNumberInput';

PhoneNumberInput.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 146
- 130
categories/formatted/react/src/components/UrlInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type UrlInputDerivedElement = HTMLInputElement; export type UrlInputDerivedElement = HTMLInputElement;


export interface UrlInputProps extends Omit<React.HTMLProps<UrlInputDerivedElement>, 'size' | 'type' | 'label'> {
export interface UrlInputProps extends Omit<React.HTMLProps<UrlInputDerivedElement>, 'size' | 'type' | 'label' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -42,155 +42,171 @@ export interface UrlInputProps extends Omit<React.HTMLProps<UrlInputDerivedEleme
/** /**
* Component for inputting textual values. * Component for inputting textual values.
*/ */
export const UrlInput = React.forwardRef<UrlInputDerivedElement, UrlInputProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: UrlInputProps,
ref,
) => {
const labelId = React.useId();
export const UrlInput = React.forwardRef<UrlInputDerivedElement, UrlInputProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
style,
id: idProp,
...etcProps
}: UrlInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="url"
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{ {
'block': block,
'inline-block align-middle': !block,
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type="url"
data-testid="input"
/>
{
label && (
<label
data-testid="label"
htmlFor={id}
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pt-4': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div <div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


UrlInput.displayName = 'UrlInput'; UrlInput.displayName = 'UrlInput';

UrlInput.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 4
- 0
categories/freeform/react/.eslintrc View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 148
- 129
categories/freeform/react/src/components/MaskedTextInput/index.tsx View File

@@ -42,155 +42,174 @@ export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInp
/** /**
* Component for inputting textual values. * Component for inputting textual values.
*/ */
export const MaskedTextInput = React.forwardRef<MaskedTextInputDerivedElement, MaskedTextInputProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: MaskedTextInputProps,
ref,
) => {
const labelId = React.useId();
export const MaskedTextInput = React.forwardRef<
MaskedTextInputDerivedElement,
MaskedTextInputProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
...etcProps
}: MaskedTextInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="password"
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{ {
'block': block,
'inline-block align-middle': !block,
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type="password"
data-testid="input"
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pt-4': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div <div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


MaskedTextInput.displayName = 'MaskedTextInput'; MaskedTextInput.displayName = 'MaskedTextInput';

MaskedTextInput.defaultProps = {
label: undefined,
hint: undefined,
size: 'medium',
indicator: undefined,
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 162
- 143
categories/freeform/react/src/components/MultilineTextInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type MultilineTextInputDerivedElement = HTMLTextAreaElement; export type MultilineTextInputDerivedElement = HTMLTextAreaElement;


export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineTextInputDerivedElement>, 'size' | 'style' | 'label'> {
export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineTextInputDerivedElement>, 'size' | 'label' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -44,168 +44,187 @@ export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineT
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const MultilineTextInput = React.forwardRef<MultilineTextInputDerivedElement, MultilineTextInputProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: MultilineTextInputProps,
ref,
) => {
const labelId = React.useId();
export const MultilineTextInput = React.forwardRef<
MultilineTextInputDerivedElement,
MultilineTextInputProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
style,
id: idProp,
...etcProps
}: MultilineTextInputProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
>
<textarea
{...etcProps}
ref={forwardedRef}
aria-labelledby={labelId}
id={id}
data-testid="input"
style={{
height: 0,
}}
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
{
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{ {
'block': block,
'inline-block align-middle': !block,
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
}, },
className,
)} )}
>
<textarea
{...etcProps}
ref={ref}
aria-labelledby={labelId}
data-testid="input"
style={{
height: 0,
}}
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
>
<div <div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
}
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


MultilineTextInput.displayName = 'MultilineTextInput'; MultilineTextInput.displayName = 'MultilineTextInput';

MultilineTextInput.defaultProps = {
label: undefined,
hint: undefined,
size: 'medium',
indicator: undefined,
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
};

+ 162
- 131
categories/freeform/react/src/components/TextInput/index.tsx View File

@@ -4,7 +4,7 @@ import clsx from 'clsx';


export type TextInputDerivedElement = HTMLInputElement; export type TextInputDerivedElement = HTMLInputElement;


export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'inputMode'> {
/** /**
* Short textual description indicating the nature of the component's value. * Short textual description indicating the nature of the component's value.
*/ */
@@ -41,6 +41,10 @@ export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedEle
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Input mode of the component.
*/
inputMode?: TextControl.InputMode,
} }


/** /**
@@ -48,156 +52,183 @@ export interface TextInputProps extends Omit<React.HTMLProps<TextInputDerivedEle
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProps>(
(
{
label = '',
hint = '',
indicator,
size = 'medium' as const,
border = false,
block = false,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: TextInputProps,
ref,
) => {
const labelId = React.useId();
export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
type = 'text' as const,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
style,
inputMode = type,
...etcProps
},
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;

let resultInputMode = inputMode as React.HTMLProps<TextInputDerivedElement>['inputMode'];
if (type === 'text' && resultInputMode === 'search') {
resultInputMode = 'text';
} else if (type === 'search' && resultInputMode === 'text') {
resultInputMode = 'search';
}


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50 overflow-hidden',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
style={style}
>
<input
{...etcProps}
ref={forwardedRef}
aria-labelledby={labelId}
id={id}
type={type}
inputMode={resultInputMode}
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{ {
'block': block,
'inline-block align-middle': !block,
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type={type}
data-testid="input"
/>
{
label && (
<label
data-testid="label"
htmlFor={id}
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none',
{ {
'text-xxs': size === 'small',
'text-xs': size === 'medium',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pt-4': variant === 'alternate',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
>
<div <div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
}
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


TextInput.displayName = 'TextInput'; TextInput.displayName = 'TextInput';

TextInput.defaultProps = {
label: undefined,
hint: undefined,
size: 'medium',
indicator: undefined,
border: false,
block: false,
type: 'text',
variant: 'default',
hiddenLabel: false,
inputMode: 'text',
};

+ 3
- 0
categories/information/react/.eslintrc View File

@@ -1,5 +1,8 @@
{ {
"root": true, "root": true,
"rules": {
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 16
- 9
categories/information/react/src/components/Badge/index.tsx View File

@@ -7,12 +7,15 @@ export interface BadgeProps extends React.HTMLProps<BadgeDerivedElement> {
rounded?: boolean; rounded?: boolean;
} }


export const Badge = React.forwardRef<BadgeDerivedElement, BadgeProps>(({
children,
rounded = false,
className,
...etcProps
}, forwardedRef) => (
export const Badge = React.forwardRef<BadgeDerivedElement, BadgeProps>((
{
children,
rounded = false,
className,
...etcProps
},
forwardedRef,
) => (
<span <span
{...etcProps} {...etcProps}
ref={forwardedRef} ref={forwardedRef}
@@ -26,10 +29,14 @@ export const Badge = React.forwardRef<BadgeDerivedElement, BadgeProps>(({
className, className,
)} )}
> >
<span className="relative w-full">
{children}
</span>
<span className="relative w-full">
{children}
</span> </span>
</span>
)); ));


Badge.displayName = 'Badge'; Badge.displayName = 'Badge';

Badge.defaultProps = {
rounded: false,
};

+ 13
- 5
categories/information/react/src/components/KeyValueTable/index.tsx View File

@@ -14,11 +14,14 @@ export interface KeyValueTableProps extends Omit<React.HTMLProps<KeyValueTableDe
properties?: (KeyValueProperty | boolean)[]; properties?: (KeyValueProperty | boolean)[];
} }


export const KeyValueTable = React.forwardRef<KeyValueTableDerivedElement, KeyValueTableProps>(({
hiddenKeys = false,
properties = [],
...etcProps
}, forwardedRef) => (
export const KeyValueTable = React.forwardRef<KeyValueTableDerivedElement, KeyValueTableProps>((
{
hiddenKeys = false,
properties = [],
...etcProps
},
forwardedRef,
) => (
<dl <dl
{...etcProps} {...etcProps}
className={clsx( className={clsx(
@@ -55,3 +58,8 @@ export const KeyValueTable = React.forwardRef<KeyValueTableDerivedElement, KeyVa
)); ));


KeyValueTable.displayName = 'KeyValueTable'; KeyValueTable.displayName = 'KeyValueTable';

KeyValueTable.defaultProps = {
hiddenKeys: false,
properties: [],
};

+ 4
- 0
categories/multichoice/react/.eslintrc View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 166
- 145
categories/multichoice/react/src/components/MenuMultiSelect/index.tsx View File

@@ -37,6 +37,9 @@ export interface MenuMultiSelectProps extends Omit<React.HTMLProps<MenuMultiSele
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Starting height of the component.
*/
startingHeight?: number | string, startingHeight?: number | string,
} }


@@ -45,171 +48,189 @@ export interface MenuMultiSelectProps extends Omit<React.HTMLProps<MenuMultiSele
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const MenuMultiSelect = React.forwardRef<MenuMultiSelectDerivedElement, MenuMultiSelectProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
startingHeight = '15rem',
...etcProps
}: MenuMultiSelectProps,
ref,
) => {
const labelId = React.useId();
export const MenuMultiSelect = React.forwardRef<
MenuMultiSelectDerivedElement,
MenuMultiSelectProps
>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
startingHeight = '15rem',
id: idProp,
...etcProps
},
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
>
<select
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
data-testid="input"
size={2}
multiple
style={{
height: startingHeight,
}}
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block overflow-auto',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
{
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{ {
'block': block,
'inline-block align-middle': !block,
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
}, },
className,
)} )}
>
<select
{...etcProps}
ref={ref}
aria-labelledby={labelId}
data-testid="input"
size={2}
multiple
style={{
height: startingHeight,
}}
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block overflow-auto',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
{
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div <div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
}
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


MenuMultiSelect.displayName = 'MenuMultiSelect'; MenuMultiSelect.displayName = 'MenuMultiSelect';

MenuMultiSelect.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
border: false,
block: false,
variant: 'default',
hiddenLabel: false,
startingHeight: '15rem',
};

+ 244
- 213
categories/multichoice/react/src/components/TagInput/index.tsx View File

@@ -1,10 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import { TagsInput } from 'react-tag-input-component'; import { TagsInput } from 'react-tag-input-component';
import clsx from 'clsx'; import clsx from 'clsx';
import {useClientSide, delegateTriggerEvent} from '@modal-sh/react-utils';
import { useClientSide, delegateTriggerEvent } from '@modal-sh/react-utils';
import { TextControl } from '@tesseract-design/web-base'; import { TextControl } from '@tesseract-design/web-base';


export type TagInputSeparator = ',' | '\n';
const TAG_INPUT_SEPARATOR_MAP = {
'comma': ',',
'newline': 'Enter',
'semicolon': ';',
} as const;

export type TagInputSeparator = keyof typeof TAG_INPUT_SEPARATOR_MAP


export type TagInputDerivedElement = HTMLTextAreaElement; export type TagInputDerivedElement = HTMLTextAreaElement;


@@ -41,7 +47,13 @@ export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedEleme
* Is the label hidden? * Is the label hidden?
*/ */
hiddenLabel?: boolean, hiddenLabel?: boolean,
/**
* Should the component be enhanced?
*/
enhanced?: boolean, enhanced?: boolean,
/**
* Separators for splitting the input value into multiple tags.
*/
separator?: TagInputSeparator[], separator?: TagInputSeparator[],
} }


@@ -50,205 +62,182 @@ export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedEleme
* *
* This component supports multiline input and adjusts its layout accordingly. * This component supports multiline input and adjusts its layout accordingly.
*/ */
export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
(
{
label = '',
hint = '',
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
enhanced: enhancedProp = false,
separator = ['\n'],
defaultValue,
disabled,
...etcProps
}: TagInputProps,
forwardedRef,
) => {
const {clientSide} = useClientSide({ clientSide: enhancedProp });
const defaultRef = React.useRef<TagInputDerivedElement>(null);
const ref = forwardedRef ?? defaultRef;
const labelId = React.useId();
const tags = React.useMemo(() => {
if (defaultValue === undefined) {
return [];
}
if (typeof defaultValue === 'string') {
return separator.reduce((theDefaultValues, separator) => {
return theDefaultValues.join(separator).split(separator);
}, [defaultValue]);
}
if (typeof defaultValue === 'number') {
return [defaultValue.toString()];
}
return defaultValue as string[];
}, [defaultValue, separator]);
export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
enhanced: enhancedProp = false,
separator = ['newline'],
defaultValue,
disabled,
id: idProp,
...etcProps
},
forwardedRef,
) => {
const { clientSide } = useClientSide({ clientSide: enhancedProp });
const defaultRef = React.useRef<TagInputDerivedElement>(null);
const ref = forwardedRef ?? defaultRef;
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;
const tags = React.useMemo(() => {
if (defaultValue === undefined) {
return [];
}
if (typeof defaultValue === 'string') {
return separator.reduce((theDefaultValues, s) => {
if (s === 'newline') {
return theDefaultValues.join('\n').split('\n');
}
return theDefaultValues.join(s).split(s);
}, [defaultValue]);
}
if (typeof defaultValue === 'number') {
return [defaultValue.toString()];
}
return defaultValue as string[];
}, [defaultValue, separator]);


const handleTagsInputChange = (tags: string[]) => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
}
setTimeout(() => {
delegateTriggerEvent('change', input, tags.map((t => t.trim())).join('\n'));
});
};
const handleTagsInputChange = (newTags: string[]) => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
}
setTimeout(() => {
delegateTriggerEvent('change', input, newTags.map(((t) => t.trim())).join('\n'));
});
};


const handleBlur = () => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
}
setTimeout(() => {
delegateTriggerEvent('blur', input);
});
const handleBlur = () => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
} }
setTimeout(() => {
delegateTriggerEvent('blur', input);
});
};


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50 group',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
clientSide && {
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
'tesseract-design-tag-input',
size === 'small' && 'tag-input-small',
size === 'medium' && 'tag-input-medium',
size === 'large' && 'tag-input-large',
indicator && 'tag-input-indicator',
variant === 'default' && 'tag-input-default',
variant === 'alternate' && 'tag-input-alternate',
className,
)}
>
<textarea
{...etcProps}
disabled={disabled}
ref={ref}
id={id}
aria-labelledby={labelId}
data-testid="input"
defaultValue={defaultValue}
style={{
height: clientSide ? undefined : 0,
}}
className={clsx( className={clsx(
'relative rounded ring-secondary/50 group',
'focus-within:ring-4',
'bg-negative rounded-inherit peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{ {
'block': block,
'inline-block align-middle': !block,
'resize': !block,
'resize-y': block,
}, },
clientSide && {
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
!clientSide && {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
!clientSide && {
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
!clientSide && {
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
!clientSide && {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
!clientSide && {
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
!clientSide && {
'min-h-10': size === 'small', 'min-h-10': size === 'small',
'min-h-12': size === 'medium', 'min-h-12': size === 'medium',
'min-h-16': size === 'large', 'min-h-16': size === 'large',
}, },
'tesseract-design-tag-input',
size === 'small' && 'tag-input-small',
size === 'medium' && 'tag-input-medium',
size === 'large' && 'tag-input-large',
indicator && 'tag-input-indicator',
variant === 'default' && 'tag-input-default',
variant === 'alternate' && 'tag-input-alternate',
className,
!clientSide && 'peer',
!clientSide && 'w-full',
clientSide && 'sr-only',
)} )}
>
<textarea
{...etcProps}
disabled={disabled}
ref={ref}
aria-labelledby={labelId}
data-testid="input"
defaultValue={defaultValue}
style={{
height: clientSide ? undefined : 0,
/>
{clientSide && (
<TagsInput
value={tags}
classNames={{
input: 'peer bg-transparent',
tag: 'text-xs p-2',
}} }}
className={clsx(
'bg-negative rounded-inherit peer block',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'resize': !block,
'resize-y': block,
},
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
!clientSide && {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
!clientSide && {
'pt-4': variant === 'alternate' && size === 'small',
'pt-5': variant === 'alternate' && size === 'medium',
'pt-8': variant === 'alternate' && size === 'large',
},
!clientSide && {
'py-2.5': variant === 'default' && size === 'small',
'py-3': variant === 'default' && size === 'medium',
'py-5': variant === 'default' && size === 'large',
},
!clientSide && {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
!clientSide && {
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
!clientSide && {
'min-h-10': size === 'small',
'min-h-12': size === 'medium',
'min-h-16': size === 'large',
},
!clientSide && 'peer',
!clientSide && 'w-full',
clientSide && 'sr-only',
)}
disabled={disabled}
onChange={handleTagsInputChange}
onBlur={handleBlur}
separators={separator.map((s) => TAG_INPUT_SEPARATOR_MAP[s])}
/> />
{clientSide && (
<TagsInput
value={tags}
classNames={{
input: 'peer bg-transparent',
tag: 'text-xs p-2',
}}
disabled={disabled}
onChange={handleTagsInputChange}
onBlur={handleBlur}
separators={separator.map((separator) => separator === '\n' ? 'Enter' : separator)}
/>
)}
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 group-focus-within:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
)}
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx( className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 group-focus-within:text-secondary text-primary leading-none bg-negative',
{ {
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
'sr-only': hiddenLabel,
}, },
{ {
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
'pr-1': !indicator,
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
@@ -257,38 +246,80 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
}, },
)} )}
> >
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div <div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
/>
)
}
</div>
);
}
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
/>
)
}
</div>
);
});


TagInput.displayName = 'TagInput'; TagInput.displayName = 'TagInput';

TagInput.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
size: 'medium',
variant: 'default',
separator: ['newline'],
border: false,
block: false,
hiddenLabel: false,
enhanced: false,
};

+ 32
- 19
categories/multichoice/react/src/components/ToggleButton/index.tsx View File

@@ -10,23 +10,26 @@ export interface ToggleButtonProps extends Omit<React.InputHTMLAttributes<Toggle
size?: Button.Size; size?: Button.Size;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode; badge?: React.ReactNode;
variant: Button.Variant;
variant?: Button.Variant;
indeterminate?: boolean; indeterminate?: boolean;
} }


export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleButtonProps>(({
children,
block = false,
compact = false,
size = 'medium',
id: idProp,
className,
subtext,
badge,
variant,
indeterminate = false,
...etcProps
}, forwardedRef) => {
export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleButtonProps>((
{
children,
block = false,
compact = false,
size = 'medium' as const,
id: idProp,
className,
subtext,
badge,
variant = 'bare' as const,
indeterminate = false,
...etcProps
},
forwardedRef,
) => {
const defaultRef = React.useRef<ToggleButtonDerivedElement>(null); const defaultRef = React.useRef<ToggleButtonDerivedElement>(null);
const ref = forwardedRef ?? defaultRef; const ref = forwardedRef ?? defaultRef;
const defaultId = React.useId(); const defaultId = React.useId();
@@ -69,7 +72,7 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
'pl-4 pr-4': !compact, 'pl-4 pr-4': !compact,
}, },
{ {
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary' : variant !== 'bare',
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled', 'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
}, },
{ {
@@ -87,7 +90,7 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
'border-current': variant !== 'filled', 'border-current': variant !== 'filled',
'border-negative': variant === 'filled', 'border-negative': variant === 'filled',
'-mr-2': compact, '-mr-2': compact,
}
},
)} )}
> >
<svg <svg
@@ -96,7 +99,7 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
{ {
'stroke-negative': variant === 'filled', 'stroke-negative': variant === 'filled',
'stroke-current': variant !== 'filled', 'stroke-current': variant !== 'filled',
}
},
)} )}
viewBox="0 0 24 24" viewBox="0 0 24 24"
role="presentation" role="presentation"
@@ -111,7 +114,7 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
{ {
'stroke-negative': variant === 'filled', 'stroke-negative': variant === 'filled',
'stroke-current': variant !== 'filled', 'stroke-current': variant !== 'filled',
}
},
)} )}
viewBox="0 0 24 24" viewBox="0 0 24 24"
role="presentation" role="presentation"
@@ -170,7 +173,17 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
</span> </span>
</label> </label>
</> </>
)
);
}); });


ToggleButton.displayName = 'ToggleButton'; ToggleButton.displayName = 'ToggleButton';

ToggleButton.defaultProps = {
block: false,
compact: false,
size: 'medium',
subtext: undefined,
badge: undefined,
indeterminate: false,
variant: 'bare',
};

+ 23
- 14
categories/multichoice/react/src/components/ToggleSwitch/index.tsx View File

@@ -6,24 +6,25 @@ export type ToggleSwitchDerivedElement = HTMLInputElement;
export interface ToggleSwitchProps extends Omit<React.InputHTMLAttributes<ToggleSwitchDerivedElement>, 'type' | 'size'> { export interface ToggleSwitchProps extends Omit<React.InputHTMLAttributes<ToggleSwitchDerivedElement>, 'type' | 'size'> {
block?: boolean; block?: boolean;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode;
indeterminate?: boolean; indeterminate?: boolean;
checkedLabel?: React.ReactNode; checkedLabel?: React.ReactNode;
uncheckedLabel?: React.ReactNode; uncheckedLabel?: React.ReactNode;
} }


export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleSwitchProps>(({
uncheckedLabel,
checkedLabel,
block = false,
id: idProp,
className,
subtext,
badge,
style,
indeterminate = false,
...etcProps
}, forwardedRef) => {
export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleSwitchProps>((
{
uncheckedLabel,
checkedLabel,
block = false,
id: idProp,
className,
subtext,
style,
indeterminate = false,
...etcProps
},
forwardedRef,
) => {
const defaultRef = React.useRef<ToggleSwitchDerivedElement>(null); const defaultRef = React.useRef<ToggleSwitchDerivedElement>(null);
const ref = forwardedRef ?? defaultRef; const ref = forwardedRef ?? defaultRef;
const defaultId = React.useId(); const defaultId = React.useId();
@@ -112,7 +113,15 @@ export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleS
</div> </div>
)} )}
</div> </div>
)
);
}); });


ToggleSwitch.displayName = 'ToggleSwitch'; ToggleSwitch.displayName = 'ToggleSwitch';

ToggleSwitch.defaultProps = {
block: false,
subtext: undefined,
indeterminate: false,
checkedLabel: undefined,
uncheckedLabel: undefined,
};

+ 20
- 13
categories/multichoice/react/src/components/ToggleTickBox/index.tsx View File

@@ -6,21 +6,22 @@ export type ToggleTickBoxDerivedElement = HTMLInputElement;
export interface ToggleTickBoxProps extends Omit<React.InputHTMLAttributes<ToggleTickBoxDerivedElement>, 'type' | 'size'> { export interface ToggleTickBoxProps extends Omit<React.InputHTMLAttributes<ToggleTickBoxDerivedElement>, 'type' | 'size'> {
block?: boolean; block?: boolean;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode;
indeterminate?: boolean; indeterminate?: boolean;
} }


export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, ToggleTickBoxProps>(({
children,
block = false,
id: idProp,
className,
subtext,
badge,
style,
indeterminate = false,
...etcProps
}, forwardedRef) => {
export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, ToggleTickBoxProps>((
{
children,
block = false,
id: idProp,
className,
subtext,
style,
indeterminate = false,
...etcProps
},
forwardedRef,
) => {
const defaultRef = React.useRef<ToggleTickBoxDerivedElement>(null); const defaultRef = React.useRef<ToggleTickBoxDerivedElement>(null);
const ref = forwardedRef ?? defaultRef; const ref = forwardedRef ?? defaultRef;
const defaultId = React.useId(); const defaultId = React.useId();
@@ -113,7 +114,13 @@ export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, Toggl
</div> </div>
)} )}
</div> </div>
)
);
}); });


ToggleTickBox.displayName = 'ToggleTickBox'; ToggleTickBox.displayName = 'ToggleTickBox';

ToggleTickBox.defaultProps = {
block: false,
indeterminate: false,
subtext: undefined,
};

+ 3
- 0
categories/navigation/react/.eslintrc View File

@@ -1,5 +1,8 @@
{ {
"root": true, "root": true,
"rules": {
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 32
- 18
categories/navigation/react/src/components/LinkButton/index.tsx View File

@@ -4,30 +4,33 @@ import { Button } from '@tesseract-design/web-base';


export type LinkButtonDerivedElement = HTMLAnchorElement; export type LinkButtonDerivedElement = HTMLAnchorElement;


export interface LinkButtonProps extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'size'> {
export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'size'> {
block?: boolean; block?: boolean;
variant: Button.Variant;
variant?: Button.Variant;
subtext?: React.ReactNode; subtext?: React.ReactNode;
badge?: React.ReactNode; badge?: React.ReactNode;
menuItem?: boolean; menuItem?: boolean;
size?: Button.Size; size?: Button.Size;
compact?: boolean; compact?: boolean;
component?: React.ElementType;
component?: React.ElementType<T>;
} }


export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonProps>(({
variant,
subtext,
badge,
menuItem = false,
children,
size = 'medium' as const,
compact = false,
className,
block = false,
component: Component = 'a',
...etcProps
}, forwardedRef) => (
export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonProps>((
{
variant = 'bare' as const,
subtext,
badge,
menuItem = false,
children,
size = 'medium' as const,
compact = false,
className,
block = false,
component: Component = 'a',
...etcProps
},
forwardedRef,
) => (
<Component <Component
{...etcProps} {...etcProps}
ref={forwardedRef} ref={forwardedRef}
@@ -46,7 +49,7 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
'pr-2': compact || menuItem, 'pr-2': compact || menuItem,
}, },
{ {
'border-2 border-primary focus:border-secondary active:border-tertiary' : variant !== 'bare',
'border-2 border-primary focus:border-secondary active:border-tertiary': variant !== 'bare',
'bg-negative text-primary focus:text-secondary active:text-tertiary': variant !== 'filled', 'bg-negative text-primary focus:text-secondary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative focus:bg-secondary active:bg-tertiary': variant === 'filled', 'bg-primary text-negative focus:bg-secondary active:bg-tertiary': variant === 'filled',
}, },
@@ -108,7 +111,7 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
viewBox="0 0 24 24" viewBox="0 0 24 24"
role="presentation" role="presentation"
> >
<polyline points="9 18 15 12 9 6"/>
<polyline points="9 18 15 12 9 6" />
</svg> </svg>
</span> </span>
)} )}
@@ -116,3 +119,14 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
)); ));


LinkButton.displayName = 'LinkButton'; LinkButton.displayName = 'LinkButton';

LinkButton.defaultProps = {
variant: 'bare',
size: 'medium',
compact: false,
menuItem: false,
component: 'a',
badge: undefined,
subtext: undefined,
block: false,
};

+ 4
- 0
categories/number/react/.eslintrc View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
},
"extends": [ "extends": [
"lxsmnsyc/typescript/react" "lxsmnsyc/typescript/react"
], ],


+ 107
- 60
categories/number/react/src/components/Slider/index.tsx View File

@@ -29,8 +29,9 @@ const filterOptions = (children: React.ReactNode): React.ReactNode => {
/* /*
* Caveat for slider: * Caveat for slider:
* *
* Since sliders are not as customizable as other components, especially with orientations, prefer using sliders where
* using horizontal sliders would be as acceptable as vertical sliders and vv.
* Since sliders are not as customizable as other components, especially with orientations,
* prefer using sliders where using horizontal sliders would be as acceptable as vertical
* sliders and vv.
*/ */


export type SliderOrientation = 'horizontal' | 'vertical'; export type SliderOrientation = 'horizontal' | 'vertical';
@@ -43,16 +44,19 @@ export interface SliderProps extends Omit<React.HTMLProps<HTMLInputElement>, 'ty
length?: React.CSSProperties['width']; length?: React.CSSProperties['width'];
} }


export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
className,
style,
children,
orient = 'horizontal',
length,
min = 0,
max = 100,
...etcProps
}, forwardedRef) => {
export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>((
{
className,
style,
children,
orient = 'horizontal',
length,
min = 0,
max = 100,
...etcProps
},
forwardedRef,
) => {
const [browser, setBrowser] = React.useState<string>(); const [browser, setBrowser] = React.useState<string>();
React.useEffect(() => { React.useEffect(() => {
const isFirefox = typeof (window as unknown as Record<string, unknown>).InstallTrigger !== 'undefined'; const isFirefox = typeof (window as unknown as Record<string, unknown>).InstallTrigger !== 'undefined';
@@ -69,27 +73,43 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
const tickMarkId = React.useId(); const tickMarkId = React.useId();


React.useEffect(() => { React.useEffect(() => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const {current: slider} = ref;
if (!slider) {
return;
}
const setupGecko = (theOrient: string, theChildren: unknown, theBrowser?: string) => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: slider } = ref;
if (!slider) {
return;
}


const isFirefox = browser === 'firefox';
const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;
if (isFirefox) {
slider.setAttribute('orient', orient);
wrapper.dataset[browser] = orient;
wrapper.removeAttribute('data-orient');
grandParent.style.width = children ? '2.5em' : '1em';
}
const isFirefox = theBrowser === 'firefox';
const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;
if (isFirefox) {
slider.setAttribute('orient', theOrient);
wrapper.dataset[theBrowser] = theOrient;
wrapper.removeAttribute('data-orient');
grandParent.style.width = theChildren ? '2.5em' : '1em';
}
};

setupGecko(orient, children, browser);


return () => { return () => {
if (slider && isFirefox) {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: slider } = ref;
if (!slider) {
return;
}

const isFirefox = browser === 'firefox';
const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;
if (slider && isFirefox && parent && grandParent && wrapper) {
grandParent.style.width = 'auto'; grandParent.style.width = 'auto';
wrapper.removeAttribute(`data-${browser}`); wrapper.removeAttribute(`data-${browser}`);
wrapper.dataset.orient = slider.getAttribute(orient) ?? undefined; wrapper.dataset.orient = slider.getAttribute(orient) ?? undefined;
@@ -99,42 +119,63 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
}, [ref, orient, children, browser]); }, [ref, orient, children, browser]);


React.useEffect(() => { React.useEffect(() => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const {current: slider} = ref;
if (!slider) {
return;
}
let shouldEffectExecute: boolean;
const setupNonGecko = (theOrient: string, theBrowser?: string) => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: slider } = ref;
if (!slider) {
return;
}


const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;
if (!theBrowser) {
return;
}


const isNotFirefox = typeof browser === 'string' && browser !== 'firefox';
if (isNotFirefox) {
wrapper.dataset[browser] = orient;
}
const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;


const shouldEffectExecute = isNotFirefox && orient === 'vertical' && slider && parent && grandParent;
if (shouldEffectExecute) {
const trueHeight = parent.clientWidth;
const trueWidth = parent.clientHeight;
const isNotFirefox = theBrowser !== 'firefox';
if (isNotFirefox) {
wrapper.dataset[theBrowser] = theOrient;
}


grandParent.dataset.height = grandParent.clientHeight.toString();
grandParent.dataset.width = grandParent.clientWidth.toString();
grandParent.style.height = trueHeight + 'px';
grandParent.style.width = trueWidth + 'px';
}
shouldEffectExecute = isNotFirefox && theOrient === 'vertical' && Boolean(slider) && Boolean(parent) && Boolean(grandParent);
if (shouldEffectExecute) {
const trueHeight = parent.clientWidth;
const trueWidth = parent.clientHeight;

grandParent.dataset.height = grandParent.clientHeight.toString();
grandParent.dataset.width = grandParent.clientWidth.toString();
grandParent.style.height = `${trueHeight}px`;
grandParent.style.width = `${trueWidth}px`;
}
};

setupNonGecko(orient, browser);


return () => { return () => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: slider } = ref;
if (!slider) {
return;
}

const wrapper = slider?.parentElement as HTMLElement;
const parent = wrapper?.parentElement as HTMLElement;
const grandParent = parent?.parentElement as HTMLElement;

if (shouldEffectExecute) { if (shouldEffectExecute) {
grandParent.style.height = grandParent.dataset.height + 'px';
grandParent.style.width = grandParent.dataset.width + 'px';
grandParent.style.height = `${grandParent.dataset.height ?? 0}px`;
grandParent.style.width = `${grandParent.dataset.width ?? 0}px`;
grandParent.removeAttribute('data-height'); grandParent.removeAttribute('data-height');
grandParent.removeAttribute('data-width'); grandParent.removeAttribute('data-width');
} }
wrapper.removeAttribute(`data-${browser}`);
wrapper.removeAttribute(`data-${browser ?? 'unknown'}`);
}; };
}, [ref, orient, browser]); }, [ref, orient, browser]);


@@ -142,7 +183,7 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
if (!(typeof ref === 'object' && ref)) { if (!(typeof ref === 'object' && ref)) {
return; return;
} }
const {current: slider} = ref;
const { current: slider } = ref;
if (!slider) { if (!slider) {
return; return;
} }
@@ -266,7 +307,7 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
<div <div
key={c.props.value} key={c.props.value}
className="absolute w-full h-full box-border" className="absolute w-full h-full box-border"
data-offset={`${(value - minValue) / (maxValue - minValue) * 100}%`}
data-offset={`${((value - minValue) / (maxValue - minValue)) * 100}%`}
> >
<div <div
className="flex h-full text-xs items-center justify-between" className="flex h-full text-xs items-center justify-between"
@@ -281,7 +322,7 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
</div> </div>
</div> </div>
</div> </div>
)
);
}) })
} }
</div> </div>
@@ -291,7 +332,13 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({
</div> </div>
</div> </div>
</> </>
)
);
}); });


Slider.displayName = 'Slider'; Slider.displayName = 'Slider';

Slider.defaultProps = {
orient: 'horizontal',
length: undefined,
children: undefined,
};

+ 144
- 130
categories/number/react/src/components/Spinner/index.tsx View File

@@ -42,156 +42,170 @@ export interface SpinnerProps extends Omit<React.HTMLProps<SpinnerDerivedElement
/** /**
* Component for inputting numeric values. * Component for inputting numeric values.
*/ */
export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>(
(
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
...etcProps
}: SpinnerProps,
ref,
) => {
const labelId = React.useId();
export const Spinner = React.forwardRef<SpinnerDerivedElement, SpinnerProps>((
{
label,
hint,
indicator,
size = 'medium' as const,
border = false,
block = false,
variant = 'default' as const,
hiddenLabel = false,
className,
id: idProp,
...etcProps
}: SpinnerProps,
forwardedRef,
) => {
const labelId = React.useId();
const defaultId = React.useId();
const id = idProp ?? defaultId;


return (
<div
return (
<div
className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
{
'block': block,
'inline-block align-middle': !block,
},
className,
)}
>
<input
{...etcProps}
ref={forwardedRef}
id={id}
aria-labelledby={labelId}
type="number"
data-testid="input"
className={clsx( className={clsx(
'relative rounded ring-secondary/50',
'focus-within:ring-4',
'bg-negative rounded-inherit w-full peer block tabular-nums',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
'tesseract-design-spinner',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
{
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
},
{
'pt-4': variant === 'alternate',
},
{ {
'block': block,
'inline-block align-middle': !block,
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
}, },
className,
)} )}
>
<input
{...etcProps}
ref={ref}
aria-labelledby={labelId}
type="number"
data-testid="input"
/>
{
label && (
<label
data-testid="label"
id={labelId}
htmlFor={id}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative select-none',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
<div
data-testid="hint"
className={clsx( className={clsx(
'bg-negative rounded-inherit w-full peer block tabular-nums',
'focus:outline-0',
'disabled:opacity-50 disabled:cursor-not-allowed',
'tesseract-design-spinner',
{
'text-xxs': size === 'small',
'text-xs': size === 'medium',
},
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative select-none text-primary',
{ {
'pl-4': variant === 'default',
'pl-1.5': variant === 'alternate',
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
}, },
{ {
'pt-4': variant === 'alternate',
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
}, },
{ {
'pr-4': variant === 'default' && !indicator,
'pr-1.5': variant === 'alternate' && !indicator,
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
}, },
{ {
'pr-10': indicator && size === 'small', 'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium', 'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large', 'pr-16': indicator && size === 'large',
}, },
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
)} )}
/>
{
label && (
<label
data-testid="label"
id={labelId}
className={clsx(
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
{
'sr-only': hiddenLabel,
},
{
'pr-1': !indicator,
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
{label}
</span>
</label>
)
}
{hint && (
>
<div <div
data-testid="hint"
className={clsx(
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
{
'bottom-0 pl-4 pb-1': variant === 'default',
'top-0.5': variant === 'alternate',
},
{
'pt-2': variant === 'alternate' && size === 'small',
'pt-3': variant === 'alternate' && size !== 'small',
},
{
'pr-4': !indicator && variant === 'default',
'pr-1': !indicator && variant === 'alternate',
},
{
'pr-10': indicator && size === 'small',
'pr-12': indicator && size === 'medium',
'pr-16': indicator && size === 'large',
},
)}
>
<div
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
>
{hint}
</div>
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
> >
{indicator}
{hint}
</div> </div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
},
);
</div>
)}
{indicator && (
<div
className={clsx(
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
{
'w-10': size === 'small',
'w-12': size === 'medium',
'w-16': size === 'large',
},
)}
>
{indicator}
</div>
)}
{
border && (
<span
data-testid="border"
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
/>
)
}
</div>
);
});


Spinner.displayName = 'Spinner'; Spinner.displayName = 'Spinner';

Spinner.defaultProps = {
label: undefined,
hint: undefined,
indicator: undefined,
border: false,
block: false,
hiddenLabel: false,
size: 'medium',
variant: 'default',
};

Loading…
Cancel
Save