mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-02-03 02:24:47 +08:00
[ui] add textSize prop to typeahead
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import React, { useState } from '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';
|
||||
|
||||
const typeaheadTextSizes: ReadonlyArray<TypeaheadTextSize> = [
|
||||
'default',
|
||||
'inherit',
|
||||
];
|
||||
|
||||
export default {
|
||||
argTypes: {
|
||||
disabled: {
|
||||
@@ -23,6 +28,10 @@ export default {
|
||||
required: {
|
||||
control: 'boolean',
|
||||
},
|
||||
textSize: {
|
||||
control: { type: 'select' },
|
||||
options: typeaheadTextSizes,
|
||||
},
|
||||
},
|
||||
component: Typeahead,
|
||||
parameters: {
|
||||
|
||||
@@ -10,6 +10,7 @@ export type TypeaheadOption = Readonly<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>;
|
||||
export type TypeaheadTextSize = 'default' | 'inherit';
|
||||
|
||||
type Attributes = Pick<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
@@ -33,10 +34,16 @@ type Props = Readonly<{
|
||||
) => void;
|
||||
onSelect: (option: TypeaheadOption) => void;
|
||||
options: ReadonlyArray<TypeaheadOption>;
|
||||
textSize?: TypeaheadTextSize;
|
||||
value?: TypeaheadOption;
|
||||
}> &
|
||||
Readonly<Attributes>;
|
||||
|
||||
const textSizes: Record<TypeaheadTextSize, string> = {
|
||||
default: 'text-sm',
|
||||
inherit: '',
|
||||
};
|
||||
|
||||
export default function Typeahead({
|
||||
disabled = false,
|
||||
isLabelHidden,
|
||||
@@ -46,108 +53,123 @@ export default function Typeahead({
|
||||
options,
|
||||
onQueryChange,
|
||||
required,
|
||||
textSize = 'default',
|
||||
value,
|
||||
onSelect,
|
||||
...props
|
||||
}: Props) {
|
||||
const [query, setQuery] = useState('');
|
||||
return (
|
||||
<Combobox
|
||||
by="id"
|
||||
disabled={disabled}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
multiple={false}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
nullable={nullable}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
<div>
|
||||
<Combobox
|
||||
by="id"
|
||||
disabled={disabled}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
onSelect(newValue as TypeaheadOption);
|
||||
}}>
|
||||
<Combobox.Label
|
||||
className={clsx(
|
||||
isLabelHidden
|
||||
? 'sr-only'
|
||||
: 'mb-1 block text-sm font-medium text-slate-700',
|
||||
)}>
|
||||
{label}
|
||||
{required && (
|
||||
<span aria-hidden="true" className="text-danger-500">
|
||||
{' '}
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
</Combobox.Label>
|
||||
<div className="relative">
|
||||
<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">
|
||||
<Combobox.Input
|
||||
multiple={false}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @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
|
||||
// @ts-ignore
|
||||
onSelect(newValue as TypeaheadOption);
|
||||
}}>
|
||||
<Combobox.Label
|
||||
className={clsx(
|
||||
isLabelHidden
|
||||
? 'sr-only'
|
||||
: clsx(
|
||||
'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(
|
||||
'w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-slate-900 focus:ring-0',
|
||||
disabled && 'pointer-events-none select-none bg-slate-100',
|
||||
)}
|
||||
displayValue={(option) =>
|
||||
(option as unknown as TypeaheadOption)?.label
|
||||
}
|
||||
required={required}
|
||||
onChange={(event) => {
|
||||
setQuery(event.target.value);
|
||||
onQueryChange(event.target.value, event);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
<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"
|
||||
'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',
|
||||
textSizes[textSize],
|
||||
)}>
|
||||
<Combobox.Input
|
||||
className={clsx(
|
||||
'w-full border-none py-2 pl-3 pr-10 leading-5 text-slate-900 focus:ring-0',
|
||||
textSizes[textSize],
|
||||
disabled && 'pointer-events-none select-none bg-slate-100',
|
||||
)}
|
||||
displayValue={(option) =>
|
||||
(option as unknown as TypeaheadOption)?.label
|
||||
}
|
||||
required={required}
|
||||
onChange={(event) => {
|
||||
setQuery(event.target.value);
|
||||
onQueryChange(event.target.value, event);
|
||||
}}
|
||||
{...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>
|
||||
<Transition
|
||||
afterLeave={() => setQuery('')}
|
||||
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>
|
||||
</Combobox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user