diff --git a/package-lock.json b/package-lock.json index 3703baa6c..d9ef86a07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33621,6 +33621,7 @@ "@spark-ui/icon-button": "^2.2.3", "@spark-ui/icons": "^1.21.6", "@spark-ui/popover": "^1.5.5", + "@spark-ui/spinner": "^2.2.2", "@spark-ui/use-merge-refs": "^0.4.0", "@spark-ui/visually-hidden": "^1.2.0", "class-variance-authority": "0.7.0", diff --git a/packages/components/combobox/package.json b/packages/components/combobox/package.json index fa89bf40d..a03815cb9 100644 --- a/packages/components/combobox/package.json +++ b/packages/components/combobox/package.json @@ -34,6 +34,7 @@ "@spark-ui/icon-button": "^2.2.3", "@spark-ui/icons": "^1.21.6", "@spark-ui/popover": "^1.5.5", + "@spark-ui/spinner": "^2.2.2", "@spark-ui/use-merge-refs": "^0.4.0", "@spark-ui/visually-hidden": "^1.2.0", "class-variance-authority": "0.7.0", diff --git a/packages/components/combobox/src/Combobox.doc.mdx b/packages/components/combobox/src/Combobox.doc.mdx index 6475b4295..cfc5b676c 100644 --- a/packages/components/combobox/src/Combobox.doc.mdx +++ b/packages/components/combobox/src/Combobox.doc.mdx @@ -155,6 +155,13 @@ Use `readOnly` prop to indicate the combobox is only readable. +### isLoading + +Use the `isLoading` prop to render the combobox in loading state. +This will prepend a spinner inside the items-list. + + + ### Status Use `state` prop to assign a specific state to the combobox, choosing from: `error`, `alert` and `success`. By doing so, the outline styles will be updated, and a status indicator will be displayed accordingly. @@ -215,6 +222,10 @@ If your `Combobox.Item` contains anything else than raw text, you may use any JS +### Search Modal + + + ## Form field ### Label diff --git a/packages/components/combobox/src/Combobox.stories.tsx b/packages/components/combobox/src/Combobox.stories.tsx index c63984fcc..f2ad64df3 100644 --- a/packages/components/combobox/src/Combobox.stories.tsx +++ b/packages/components/combobox/src/Combobox.stories.tsx @@ -1,6 +1,9 @@ /* eslint-disable max-lines */ // import { Button } from '@spark-ui/button' +import { Button } from '@spark-ui/button' import { Checkbox, CheckboxGroup } from '@spark-ui/checkbox' +import { Chip } from '@spark-ui/chip' +import { Dialog } from '@spark-ui/dialog' import { FormField } from '@spark-ui/form-field' import { PenOutline } from '@spark-ui/icons/dist/icons/PenOutline' import { RadioGroup } from '@spark-ui/radio-group' @@ -780,3 +783,122 @@ export const FormFieldValidation: StoryFn = () => { ) } + +export const IsLoading: StoryFn = _args => { + return ( +
+ + + + + + + + + + + + + No results found + To Kill a Mockingbird + War and Peace + The Idiot + A Picture of Dorian Gray + 1984 + + Pride and Prejudice but it is an extremely long title + + + + +
+ ) +} +export const ModalSearch: StoryFn = () => { + const books = [ + { id: 1, name: 'Things Fall Apart' }, + { id: 2, name: 'The Catcher in the Rye' }, + { id: 3, name: 'The Great Gatsby' }, + { id: 4, name: 'Fairy tales' }, + { id: 5, name: 'The Hobbit' }, + { id: 6, name: 'The Lord of the Rings' }, + { id: 7, name: 'And Then There Were None' }, + { id: 8, name: 'The Da Vinci Code' }, + { id: 9, name: 'The Alchemist' }, + { id: 10, name: 'The Epic Of Gilgamesh' }, + { id: 11, name: 'The Book Thief' }, + { id: 12, name: 'The Little Prince' }, + { id: 13, name: 'The Book Of Job' }, + { id: 14, name: 'The Grapes Of Wrath' }, + { id: 15, name: 'Pride and Prejudice' }, + { id: 16, name: 'The Odyssey' }, + { id: 17, name: 'One Hundred Years of Solitude' }, + { id: 18, name: 'Crime and Punishment' }, + { id: 19, name: 'Gypsy Ballads' }, + { id: 20, name: 'Love in the Time of Cholera' }, + { id: 21, name: 'Hunger' }, + { id: 22, name: 'The Old Man and the Sea' }, + { id: 23, name: 'To Kill a Mockingbird' }, + { id: 24, name: 'War and Peace' }, + { id: 25, name: 'The Idiot' }, + { id: 26, name: 'Scaramouche' }, + { id: 27, name: 'A Picture of Dorian Gray' }, + { id: 28, name: '1984' }, + ] + const [value, setValue] = useState() + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ setIsOpen(!isOpen)}> + { + if (value === undefined) { + setValue(undefined) + } else { + const [_, id] = value.split('-') + const book = books.find(({ id: bookId }) => `${bookId}` === id) + setValue(book ? book?.name : undefined) + } + setIsOpen(false) + }} + defaultValue={value} + > + {value ? ( + setValue(undefined)}> + {value} + + + ) : ( + <> + + + + + + + + + + + + + + + No results found + {books.map(({ name, id }) => ( + {name} + ))} + + + + + + )} + + +
+ ) +} diff --git a/packages/components/combobox/src/ComboboxContext.tsx b/packages/components/combobox/src/ComboboxContext.tsx index 7fc51c92f..554e856c8 100644 --- a/packages/components/combobox/src/ComboboxContext.tsx +++ b/packages/components/combobox/src/ComboboxContext.tsx @@ -35,6 +35,7 @@ export interface ComboboxContextState extends DownshiftState { setLastInteractionType: (type: 'mouse' | 'keyboard') => void innerInputRef: React.RefObject triggerAreaRef: React.RefObject + isLoading?: boolean } export type ComboboxContextCommonProps = PropsWithChildren<{ @@ -75,6 +76,10 @@ export type ComboboxContextCommonProps = PropsWithChildren<{ * If you wish to keep every item on a single line, disabled this property. */ wrap?: boolean + /** + * Display a spinner to indicate to the user that the combobox is loading results for . + */ + isLoading?: boolean }> interface ComboboxPropsSingle { @@ -145,6 +150,7 @@ export const ComboboxProvider = ({ open: controlledOpen, defaultOpen, onOpenChange, + isLoading, }: ComboboxContextProps) => { const isMounted = useRef(false) @@ -395,6 +401,7 @@ export const ComboboxProvider = ({ setInputValue, selectItem: onInternalSelectedItemChange, setSelectedItems: onInternalSelectedItemsChange, + isLoading, }} > {children} diff --git a/packages/components/combobox/src/ComboboxItems.tsx b/packages/components/combobox/src/ComboboxItems.tsx index 0116a1b03..95655fdd8 100644 --- a/packages/components/combobox/src/ComboboxItems.tsx +++ b/packages/components/combobox/src/ComboboxItems.tsx @@ -1,3 +1,4 @@ +import { Spinner } from '@spark-ui/spinner' import { useMergeRefs } from '@spark-ui/use-merge-refs' import { cx } from 'class-variance-authority' import { forwardRef, ReactNode, type Ref } from 'react' @@ -21,19 +22,25 @@ export const Items = forwardRef( const ref = useMergeRefs(forwardedRef, downshiftRef) + const isOpen = ctx.hasPopover ? ctx.isOpen : true + + console.log('isLoading', ctx.isLoading) + return (
    + {ctx.isLoading && } {children}
)