From 588db67ae10160d942168b21d815a477f0fe9715 Mon Sep 17 00:00:00 2001 From: jiwlee Date: Wed, 22 Oct 2025 00:38:25 +0900 Subject: [PATCH 1/2] feat: Add glob support for item filtering in Autocomplete component Signed-off-by: jiwlee --- src/components/autocomplete/autocomplete.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/autocomplete/autocomplete.tsx b/src/components/autocomplete/autocomplete.tsx index 665e4ba4..66ae0fdf 100644 --- a/src/components/autocomplete/autocomplete.tsx +++ b/src/components/autocomplete/autocomplete.tsx @@ -1,4 +1,5 @@ import {default as classNames} from 'classnames'; +import { Minimatch, IOptions } from 'minimatch'; import {CSSProperties, ReactNode} from 'react'; import * as React from 'react'; import ReactAutocomplete from 'react-autocomplete'; @@ -27,6 +28,7 @@ export interface AutocompleteProps { qeid?: string; /** @default true */ // per https://github.com/reactjs/react-autocomplete/blob/41388f7d7760bf6cf38e7946e43d4fddd9c7c176/lib/Autocomplete.js#L188 autoHighlight?: ReactAutocomplete.Props['autoHighlight']; + glob?: boolean | IOptions; } export const Autocomplete = (props: AutocompleteProps) => { @@ -107,6 +109,13 @@ export const Autocomplete = (props: AutocompleteProps) => { inputProps={props.inputProps} wrapperProps={wrapperProps} shouldItemRender={(item: AutocompleteOption, val: string) => { + if (props.glob) { + const useGlob = typeof props.glob === 'boolean' ? props.glob : !!props.glob; + const globOptions = typeof props.glob === 'boolean' ? null : props.glob; + const globMatcher = useGlob && val ? new Minimatch(val, globOptions) : null; + + return globMatcher ? globMatcher.match(item.label) : true; + } return !props.filterSuggestions || item.label.toLowerCase().includes(val.toLowerCase()); }} renderMenu={function(menuItems: ReactNode[], _: string, style: CSSProperties) { From 070b69182a78384540cfe8fc4e0d98b2590417fb Mon Sep 17 00:00:00 2001 From: jiwlee Date: Wed, 22 Oct 2025 00:39:18 +0900 Subject: [PATCH 2/2] chore: Add Autocomplete stories with various examples and glob pattern matching Signed-off-by: jiwlee --- stories/autocomplete.stories.tsx | 201 +++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 stories/autocomplete.stories.tsx diff --git a/stories/autocomplete.stories.tsx b/stories/autocomplete.stories.tsx new file mode 100644 index 00000000..55ad116a --- /dev/null +++ b/stories/autocomplete.stories.tsx @@ -0,0 +1,201 @@ +import * as React from 'react'; +import { Autocomplete } from '../src/components/autocomplete/autocomplete'; + +export default { + title: 'Autocomplete', + component: Autocomplete, +}; + +// Sample data +const fruits = [ + 'Apple', + 'Banana', + 'Cherry', + 'Date', + 'Elderberry', + 'Fig', + 'Grape', + 'Honeydew', + 'Kiwi', + 'Lemon', + 'Mango', + 'Orange', + 'Papaya', + 'Quince', + 'Raspberry', + 'Strawberry', + 'Tangerine', + 'Watermelon', +]; + +const complexItems = [ + { value: 'react', label: 'React' }, + { value: 'vue', label: 'Vue.js' }, + { value: 'angular', label: 'Angular' }, + { value: 'svelte', label: 'Svelte' }, + { value: 'nextjs', label: 'Next.js' }, + { value: 'nuxtjs', label: 'Nuxt.js' }, +]; + +// Basic autocomplete story +export const Default = () => { + const [value, setValue] = React.useState(''); + + return ( + setValue(val)} + onSelect={(val) => setValue(val)} + inputProps={{ + placeholder: 'Type to search fruits...', + style: { width: '300px', padding: '8px' }, + }} + /> + ); +}; + +// Complex items with custom labels +export const OptionItems = () => { + const [value, setValue] = React.useState(''); + + return ( + setValue(val)} + onSelect={(val) => setValue(val)} + inputProps={{ + placeholder: 'Select a framework...', + style: { width: '300px', padding: '8px' }, + }} + /> + ); +}; + +// Custom render item +export const CustomRenderItem = () => { + const [value, setValue] = React.useState(''); + + return ( + setValue(val)} + onSelect={(val) => setValue(val)} + inputProps={{ + placeholder: 'Select a framework...', + style: { width: '300px', padding: '8px' }, + }} + renderItem={(item) => ( +
+ + JS + + {item.label} +
+ )} + /> + ); +}; + +// Custom input render +export const CustomInput = () => { + const [value, setValue] = React.useState(''); + + return ( + setValue(val)} + onSelect={(val) => setValue(val)} + renderInput={(props) => ( +
+ + + ⌄ + +
+ )} + /> + ); +}; + +// Glob pattern matching +export const GlobPattern = () => { + const [value, setValue] = React.useState(''); + + const fileItems = [ + 'component.ts', + 'component.spec.ts', + 'component.scss', + 'service.ts', + 'service.spec.ts', + 'model.ts', + 'utils.ts', + 'utils.spec.ts', + 'index.ts', + 'main.scss', + 'theme.scss', + ]; + + return ( +
+
+ Try glob patterns like: *.ts, *.spec.*, component.*, etc. +
+ setValue(val)} + onSelect={(val) => setValue(val)} + inputProps={{ + placeholder: 'Try patterns like *.ts, *.spec.*, component.*', + style: { width: '350px', padding: '8px' }, + }} + /> +
+ ); +};