[ui] add textSize prop to typeahead

pull/451/head
Yangshun Tay 2 years ago
parent a1fc43242e
commit 9df6b52392

@ -1,8 +1,13 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import type { ComponentMeta } from '@storybook/react'; import type { ComponentMeta } from '@storybook/react';
import type { TypeaheadOption } from '@tih/ui'; import type { TypeaheadOption, TypeaheadTextSize } from '@tih/ui';
import { Typeahead } from '@tih/ui'; import { Typeahead } from '@tih/ui';
const typeaheadTextSizes: ReadonlyArray<TypeaheadTextSize> = [
'default',
'inherit',
];
export default { export default {
argTypes: { argTypes: {
disabled: { disabled: {
@ -23,6 +28,10 @@ export default {
required: { required: {
control: 'boolean', control: 'boolean',
}, },
textSize: {
control: { type: 'select' },
options: typeaheadTextSizes,
},
}, },
component: Typeahead, component: Typeahead,
parameters: { parameters: {

@ -10,6 +10,7 @@ export type TypeaheadOption = Readonly<{
label: string; label: string;
value: string; value: string;
}>; }>;
export type TypeaheadTextSize = 'default' | 'inherit';
type Attributes = Pick< type Attributes = Pick<
InputHTMLAttributes<HTMLInputElement>, InputHTMLAttributes<HTMLInputElement>,
@ -33,10 +34,16 @@ type Props = Readonly<{
) => void; ) => void;
onSelect: (option: TypeaheadOption) => void; onSelect: (option: TypeaheadOption) => void;
options: ReadonlyArray<TypeaheadOption>; options: ReadonlyArray<TypeaheadOption>;
textSize?: TypeaheadTextSize;
value?: TypeaheadOption; value?: TypeaheadOption;
}> & }> &
Readonly<Attributes>; Readonly<Attributes>;
const textSizes: Record<TypeaheadTextSize, string> = {
default: 'text-sm',
inherit: '',
};
export default function Typeahead({ export default function Typeahead({
disabled = false, disabled = false,
isLabelHidden, isLabelHidden,
@ -46,108 +53,123 @@ export default function Typeahead({
options, options,
onQueryChange, onQueryChange,
required, required,
textSize = 'default',
value, value,
onSelect, onSelect,
...props ...props
}: Props) { }: Props) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
return ( return (
<Combobox <div>
by="id" <Combobox
disabled={disabled} by="id"
// eslint-disable-next-line @typescript-eslint/ban-ts-comment disabled={disabled}
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
multiple={false} // @ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment multiple={false}
// @ts-ignore
nullable={nullable}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
value={value}
onChange={(newValue) => {
if (newValue == null) {
return;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
onSelect(newValue as TypeaheadOption); nullable={nullable}
}}> // eslint-disable-next-line @typescript-eslint/ban-ts-comment
<Combobox.Label // @ts-ignore
className={clsx( value={value}
isLabelHidden onChange={(newValue) => {
? 'sr-only' if (newValue == null) {
: 'mb-1 block text-sm font-medium text-slate-700', return;
)}> }
{label}
{required && ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment
<span aria-hidden="true" className="text-danger-500"> // @ts-ignore
{' '} onSelect(newValue as TypeaheadOption);
* }}>
</span> <Combobox.Label
)} className={clsx(
</Combobox.Label> isLabelHidden
<div className="relative"> ? 'sr-only'
<div className="focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2"> : clsx(
<Combobox.Input 'mb-1 block font-medium text-slate-700',
textSizes[textSize],
),
)}>
{label}
{required && (
<span aria-hidden="true" className="text-danger-500">
{' '}
*
</span>
)}
</Combobox.Label>
<div className="relative">
<div
className={clsx( className={clsx(
'w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-slate-900 focus:ring-0', 'focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2',
disabled && 'pointer-events-none select-none bg-slate-100', textSizes[textSize],
)} )}>
displayValue={(option) => <Combobox.Input
(option as unknown as TypeaheadOption)?.label className={clsx(
} 'w-full border-none py-2 pl-3 pr-10 leading-5 text-slate-900 focus:ring-0',
required={required} textSizes[textSize],
onChange={(event) => { disabled && 'pointer-events-none select-none bg-slate-100',
setQuery(event.target.value); )}
onQueryChange(event.target.value, event); displayValue={(option) =>
}} (option as unknown as TypeaheadOption)?.label
{...props} }
/> required={required}
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2"> onChange={(event) => {
<ChevronDownIcon setQuery(event.target.value);
aria-hidden="true" onQueryChange(event.target.value, event);
className="h-5 w-5 text-slate-400" }}
{...props}
/> />
</Combobox.Button> <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon
aria-hidden="true"
className="h-5 w-5 text-slate-400"
/>
</Combobox.Button>
</div>
<Transition
afterLeave={() => setQuery('')}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<Combobox.Options
className={clsx(
'absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none',
textSizes[textSize],
)}>
{options.length === 0 && query !== '' ? (
<div className="relative cursor-default select-none py-2 px-4 text-slate-700">
{noResultsMessage}
</div>
) : (
options.map((option) => (
<Combobox.Option
key={option.id}
className={({ active }) =>
clsx(
'relative cursor-default select-none py-2 px-4 text-slate-500',
active && 'bg-slate-100',
)
}
value={option}>
{({ selected }) => (
<span
className={clsx(
'block truncate',
selected ? 'font-medium' : 'font-normal',
)}>
{option.label}
</span>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div> </div>
<Transition </Combobox>
afterLeave={() => setQuery('')} </div>
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{options.length === 0 && query !== '' ? (
<div className="relative cursor-default select-none py-2 px-4 text-slate-700">
{noResultsMessage}
</div>
) : (
options.map((option) => (
<Combobox.Option
key={option.id}
className={({ active }) =>
clsx(
'relative cursor-default select-none py-2 px-4 text-slate-500',
active && 'bg-slate-100',
)
}
value={option}>
{({ selected }) => (
<span
className={clsx(
'block truncate',
selected ? 'font-medium' : 'font-normal',
)}>
{option.label}
</span>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
); );
} }

Loading…
Cancel
Save