Last active
April 4, 2023 00:39
-
-
Save nestarz/86ebbccc2d02f4b2a2a556833edf42b0 to your computer and use it in GitHub Desktop.
Revisions
-
nestarz revised this gist
Apr 4, 2023 . 1 changed file with 102 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,102 @@ import { h } from "preact"; import { createContext, ComponentChildren } from "preact"; import { useContext } from "preact/hooks"; import { Signal } from "@preact/signals"; interface ComboboxProps { children: ComponentChildren; value: string | Signal<string>; onChange: (value: string | null) => void; nullable?: boolean; freeSolo?: boolean; } interface ComboboxInputProps { onChange: (event: Event) => void; displayValue: (value: string | null) => string; } interface ComboboxOptionsProps { children: ComponentChildren; } interface ComboboxOptionProps { children: ComponentChildren; value: string; } const ComboboxContext = createContext<{ value: string | Signal<string>; onChange: (value: string | null) => void; nullable?: boolean; freeSolo?: boolean; }>({ value: "", nullable: false, freeSolo: false, onChange: () => {}, }); export const Combobox = ({ value, onChange, nullable, freeSolo, ...props }: ComboboxProps) => { return ( <ComboboxContext.Provider value={{ value, nullable, freeSolo, onChange }}> <div role="combobox" {...props} /> </ComboboxContext.Provider> ); }; const isNullOrUndefined = (v) => v === null || v === undefined; export const ComboboxInput = ({ onChange, displayValue, ...props }: ComboboxInputProps) => { const { value, nullable, ...c } = useContext(ComboboxContext); const getValue = (v) => (v?.props ? v.value : v); const onBlur = (e) => { const newValue = getValue(value); e.target.value = isNullOrUndefined(newValue) ? "" : newValue; }; const handleChange = (e) => { const currValue = e.target.value; onChange(e); if ((c.freeSolo || nullable) && currValue === "") c.onChange(null); else if (c.freeSolo) c.onChange(currValue); }; return ( <input type="text" value={ displayValue?.(getValue(value)) ?? (isNullOrUndefined(getValue(value)) ? "" : value) } onChange={handleChange} onInput={handleChange} onBlur={onBlur} autoComplete="off" {...props} /> ); }; export const ComboboxOptions = ({ ...props }: ComboboxOptionsProps) => { return <ul role="listbox" {...props} />; }; export const ComboboxOption = ({ value, ...props }: ComboboxOptionProps) => { const { onChange } = useContext(ComboboxContext); const handleChange = (e: Event) => { onChange(value); e.target?.blur(); }; return ( <button type="button" role="option" onClick={handleChange} {...props} /> ); }; -
nestarz created this gist
Apr 4, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,57 @@ import { h } from "preact"; import clsx from "clsx"; import { Combobox as Combox, ComboboxInput, ComboboxOption, ComboboxOptions, } from "./Combobox.tsx"; export const Combobox = ({ className, value, onChange, onInputChange, options, nullable, freeSolo, displayValue, }) => { return ( <Combox className={clsx(className, "group relative")} value={value} onChange={onChange} nullable={nullable} freeSolo={freeSolo} > <div className="relative flex items-center justify-center w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm"> <ComboboxInput className="w-full border-none py-2 pl-3 text-sm leading-5 text-gray-900 focus:ring-0 outline-0" onChange={onInputChange} displayValue={displayValue} /> <div tabIndex={0}> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg" className="stroke-black stroke-1 w-5 h-5 fill-none pr-2" > <path d="M1 3 L5 7 L9 3" /> </svg> </div> </div> <ComboboxOptions className="hidden group-focus-within:(absolute min-w-max z-10 flex flex-col mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm)"> {(options.props ? options.value : options)?.map((item) => ( <ComboboxOption className="relative flex items-start cursor-default select-none py-2 pl-6 pr-4 text-gray-900 cursor-pointer hover:(bg-slate-900 text-white)" key={item} value={item} > {displayValue?.(item) ?? item} </ComboboxOption> ))} </ComboboxOptions> </Combox> ); };