[ui][select] add errorMessage and placeholder

pull/368/head
Yangshun Tay 2 years ago
parent c7a0c095de
commit b87afb1383

@ -14,6 +14,9 @@ export default {
control: { type: 'select' }, control: { type: 'select' },
options: SelectDisplays, options: SelectDisplays,
}, },
errorMessage: {
control: 'text',
},
isLabelHidden: { isLabelHidden: {
control: 'boolean', control: 'boolean',
}, },
@ -23,6 +26,9 @@ export default {
name: { name: {
control: 'text', control: 'text',
}, },
placeholder: {
control: 'text',
},
}, },
component: Select, component: Select,
title: 'Select', title: 'Select',
@ -181,28 +187,78 @@ export function Required() {
const [value, setValue] = useState('apple'); const [value, setValue] = useState('apple');
return ( return (
<div className="space-x-4"> <Select
<Select label="Select a fruit"
label="Select a fruit" options={[
options={[ {
{ label: 'Apple',
label: 'Apple', value: 'apple',
value: 'apple', },
}, {
{ label: 'Banana',
label: 'Banana', value: 'banana',
value: 'banana', },
}, {
{ label: 'Orange',
label: 'Orange', value: 'orange',
value: 'orange', },
}, ]}
]} required={true}
required={true} value={value}
value={value} onChange={setValue}
onChange={setValue} />
/> );
</div> }
export function Placeholder() {
return (
<Select
label="Select a fruit"
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Orange',
value: 'orange',
},
]}
placeholder="Select a fruit"
required={true}
/>
);
}
export function Error() {
const [value, setValue] = useState('banana');
return (
<Select
errorMessage={value !== 'apple' ? 'Must select apple' : undefined}
label="Select a fruit"
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Orange',
value: 'orange',
},
]}
required={true}
value={value}
onChange={setValue}
/>
); );
} }

@ -20,11 +20,13 @@ type Props<T> = Readonly<{
borderStyle?: SelectBorderStyle; borderStyle?: SelectBorderStyle;
defaultValue?: T; defaultValue?: T;
display?: SelectDisplay; display?: SelectDisplay;
errorMessage?: string;
isLabelHidden?: boolean; isLabelHidden?: boolean;
label: string; label: string;
name?: string; name?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
options: ReadonlyArray<SelectItem<T>>; options: ReadonlyArray<SelectItem<T>>;
placeholder?: string;
value?: T; value?: T;
}> & }> &
Readonly<Attributes>; Readonly<Attributes>;
@ -34,15 +36,25 @@ const borderClasses: Record<SelectBorderStyle, string> = {
borderless: 'border-transparent bg-transparent', borderless: 'border-transparent bg-transparent',
}; };
type State = 'error' | 'normal';
const stateClasses: Record<State, string> = {
error:
'border-danger-300 text-danger-900 placeholder-danger-300 focus:outline-none focus:ring-danger-500 focus:border-danger-500',
normal: 'focus:border-primary-500 focus:ring-primary-500',
};
function Select<T>( function Select<T>(
{ {
borderStyle = 'bordered', borderStyle = 'bordered',
defaultValue, defaultValue,
display, display,
disabled, disabled,
errorMessage,
label, label,
isLabelHidden, isLabelHidden,
options, options,
placeholder,
required, required,
value, value,
onChange, onChange,
@ -50,7 +62,10 @@ function Select<T>(
}: Props<T>, }: Props<T>,
ref: ForwardedRef<HTMLSelectElement>, ref: ForwardedRef<HTMLSelectElement>,
) { ) {
const hasError = errorMessage != null;
const id = useId(); const id = useId();
const errorId = useId();
const state: State = hasError ? 'error' : 'normal';
return ( return (
<div> <div>
@ -69,10 +84,12 @@ function Select<T>(
)} )}
<select <select
ref={ref} ref={ref}
aria-describedby={hasError ? errorId : undefined}
aria-label={isLabelHidden ? label : undefined} aria-label={isLabelHidden ? label : undefined}
className={clsx( className={clsx(
display === 'block' && 'block w-full', display === 'block' && 'block w-full',
'focus:border-primary-500 focus:ring-primary-500 rounded-md py-2 pl-3 pr-8 text-base focus:outline-none sm:text-sm', 'rounded-md py-2 pl-3 pr-8 text-base focus:outline-none sm:text-sm',
stateClasses[state],
borderClasses[borderStyle], borderClasses[borderStyle],
disabled && 'bg-slate-100', disabled && 'bg-slate-100',
)} )}
@ -85,12 +102,22 @@ function Select<T>(
onChange?.(event.target.value); onChange?.(event.target.value);
}} }}
{...props}> {...props}>
{placeholder && (
<option disabled={true} hidden={true} selected={true} value="">
{placeholder}
</option>
)}
{options.map(({ label: optionLabel, value: optionValue }) => ( {options.map(({ label: optionLabel, value: optionValue }) => (
<option key={String(optionValue)} value={String(optionValue)}> <option key={String(optionValue)} value={String(optionValue)}>
{optionLabel} {optionLabel}
</option> </option>
))} ))}
</select> </select>
{errorMessage && (
<p className="text-danger-600 mt-2 text-sm" id={errorId}>
{errorMessage}
</p>
)}
</div> </div>
); );
} }

Loading…
Cancel
Save