@12-apps/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-catalog.md +806 -0
- package/components-guidelines.md +347 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +77 -0
- package/package.json +247 -0
- package/src/__tests__/package-wiring.test.ts +37 -0
- package/src/__tests__/setup.ts +1 -0
- package/src/__tests__/social-login-button.test.tsx +186 -0
- package/src/__tests__/user-avatar.test.tsx +195 -0
- package/src/button.tsx +22 -0
- package/src/components/command/__tests__/command.test.tsx +112 -0
- package/src/components/data-display/Alert/Alert.md +209 -0
- package/src/components/data-display/Alert/Alert.stories.tsx +561 -0
- package/src/components/data-display/Alert/Alert.test.stories.tsx +709 -0
- package/src/components/data-display/Alert/Alert.tsx +449 -0
- package/src/components/data-display/Alert/Alert.types.ts +79 -0
- package/src/components/data-display/Alert/index.ts +2 -0
- package/src/components/data-display/AlertDialog/AlertDialog.md +408 -0
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +360 -0
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +753 -0
- package/src/components/data-display/AlertDialog/AlertDialog.tsx +273 -0
- package/src/components/data-display/AlertDialog/AlertDialog.types.ts +74 -0
- package/src/components/data-display/AlertDialog/index.ts +2 -0
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +230 -0
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +270 -0
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +116 -0
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.types.ts +70 -0
- package/src/components/data-display/AsyncStateContainer/index.ts +2 -0
- package/src/components/data-display/Avatar/Avatar.md +568 -0
- package/src/components/data-display/Avatar/Avatar.stories.tsx +718 -0
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +820 -0
- package/src/components/data-display/Avatar/Avatar.tsx +507 -0
- package/src/components/data-display/Avatar/Avatar.types.ts +88 -0
- package/src/components/data-display/Avatar/index.ts +2 -0
- package/src/components/data-display/Badge/Badge.md +469 -0
- package/src/components/data-display/Badge/Badge.stories.tsx +1136 -0
- package/src/components/data-display/Badge/Badge.test.stories.tsx +1193 -0
- package/src/components/data-display/Badge/Badge.tsx +606 -0
- package/src/components/data-display/Badge/Badge.types.ts +118 -0
- package/src/components/data-display/Badge/index.ts +2 -0
- package/src/components/data-display/Banner/Banner.stories.tsx +524 -0
- package/src/components/data-display/Banner/Banner.test.stories.tsx +503 -0
- package/src/components/data-display/Banner/Banner.tsx +350 -0
- package/src/components/data-display/Banner/Banner.types.ts +41 -0
- package/src/components/data-display/Banner/banner.md +141 -0
- package/src/components/data-display/Banner/index.ts +2 -0
- package/src/components/data-display/Carousel/Carousel.md +815 -0
- package/src/components/data-display/Carousel/Carousel.stories.tsx +607 -0
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +691 -0
- package/src/components/data-display/Carousel/Carousel.tsx +765 -0
- package/src/components/data-display/Carousel/Carousel.types.ts +79 -0
- package/src/components/data-display/Carousel/index.ts +8 -0
- package/src/components/data-display/Chart/Chart.md +352 -0
- package/src/components/data-display/Chart/Chart.stories.tsx +568 -0
- package/src/components/data-display/Chart/Chart.test.stories.tsx +174 -0
- package/src/components/data-display/Chart/Chart.tsx +520 -0
- package/src/components/data-display/Chart/Chart.types.ts +65 -0
- package/src/components/data-display/Chart/index.ts +2 -0
- package/src/components/data-display/Chip/Chip.stories.tsx +251 -0
- package/src/components/data-display/Chip/Chip.test.stories.tsx +263 -0
- package/src/components/data-display/Chip/Chip.tsx +134 -0
- package/src/components/data-display/Chip/Chip.types.ts +51 -0
- package/src/components/data-display/Chip/chip.md +395 -0
- package/src/components/data-display/Chip/index.ts +2 -0
- package/src/components/data-display/DataGrid/DataGrid.md +602 -0
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +515 -0
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +659 -0
- package/src/components/data-display/DataGrid/DataGrid.tsx +743 -0
- package/src/components/data-display/DataGrid/DataGrid.types.ts +171 -0
- package/src/components/data-display/DataGrid/index.ts +21 -0
- package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +197 -0
- package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.types.ts +46 -0
- package/src/components/data-display/DateTimeDisplay/index.ts +2 -0
- package/src/components/data-display/DescriptionItem/DescriptionItem.md +226 -0
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +254 -0
- package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +100 -0
- package/src/components/data-display/DescriptionItem/DescriptionItem.types.ts +1 -0
- package/src/components/data-display/DescriptionItem/index.ts +2 -0
- package/src/components/data-display/EmptyState/EmptyState.md +265 -0
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +282 -0
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +441 -0
- package/src/components/data-display/EmptyState/EmptyState.tsx +180 -0
- package/src/components/data-display/EmptyState/EmptyState.types.ts +31 -0
- package/src/components/data-display/EmptyState/index.ts +2 -0
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +110 -0
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +133 -0
- package/src/components/data-display/ErrorState/ErrorState.tsx +118 -0
- package/src/components/data-display/ErrorState/ErrorState.types.ts +45 -0
- package/src/components/data-display/ErrorState/index.ts +2 -0
- package/src/components/data-display/HoverCard/HoverCard.md +389 -0
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +648 -0
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +929 -0
- package/src/components/data-display/HoverCard/HoverCard.tsx +657 -0
- package/src/components/data-display/HoverCard/HoverCard.types.ts +116 -0
- package/src/components/data-display/HoverCard/index.ts +2 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.md +532 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +411 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +245 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.types.ts +64 -0
- package/src/components/data-display/InteractiveTooltip/index.ts +2 -0
- package/src/components/data-display/LazyImage/LazyImage.md +356 -0
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +387 -0
- package/src/components/data-display/LazyImage/LazyImage.tsx +323 -0
- package/src/components/data-display/LazyImage/LazyImage.types.ts +205 -0
- package/src/components/data-display/LazyImage/index.ts +2 -0
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +315 -0
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +605 -0
- package/src/components/data-display/Lightbox/Lightbox.tsx +666 -0
- package/src/components/data-display/Lightbox/Lightbox.types.ts +49 -0
- package/src/components/data-display/Lightbox/index.ts +2 -0
- package/src/components/data-display/Lightbox/lightbox.md +298 -0
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +124 -0
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +99 -0
- package/src/components/data-display/LoadingState/LoadingState.tsx +103 -0
- package/src/components/data-display/LoadingState/LoadingState.types.ts +37 -0
- package/src/components/data-display/LoadingState/index.ts +2 -0
- package/src/components/data-display/MapPreview/MapPreview.md +154 -0
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +458 -0
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +390 -0
- package/src/components/data-display/MapPreview/MapPreview.tsx +922 -0
- package/src/components/data-display/MapPreview/MapPreview.types.ts +56 -0
- package/src/components/data-display/MapPreview/index.ts +2 -0
- package/src/components/data-display/Popover/Popover.md +245 -0
- package/src/components/data-display/Popover/Popover.stories.tsx +295 -0
- package/src/components/data-display/Popover/Popover.test.stories.tsx +1168 -0
- package/src/components/data-display/Popover/Popover.tsx +132 -0
- package/src/components/data-display/Popover/Popover.types.ts +33 -0
- package/src/components/data-display/Popover/index.ts +2 -0
- package/src/components/data-display/Progress/Progress.md +450 -0
- package/src/components/data-display/Progress/Progress.stories.tsx +481 -0
- package/src/components/data-display/Progress/Progress.test.stories.tsx +2070 -0
- package/src/components/data-display/Progress/Progress.tsx +332 -0
- package/src/components/data-display/Progress/Progress.types.ts +61 -0
- package/src/components/data-display/Progress/index.ts +2 -0
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.md +108 -0
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +160 -0
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +190 -0
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +224 -0
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.types.ts +101 -0
- package/src/components/data-display/SectionOnboarding/index.ts +7 -0
- package/src/components/data-display/Sheet/Sheet.md +475 -0
- package/src/components/data-display/Sheet/Sheet.stories.tsx +883 -0
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +1101 -0
- package/src/components/data-display/Sheet/Sheet.tsx +978 -0
- package/src/components/data-display/Sheet/Sheet.types.ts +93 -0
- package/src/components/data-display/Sheet/index.ts +8 -0
- package/src/components/data-display/Stepper/Stepper.md +542 -0
- package/src/components/data-display/Stepper/Stepper.stories.tsx +540 -0
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +607 -0
- package/src/components/data-display/Stepper/Stepper.tsx +328 -0
- package/src/components/data-display/Stepper/Stepper.types.ts +97 -0
- package/src/components/data-display/Stepper/index.ts +2 -0
- package/src/components/data-display/Table/Table.md +243 -0
- package/src/components/data-display/Table/Table.stories.tsx +801 -0
- package/src/components/data-display/Table/Table.test.stories.tsx +369 -0
- package/src/components/data-display/Table/Table.tsx +831 -0
- package/src/components/data-display/Table/Table.types.ts +335 -0
- package/src/components/data-display/Table/index.ts +2 -0
- package/src/components/data-display/Timeline/Timeline.md +201 -0
- package/src/components/data-display/Timeline/Timeline.stories.tsx +444 -0
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +944 -0
- package/src/components/data-display/Timeline/Timeline.tsx +348 -0
- package/src/components/data-display/Timeline/Timeline.types.ts +26 -0
- package/src/components/data-display/Timeline/index.ts +2 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.md +162 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +475 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +734 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +399 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.types.ts +19 -0
- package/src/components/data-display/TimingDiagram/index.ts +2 -0
- package/src/components/data-display/Tooltip/Tooltip.md +397 -0
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +465 -0
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +1168 -0
- package/src/components/data-display/Tooltip/Tooltip.tsx +161 -0
- package/src/components/data-display/Tooltip/Tooltip.types.ts +33 -0
- package/src/components/data-display/Tooltip/index.ts +2 -0
- package/src/components/data-display/index.ts +29 -0
- package/src/components/feedback/Dialog/Dialog.md +527 -0
- package/src/components/feedback/Dialog/Dialog.stories.tsx +833 -0
- package/src/components/feedback/Dialog/Dialog.styles.ts +171 -0
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +955 -0
- package/src/components/feedback/Dialog/Dialog.tsx +256 -0
- package/src/components/feedback/Dialog/Dialog.types.ts +46 -0
- package/src/components/feedback/Dialog/index.ts +9 -0
- package/src/components/feedback/Modal/Modal.md +629 -0
- package/src/components/feedback/Modal/Modal.stories.tsx +765 -0
- package/src/components/feedback/Modal/Modal.test.stories.tsx +251 -0
- package/src/components/feedback/Modal/Modal.tsx +279 -0
- package/src/components/feedback/Modal/Modal.types.ts +26 -0
- package/src/components/feedback/Modal/index.ts +7 -0
- package/src/components/feedback/Sonner/Sonner.stories.tsx +698 -0
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +896 -0
- package/src/components/feedback/Sonner/Sonner.tsx +493 -0
- package/src/components/feedback/Sonner/Sonner.types.ts +65 -0
- package/src/components/feedback/Sonner/index.ts +8 -0
- package/src/components/feedback/StackedModal/StackedModal.md +273 -0
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +1785 -0
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +975 -0
- package/src/components/feedback/StackedModal/StackedModal.tsx +664 -0
- package/src/components/feedback/StackedModal/StackedModal.types.ts +70 -0
- package/src/components/feedback/StackedModal/index.ts +11 -0
- package/src/components/feedback/Toast/Toast.md +547 -0
- package/src/components/feedback/Toast/Toast.stories.tsx +548 -0
- package/src/components/feedback/Toast/Toast.test.stories.tsx +834 -0
- package/src/components/feedback/Toast/Toast.tsx +279 -0
- package/src/components/feedback/Toast/Toast.types.ts +59 -0
- package/src/components/feedback/Toast/index.ts +8 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.md +610 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +691 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +634 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +554 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.types.ts +30 -0
- package/src/components/feedback/TutorialOverlay/index.ts +2 -0
- package/src/components/feedback/index.ts +6 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.md +633 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +663 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +723 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +836 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +37 -0
- package/src/components/form/AddressAutocomplete/index.ts +2 -0
- package/src/components/form/Autocomplete/Autocomplete.helpers.ts +115 -0
- package/src/components/form/Autocomplete/Autocomplete.keys.ts +134 -0
- package/src/components/form/Autocomplete/Autocomplete.parts.tsx +337 -0
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +715 -0
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +825 -0
- package/src/components/form/Autocomplete/Autocomplete.tsx +166 -0
- package/src/components/form/Autocomplete/Autocomplete.types.ts +116 -0
- package/src/components/form/Autocomplete/autocomplete.md +789 -0
- package/src/components/form/Autocomplete/index.ts +8 -0
- package/src/components/form/Autocomplete/useAutocomplete.ts +311 -0
- package/src/components/form/Button/Button.md +411 -0
- package/src/components/form/Button/Button.stories.tsx +362 -0
- package/src/components/form/Button/Button.test.stories.tsx +603 -0
- package/src/components/form/Button/Button.tsx +326 -0
- package/src/components/form/Button/Button.types.ts +74 -0
- package/src/components/form/Button/index.ts +2 -0
- package/src/components/form/Calendar/Calendar.md +807 -0
- package/src/components/form/Calendar/Calendar.stories.tsx +349 -0
- package/src/components/form/Calendar/Calendar.test.stories.tsx +465 -0
- package/src/components/form/Calendar/Calendar.tsx +571 -0
- package/src/components/form/Calendar/Calendar.types.ts +81 -0
- package/src/components/form/Calendar/index.ts +11 -0
- package/src/components/form/Checkbox/Checkbox.md +200 -0
- package/src/components/form/Checkbox/Checkbox.stories.tsx +265 -0
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +181 -0
- package/src/components/form/Checkbox/Checkbox.tsx +170 -0
- package/src/components/form/Checkbox/Checkbox.types.ts +20 -0
- package/src/components/form/Checkbox/index.ts +2 -0
- package/src/components/form/CodeEditor/CodeEditor.md +350 -0
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +871 -0
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +575 -0
- package/src/components/form/CodeEditor/CodeEditor.tsx +415 -0
- package/src/components/form/CodeEditor/CodeEditor.types.ts +38 -0
- package/src/components/form/CodeEditor/index.ts +2 -0
- package/src/components/form/Command/Command.md +468 -0
- package/src/components/form/Command/Command.stories.tsx +580 -0
- package/src/components/form/Command/Command.test.stories.tsx +215 -0
- package/src/components/form/Command/Command.tsx +545 -0
- package/src/components/form/Command/Command.types.ts +116 -0
- package/src/components/form/Command/index.ts +12 -0
- package/src/components/form/CreatableSelect/CreatableSelect.md +114 -0
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +112 -0
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +134 -0
- package/src/components/form/CreatableSelect/CreatableSelect.tsx +149 -0
- package/src/components/form/CreatableSelect/CreatableSelect.types.ts +37 -0
- package/src/components/form/CreatableSelect/index.ts +2 -0
- package/src/components/form/Form/Form.md +325 -0
- package/src/components/form/Form/Form.stories.tsx +687 -0
- package/src/components/form/Form/Form.test.stories.tsx +601 -0
- package/src/components/form/Form/Form.tsx +136 -0
- package/src/components/form/Form/Form.types.ts +42 -0
- package/src/components/form/Form/index.ts +8 -0
- package/src/components/form/Input/Input.md +252 -0
- package/src/components/form/Input/Input.stories.tsx +256 -0
- package/src/components/form/Input/Input.test.stories.tsx +1184 -0
- package/src/components/form/Input/Input.tsx +263 -0
- package/src/components/form/Input/Input.types.ts +24 -0
- package/src/components/form/Input/index.ts +2 -0
- package/src/components/form/InputOTP/InputOTP.md +419 -0
- package/src/components/form/InputOTP/InputOTP.stories.tsx +774 -0
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +596 -0
- package/src/components/form/InputOTP/InputOTP.tsx +231 -0
- package/src/components/form/InputOTP/InputOTP.types.ts +68 -0
- package/src/components/form/InputOTP/index.ts +2 -0
- package/src/components/form/Label/Label.md +426 -0
- package/src/components/form/Label/Label.stories.tsx +473 -0
- package/src/components/form/Label/Label.test.stories.tsx +909 -0
- package/src/components/form/Label/Label.tsx +367 -0
- package/src/components/form/Label/Label.types.ts +35 -0
- package/src/components/form/Label/index.ts +2 -0
- package/src/components/form/Menubar/Menubar.md +275 -0
- package/src/components/form/Menubar/Menubar.stories.tsx +472 -0
- package/src/components/form/Menubar/Menubar.test.stories.tsx +588 -0
- package/src/components/form/Menubar/Menubar.tsx +467 -0
- package/src/components/form/Menubar/Menubar.types.ts +71 -0
- package/src/components/form/Menubar/index.ts +8 -0
- package/src/components/form/PasswordStrength/PasswordStrength.md +139 -0
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +562 -0
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +327 -0
- package/src/components/form/PasswordStrength/PasswordStrength.tsx +419 -0
- package/src/components/form/PasswordStrength/PasswordStrength.types.ts +18 -0
- package/src/components/form/PasswordStrength/index.ts +2 -0
- package/src/components/form/PhoneInput/PhoneInput.md +578 -0
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +495 -0
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +666 -0
- package/src/components/form/PhoneInput/PhoneInput.tsx +376 -0
- package/src/components/form/PhoneInput/PhoneInput.types.ts +25 -0
- package/src/components/form/PhoneInput/index.ts +2 -0
- package/src/components/form/RadioGroup/RadioGroup.md +477 -0
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +518 -0
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +685 -0
- package/src/components/form/RadioGroup/RadioGroup.tsx +664 -0
- package/src/components/form/RadioGroup/RadioGroup.types.ts +82 -0
- package/src/components/form/RadioGroup/index.ts +2 -0
- package/src/components/form/RichTextEditor/RichTextEditor.md +710 -0
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +261 -0
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +498 -0
- package/src/components/form/RichTextEditor/RichTextEditor.tsx +468 -0
- package/src/components/form/RichTextEditor/RichTextEditor.types.ts +72 -0
- package/src/components/form/RichTextEditor/index.ts +2 -0
- package/src/components/form/SearchPalette/SearchPalette.md +69 -0
- package/src/components/form/SearchPalette/SearchPalette.parts.tsx +358 -0
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +135 -0
- package/src/components/form/SearchPalette/SearchPalette.tsx +141 -0
- package/src/components/form/SearchPalette/SearchPalette.types.ts +82 -0
- package/src/components/form/SearchPalette/index.ts +7 -0
- package/src/components/form/Select/Select.md +254 -0
- package/src/components/form/Select/Select.stories.tsx +364 -0
- package/src/components/form/Select/Select.test.stories.tsx +977 -0
- package/src/components/form/Select/Select.tsx +239 -0
- package/src/components/form/Select/Select.types.ts +57 -0
- package/src/components/form/Select/index.ts +2 -0
- package/src/components/form/Slider/Slider.md +326 -0
- package/src/components/form/Slider/Slider.stories.tsx +418 -0
- package/src/components/form/Slider/Slider.test.stories.tsx +1044 -0
- package/src/components/form/Slider/Slider.tsx +374 -0
- package/src/components/form/Slider/Slider.types.ts +113 -0
- package/src/components/form/Slider/index.ts +2 -0
- package/src/components/form/Switch/Switch.md +565 -0
- package/src/components/form/Switch/Switch.stories.tsx +697 -0
- package/src/components/form/Switch/Switch.test.stories.tsx +597 -0
- package/src/components/form/Switch/Switch.tsx +578 -0
- package/src/components/form/Switch/Switch.types.ts +130 -0
- package/src/components/form/Switch/index.ts +2 -0
- package/src/components/form/Textarea/Textarea.stories.tsx +473 -0
- package/src/components/form/Textarea/Textarea.test.stories.tsx +708 -0
- package/src/components/form/Textarea/Textarea.tsx +678 -0
- package/src/components/form/Textarea/Textarea.types.ts +84 -0
- package/src/components/form/Textarea/index.ts +2 -0
- package/src/components/form/Toggle/Toggle.md +234 -0
- package/src/components/form/Toggle/Toggle.stories.tsx +638 -0
- package/src/components/form/Toggle/Toggle.test.stories.tsx +1167 -0
- package/src/components/form/Toggle/Toggle.tsx +265 -0
- package/src/components/form/Toggle/Toggle.types.ts +79 -0
- package/src/components/form/Toggle/index.ts +2 -0
- package/src/components/form/ToggleGroup/ToggleGroup.md +329 -0
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +617 -0
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +918 -0
- package/src/components/form/ToggleGroup/ToggleGroup.tsx +129 -0
- package/src/components/form/ToggleGroup/ToggleGroup.types.ts +53 -0
- package/src/components/form/ToggleGroup/index.ts +2 -0
- package/src/components/form/UploadButton/UploadButton.md +44 -0
- package/src/components/form/UploadButton/UploadButton.stories.tsx +366 -0
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +454 -0
- package/src/components/form/UploadButton/UploadButton.tsx +312 -0
- package/src/components/form/UploadButton/UploadButton.types.ts +58 -0
- package/src/components/form/UploadButton/index.ts +2 -0
- package/src/components/form/index.ts +25 -0
- package/src/components/form/total-form/Fields/SelectField.tsx +57 -0
- package/src/components/form/total-form/Fields/TextField.tsx +58 -0
- package/src/components/form/total-form/Fields/index.ts +4 -0
- package/src/components/form/total-form/FormContainer.tsx +104 -0
- package/src/components/form/total-form/FormContext.tsx +38 -0
- package/src/components/form/total-form/FormErrorSnackbar.tsx +35 -0
- package/src/components/form/total-form/SubmitButton.tsx +27 -0
- package/src/components/form/total-form/index.ts +12 -0
- package/src/components/form/total-form/total-form.test.tsx +97 -0
- package/src/components/index.ts +7 -0
- package/src/components/layout/Accordion/Accordion.md +725 -0
- package/src/components/layout/Accordion/Accordion.stories.tsx +690 -0
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +1362 -0
- package/src/components/layout/Accordion/Accordion.tsx +242 -0
- package/src/components/layout/Accordion/Accordion.types.ts +32 -0
- package/src/components/layout/Accordion/index.ts +8 -0
- package/src/components/layout/Card/Card.md +533 -0
- package/src/components/layout/Card/Card.stories.tsx +593 -0
- package/src/components/layout/Card/Card.test.stories.tsx +1134 -0
- package/src/components/layout/Card/Card.tsx +404 -0
- package/src/components/layout/Card/Card.types.ts +57 -0
- package/src/components/layout/Card/index.ts +8 -0
- package/src/components/layout/Collapsible/Collapsible.md +135 -0
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +180 -0
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +304 -0
- package/src/components/layout/Collapsible/Collapsible.tsx +198 -0
- package/src/components/layout/Collapsible/Collapsible.types.ts +35 -0
- package/src/components/layout/Collapsible/index.ts +6 -0
- package/src/components/layout/Container/Container.md +420 -0
- package/src/components/layout/Container/Container.stories.tsx +557 -0
- package/src/components/layout/Container/Container.test.stories.tsx +750 -0
- package/src/components/layout/Container/Container.tsx +72 -0
- package/src/components/layout/Container/Container.types.ts +11 -0
- package/src/components/layout/Container/index.ts +2 -0
- package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +99 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.md +75 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +129 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +251 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.tsx +108 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +229 -0
- package/src/components/layout/ContentToolbar/FilterTrigger.tsx +71 -0
- package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +297 -0
- package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +334 -0
- package/src/components/layout/ContentToolbar/SortByDropdown.tsx +213 -0
- package/src/components/layout/ContentToolbar/ViewSelector.tsx +98 -0
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +39 -0
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +110 -0
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +99 -0
- package/src/components/layout/ContentToolbar/index.ts +24 -0
- package/src/components/layout/ContentToolbar/tests.md +42 -0
- package/src/components/layout/ContentToolbar/track.md +41 -0
- package/src/components/layout/Dashboard/Dashboard.md +71 -0
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +104 -0
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +164 -0
- package/src/components/layout/Dashboard/Dashboard.tsx +297 -0
- package/src/components/layout/Dashboard/Dashboard.types.ts +157 -0
- package/src/components/layout/Dashboard/DashboardContext.tsx +47 -0
- package/src/components/layout/Dashboard/DashboardParts.tsx +216 -0
- package/src/components/layout/Dashboard/index.ts +18 -0
- package/src/components/layout/Dashboard/tests.md +46 -0
- package/src/components/layout/Dashboard/track.md +50 -0
- package/src/components/layout/Drawer/Drawer.md +479 -0
- package/src/components/layout/Drawer/Drawer.stories.tsx +660 -0
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +1339 -0
- package/src/components/layout/Drawer/Drawer.tsx +150 -0
- package/src/components/layout/Drawer/Drawer.types.ts +34 -0
- package/src/components/layout/Drawer/index.ts +2 -0
- package/src/components/layout/Resizable/Resizable.md +308 -0
- package/src/components/layout/Resizable/Resizable.stories.tsx +1039 -0
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +750 -0
- package/src/components/layout/Resizable/Resizable.tsx +190 -0
- package/src/components/layout/Resizable/Resizable.types.ts +34 -0
- package/src/components/layout/Resizable/index.ts +2 -0
- package/src/components/layout/ScrollArea/ScrollArea.md +122 -0
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +651 -0
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +619 -0
- package/src/components/layout/ScrollArea/ScrollArea.tsx +449 -0
- package/src/components/layout/ScrollArea/ScrollArea.types.ts +80 -0
- package/src/components/layout/ScrollArea/index.ts +7 -0
- package/src/components/layout/Separator/Separator.stories.tsx +563 -0
- package/src/components/layout/Separator/Separator.test.stories.tsx +527 -0
- package/src/components/layout/Separator/Separator.tsx +187 -0
- package/src/components/layout/Separator/Separator.types.ts +17 -0
- package/src/components/layout/Separator/index.ts +2 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.md +139 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +357 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +194 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +175 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.tsx +69 -0
- package/src/components/layout/SettingsLayout/SettingsLayout.types.ts +61 -0
- package/src/components/layout/SettingsLayout/index.ts +6 -0
- package/src/components/layout/Sidebar/Sidebar.md +367 -0
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +611 -0
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +1326 -0
- package/src/components/layout/Sidebar/Sidebar.tsx +131 -0
- package/src/components/layout/Sidebar/Sidebar.types.ts +30 -0
- package/src/components/layout/Sidebar/index.ts +7 -0
- package/src/components/layout/Skeleton/Skeleton.md +240 -0
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +689 -0
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +521 -0
- package/src/components/layout/Skeleton/Skeleton.tsx +166 -0
- package/src/components/layout/Skeleton/Skeleton.types.ts +19 -0
- package/src/components/layout/Skeleton/index.ts +2 -0
- package/src/components/layout/Spacer/Spacer.md +84 -0
- package/src/components/layout/Spacer/Spacer.stories.tsx +502 -0
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +472 -0
- package/src/components/layout/Spacer/Spacer.tsx +68 -0
- package/src/components/layout/Spacer/Spacer.types.ts +12 -0
- package/src/components/layout/Spacer/index.ts +2 -0
- package/src/components/layout/TableFilter/TableFilter.md +55 -0
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +82 -0
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +87 -0
- package/src/components/layout/TableFilter/TableFilter.tsx +383 -0
- package/src/components/layout/TableFilter/TableFilter.types.ts +87 -0
- package/src/components/layout/TableFilter/TableFilterContext.tsx +29 -0
- package/src/components/layout/TableFilter/index.ts +15 -0
- package/src/components/layout/TableFilter/tests.md +37 -0
- package/src/components/layout/TableFilter/track.md +36 -0
- package/src/components/layout/index.ts +14 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +558 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +640 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +1007 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +516 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +132 -0
- package/src/components/navigation/Breadcrumbs/index.ts +2 -0
- package/src/components/navigation/CommandPalette/CommandPalette.md +201 -0
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +384 -0
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +437 -0
- package/src/components/navigation/CommandPalette/CommandPalette.tsx +434 -0
- package/src/components/navigation/CommandPalette/CommandPalette.types.ts +25 -0
- package/src/components/navigation/CommandPalette/index.ts +2 -0
- package/src/components/navigation/ContextMenu/ContextMenu.md +535 -0
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +611 -0
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +850 -0
- package/src/components/navigation/ContextMenu/ContextMenu.tsx +254 -0
- package/src/components/navigation/ContextMenu/ContextMenu.types.ts +96 -0
- package/src/components/navigation/ContextMenu/index.ts +2 -0
- package/src/components/navigation/DropdownMenu/DropdownMenu.md +514 -0
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +580 -0
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +660 -0
- package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +305 -0
- package/src/components/navigation/DropdownMenu/DropdownMenu.types.ts +116 -0
- package/src/components/navigation/DropdownMenu/index.ts +2 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.md +580 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +874 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.types.ts +135 -0
- package/src/components/navigation/NavigationMenu/index.ts +2 -0
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +518 -0
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +931 -0
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +503 -0
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +455 -0
- package/src/components/navigation/Pagination/Pagination.md +699 -0
- package/src/components/navigation/Pagination/Pagination.stories.tsx +604 -0
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +520 -0
- package/src/components/navigation/Pagination/Pagination.tsx +356 -0
- package/src/components/navigation/Pagination/Pagination.types.ts +129 -0
- package/src/components/navigation/Pagination/index.ts +2 -0
- package/src/components/navigation/Tabs/Tabs.md +774 -0
- package/src/components/navigation/Tabs/Tabs.stories.tsx +1006 -0
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +1225 -0
- package/src/components/navigation/Tabs/Tabs.tsx +433 -0
- package/src/components/navigation/Tabs/Tabs.types.ts +239 -0
- package/src/components/navigation/Tabs/index.ts +2 -0
- package/src/components/navigation/index.ts +7 -0
- package/src/components/typography/Blockquote/Blockquote.md +130 -0
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +359 -0
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +432 -0
- package/src/components/typography/Blockquote/Blockquote.tsx +166 -0
- package/src/components/typography/Blockquote/Blockquote.types.ts +11 -0
- package/src/components/typography/Blockquote/index.ts +2 -0
- package/src/components/typography/Code/Code.md +182 -0
- package/src/components/typography/Code/Code.stories.tsx +686 -0
- package/src/components/typography/Code/Code.test.stories.tsx +703 -0
- package/src/components/typography/Code/Code.tsx +228 -0
- package/src/components/typography/Code/Code.types.ts +12 -0
- package/src/components/typography/Code/index.ts +2 -0
- package/src/components/typography/Heading/Heading.md +143 -0
- package/src/components/typography/Heading/Heading.stories.tsx +451 -0
- package/src/components/typography/Heading/Heading.test.stories.tsx +830 -0
- package/src/components/typography/Heading/Heading.tsx +178 -0
- package/src/components/typography/Heading/Heading.types.ts +11 -0
- package/src/components/typography/Heading/index.ts +2 -0
- package/src/components/typography/Paragraph/Paragraph.md +80 -0
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +280 -0
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +238 -0
- package/src/components/typography/Paragraph/Paragraph.tsx +99 -0
- package/src/components/typography/Paragraph/Paragraph.types.ts +10 -0
- package/src/components/typography/Paragraph/index.ts +2 -0
- package/src/components/typography/Text/Text.md +129 -0
- package/src/components/typography/Text/Text.stories.tsx +359 -0
- package/src/components/typography/Text/Text.test.stories.tsx +762 -0
- package/src/components/typography/Text/Text.tsx +159 -0
- package/src/components/typography/Text/Text.types.ts +15 -0
- package/src/components/typography/Text/index.ts +2 -0
- package/src/components/typography/index.ts +5 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.md +359 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +947 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +596 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.tsx +595 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.types.ts +98 -0
- package/src/components/utility/AnimatedIcon/index.ts +2 -0
- package/src/components/utility/AspectRatio/AspectRatio.md +200 -0
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +414 -0
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +859 -0
- package/src/components/utility/AspectRatio/AspectRatio.tsx +76 -0
- package/src/components/utility/AspectRatio/AspectRatio.types.ts +13 -0
- package/src/components/utility/AspectRatio/index.ts +2 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.md +531 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +620 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +358 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +204 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.types.ts +39 -0
- package/src/components/utility/InfiniteScroll/index.ts +6 -0
- package/src/components/utility/Portal/Portal.button-container.example.tsx +99 -0
- package/src/components/utility/Portal/Portal.md +593 -0
- package/src/components/utility/Portal/Portal.stories.tsx +340 -0
- package/src/components/utility/Portal/Portal.test.stories.tsx +2086 -0
- package/src/components/utility/Portal/Portal.tsx +39 -0
- package/src/components/utility/Portal/Portal.types.ts +7 -0
- package/src/components/utility/Portal/index.ts +2 -0
- package/src/components/utility/Transition/Transition.md +684 -0
- package/src/components/utility/Transition/Transition.stories.tsx +493 -0
- package/src/components/utility/Transition/Transition.test.stories.tsx +854 -0
- package/src/components/utility/Transition/Transition.tsx +140 -0
- package/src/components/utility/Transition/Transition.types.ts +14 -0
- package/src/components/utility/Transition/index.ts +6 -0
- package/src/components/utility/VirtualList/VirtualList.md +520 -0
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +1073 -0
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +1074 -0
- package/src/components/utility/VirtualList/VirtualList.tsx +369 -0
- package/src/components/utility/VirtualList/VirtualList.types.ts +56 -0
- package/src/components/utility/VirtualList/index.ts +7 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.md +187 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +433 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +364 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.tsx +506 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.types.ts +133 -0
- package/src/components/utility/WorkflowStep/index.ts +13 -0
- package/src/components/utility/index.ts +7 -0
- package/src/index.ts +17 -0
- package/src/mui/AppBar.ts +2 -0
- package/src/mui/Box.ts +2 -0
- package/src/mui/CssBaseline.ts +2 -0
- package/src/mui/FormControl.ts +2 -0
- package/src/mui/FormControlLabel.ts +2 -0
- package/src/mui/Grid2.ts +2 -0
- package/src/mui/InputLabel.ts +2 -0
- package/src/mui/List.ts +2 -0
- package/src/mui/ListItem.ts +2 -0
- package/src/mui/ListItemButton.ts +2 -0
- package/src/mui/ListItemText.ts +2 -0
- package/src/mui/Stack.ts +2 -0
- package/src/mui/SvgIcon.ts +2 -0
- package/src/mui/Toolbar.ts +2 -0
- package/src/mui/styles.ts +2 -0
- package/src/mui/useMediaQuery.ts +1 -0
- package/src/social-login-button.tsx +199 -0
- package/src/user-avatar.tsx +102 -0
- package/src/utils/cn.ts +9 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
# Autocomplete — AI Build Instructions
|
|
2
|
+
|
|
3
|
+
> Goal: Implement an agnostic, accessible, and high‑performance Autocomplete component usable for search boxes, dataset filters, tag inputs, and command palettes. Keep it framework-idiomatic (React), type-safe (TypeScript), testable, and styling-agnostic (SCSS or Tailwind adapters allowed).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1) Summary
|
|
8
|
+
|
|
9
|
+
- **Pattern**: ARIA Combobox with popup listbox.
|
|
10
|
+
- **Input**: Controlled string value (`value`, `onChange`).
|
|
11
|
+
- **Suggestions**: Controlled array of generic items (`T`).
|
|
12
|
+
- **Selection**: Emits `onSelect(item)`; optional multi-select with chips.
|
|
13
|
+
- **Typed suggestions**: Optional `search`/`link` items — a `link` shows a link icon and opens its URL in a new tab; a `search` shows a search icon and fills the input.
|
|
14
|
+
- **Inline completion**: Optional ghost remainder and `Tab/→` acceptance (skips `link` items).
|
|
15
|
+
- **Modes**: Free text, async (remote), multiple (chips), custom render, command palette.
|
|
16
|
+
- **Performance**: Debounce, precompute highlights, (optional) virtualization for large lists.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2) Non-Functional Constraints
|
|
21
|
+
|
|
22
|
+
- **Accessibility first**: Follow WAI-ARIA Combobox with Listbox pattern.
|
|
23
|
+
- **No global side effects**: All listeners cleaned up; no scroll lock needed.
|
|
24
|
+
- **No external heavy deps**: Avoid large libs; expose escape hatches for virtualization.
|
|
25
|
+
- **Deterministic focus**: Works keyboard-only; predictable `Tab` order.
|
|
26
|
+
- **SSR-friendly**: Guard window/document usage.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 3) Public API (TypeScript)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export interface AutocompleteProps<T = any> {
|
|
34
|
+
/** Controlled input value */
|
|
35
|
+
value: string;
|
|
36
|
+
/** Input value change */
|
|
37
|
+
onChange: (val: string) => void;
|
|
38
|
+
|
|
39
|
+
/** Suggestion items (generic) */
|
|
40
|
+
suggestions: T[];
|
|
41
|
+
/** Unique key extractor */
|
|
42
|
+
getKey?: (item: T) => string;
|
|
43
|
+
/** Text label extractor */
|
|
44
|
+
getLabel?: (item: T) => string;
|
|
45
|
+
/** Optional description extractor (for SR and UI) */
|
|
46
|
+
getDescription?: (item: T) => string | undefined;
|
|
47
|
+
/** Custom row renderer */
|
|
48
|
+
renderSuggestion?: (item: T, state: { active: boolean; query: string }) => React.ReactNode;
|
|
49
|
+
/** Called when a suggestion is chosen */
|
|
50
|
+
onSelect?: (item: T) => void;
|
|
51
|
+
|
|
52
|
+
/** Resolve a suggestion's type: 'link' opens getUrl(item) on select; 'search' fills the input. */
|
|
53
|
+
getSuggestionType?: (item: T) => 'search' | 'link' | undefined;
|
|
54
|
+
/** Resolve the URL for a 'link' suggestion. */
|
|
55
|
+
getUrl?: (item: T) => string | undefined;
|
|
56
|
+
/** Open a 'link' suggestion. Default: window.open(url, '_blank', 'noopener,noreferrer'). */
|
|
57
|
+
openLink?: (url: string, item: T) => void;
|
|
58
|
+
|
|
59
|
+
/** Allow values not in suggestions */
|
|
60
|
+
allowFreeText?: boolean;
|
|
61
|
+
/** Enable multiple selection (chips) */
|
|
62
|
+
multiple?: boolean;
|
|
63
|
+
/** Selected items in multiple mode */
|
|
64
|
+
selectedItems?: T[];
|
|
65
|
+
/** Update selected items in multiple mode */
|
|
66
|
+
onSelectedItemsChange?: (items: T[]) => void;
|
|
67
|
+
|
|
68
|
+
/** Async mode flags */
|
|
69
|
+
async?: boolean;
|
|
70
|
+
isLoading?: boolean;
|
|
71
|
+
/** Debounce in ms for local filtering or remote fetch triggers */
|
|
72
|
+
debounceMs?: number;
|
|
73
|
+
|
|
74
|
+
/** Inline completion remainder visibility */
|
|
75
|
+
showGhostText?: boolean;
|
|
76
|
+
/** Case/fuzzy options (local filtering only) */
|
|
77
|
+
matchMode?: 'startsWith' | 'contains' | 'fuzzy';
|
|
78
|
+
|
|
79
|
+
/** A11y & structure */
|
|
80
|
+
id?: string; // base id for combobox; derive popup ids from this
|
|
81
|
+
inputAriaLabel?: string; // if no visible label is present
|
|
82
|
+
placeholder?: string;
|
|
83
|
+
autoFocus?: boolean;
|
|
84
|
+
|
|
85
|
+
/** Styling hooks */
|
|
86
|
+
className?: string; // root
|
|
87
|
+
inputClassName?: string;
|
|
88
|
+
listClassName?: string;
|
|
89
|
+
itemClassName?: string;
|
|
90
|
+
activeItemClassName?: string;
|
|
91
|
+
chipClassName?: string;
|
|
92
|
+
|
|
93
|
+
/** Portal the popup if desired */
|
|
94
|
+
portal?: boolean | { container?: Element };
|
|
95
|
+
/** Max visible items before scroll */
|
|
96
|
+
maxVisibleItems?: number;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Defaults**
|
|
101
|
+
|
|
102
|
+
- `getKey = (i:any) => String((i && (i.id ?? i.key ?? i.value ?? i.label)) ?? i)`
|
|
103
|
+
- `getLabel = (i:any) => String(i?.label ?? i?.name ?? i?.text ?? i)`
|
|
104
|
+
- `debounceMs = 150`, `matchMode = 'contains'`, `showGhostText = true`
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4) States & Derived Data
|
|
109
|
+
|
|
110
|
+
- `open: boolean` — popup visibility.
|
|
111
|
+
- `activeIndex: number` — highlighted suggestion index (−1 when none).
|
|
112
|
+
- `ghost: string` — inline completion remainder (if enabled).
|
|
113
|
+
- `inputValue: string` — mirror of `value` for IME composition handling.
|
|
114
|
+
- `composition: boolean` — true during IME composition (don’t navigate).
|
|
115
|
+
- `listId`, `activeId` — derived for aria.
|
|
116
|
+
|
|
117
|
+
Derived collections:
|
|
118
|
+
|
|
119
|
+
- `filteredSuggestions` — if **local** filtering is used; otherwise pass-through.
|
|
120
|
+
- `virtualWindow` — optional slice when virtualizing.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 5) Accessibility (MUST)
|
|
125
|
+
|
|
126
|
+
Implement WAI-ARIA **Combobox** w/ **Listbox**:
|
|
127
|
+
|
|
128
|
+
- Input element: `role="combobox"`, `aria-autocomplete="list"`, `aria-expanded`, `aria-controls={listId}`, `aria-activedescendant={activeId || undefined}`.
|
|
129
|
+
- Popup list: `role="listbox"`, owns `role="option"` children with `id`.
|
|
130
|
+
- Keyboard:
|
|
131
|
+
- `ArrowDown/ArrowUp`: move active item; open if closed and suggestions exist.
|
|
132
|
+
- `Enter`: select active item (or submit free text if allowed).
|
|
133
|
+
- `Tab` or `ArrowRight` when `ghost` present: accept remainder.
|
|
134
|
+
- `Esc`: close popup, keep focus in input; second `Esc` clears value (optional).
|
|
135
|
+
- Respect **IME composition**: ignore arrows/enter while composing.
|
|
136
|
+
|
|
137
|
+
- Focus trap not required; focus stays in input; popup click should not blur input prematurely.
|
|
138
|
+
- Screen reader announcements: update `aria-activedescendant` and include label + optional description via `aria-describedby`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 6) Behavior Spec
|
|
143
|
+
|
|
144
|
+
### Opening/Closing
|
|
145
|
+
|
|
146
|
+
- Open when input has focus and there are suggestions (or async loading) and either `value.length > 0` or `force open` rules.
|
|
147
|
+
- Close on: blur (after short timeout to allow click), `Esc`, selection, or when input emptied (configurable).
|
|
148
|
+
|
|
149
|
+
### Inline Ghost Text
|
|
150
|
+
|
|
151
|
+
- Compute `ghost` only if `showGhostText` and first suggestion label startsWith query (case-insensitive).
|
|
152
|
+
- Accept remainder on `Tab` or `ArrowRight`.
|
|
153
|
+
|
|
154
|
+
### Selection
|
|
155
|
+
|
|
156
|
+
- Single mode: `onSelect(item)` and optionally fill `value = getLabel(item)`.
|
|
157
|
+
- Multiple mode: append to `selectedItems` if not present; clear input; keep popup open.
|
|
158
|
+
|
|
159
|
+
### Typed suggestions (search vs. link)
|
|
160
|
+
|
|
161
|
+
Provide `getSuggestionType` (and `getUrl` for links) to render a per-row icon and
|
|
162
|
+
change select behavior. A `link` suggestion opens its URL via `openLink` (default
|
|
163
|
+
`window.open(url, '_blank', 'noopener,noreferrer')`) and does **not** change the
|
|
164
|
+
input value or add a chip; a `search` suggestion behaves as normal. Untyped items
|
|
165
|
+
(no `getSuggestionType`) keep the classic icon-less behavior. Ghost-text inline
|
|
166
|
+
completion only considers `search`/untyped items.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Autocomplete<QuickSearchItem>
|
|
170
|
+
value={q}
|
|
171
|
+
onChange={setQ}
|
|
172
|
+
suggestions={items} // [{ id, text, type: 'search' | 'link', url? }]
|
|
173
|
+
getKey={(i) => i.id}
|
|
174
|
+
getLabel={(i) => i.text}
|
|
175
|
+
getSuggestionType={(i) => i.type}
|
|
176
|
+
getUrl={(i) => i.url}
|
|
177
|
+
// openLink={(url) => router.push(url)} // optional: route instead of new tab
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Async
|
|
182
|
+
|
|
183
|
+
- When `async=true`, **do not** filter locally unless asked; instead emit `onChange` with debounced value to trigger upstream fetch. Show spinner when `isLoading`.
|
|
184
|
+
|
|
185
|
+
### Virtualization (optional)
|
|
186
|
+
|
|
187
|
+
- If `suggestions.length > 200`, allow `virtualize=true` in future; for now expose `maxVisibleItems` with overflow scroll. Keep active item scrolled into view.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 7) Rendering Structure (Reference)
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<div className={cx('ac-root', className)} id={id}>
|
|
195
|
+
{multiple && <Chips ... />}
|
|
196
|
+
<div className="ac-inputWrap">
|
|
197
|
+
<input /* combobox roles/attrs */ />
|
|
198
|
+
{showGhostText && <span className="ac-ghost" aria-hidden>{ghost}</span>}
|
|
199
|
+
{isLoading && <Spinner className="ac-spinner" />}
|
|
200
|
+
</div>
|
|
201
|
+
{open && (
|
|
202
|
+
<PortalIf portal={portal}>
|
|
203
|
+
<ul role="listbox" id={listId} className={listClassName}>
|
|
204
|
+
{visibleItems.map((it, i) => (
|
|
205
|
+
<li role="option"
|
|
206
|
+
id={optionId(i)}
|
|
207
|
+
aria-selected={i===activeIndex}
|
|
208
|
+
className={cx(itemClassName, i===activeIndex && activeItemClassName)}
|
|
209
|
+
onMouseEnter={() => setActiveIndex(i)}
|
|
210
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
211
|
+
onClick={() => select(it)}>
|
|
212
|
+
{renderSuggestion ? renderSuggestion(it, { active: i===activeIndex, query: value }) : (
|
|
213
|
+
<DefaultRow label={getLabel(it)} query={value} />
|
|
214
|
+
)}
|
|
215
|
+
</li>
|
|
216
|
+
))}
|
|
217
|
+
</ul>
|
|
218
|
+
</PortalIf>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 8) Keyboard Map
|
|
226
|
+
|
|
227
|
+
- **ArrowDown/Up**: cycle active; wrap disabled by default.
|
|
228
|
+
- **Enter**: select active; if no active and `allowFreeText`, confirm free text.
|
|
229
|
+
- **Tab/→**: accept ghost remainder (if any).
|
|
230
|
+
- **Esc**: close popup; keep input value.
|
|
231
|
+
- **Backspace** (multiple): remove last chip when input empty.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 9) Styles (agnostic contract)
|
|
236
|
+
|
|
237
|
+
Provide minimal class hooks and CSS vars; leave theme to DS:
|
|
238
|
+
|
|
239
|
+
- `.ac-root`, `.ac-inputWrap`, `.ac-ghost`, `.ac-spinner`, `.ac-list`, `.ac-item`, `.ac-item--active`, `.ac-chip`.
|
|
240
|
+
- CSS vars: `--ac-radius`, `--ac-shadow`, `--ac-maxHeight`, `--ac-gap`.
|
|
241
|
+
|
|
242
|
+
Ghost text is positioned as a twin overlay behind the caret (use a stacked container with the real input on top, or pseudo-element when using monospace; choose the twin overlay approach for variable fonts).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 10) Performance Notes
|
|
247
|
+
|
|
248
|
+
- Debounce input changes (default 150ms). Cancel on unmount.
|
|
249
|
+
- Memoize derived labels and highlight splits.
|
|
250
|
+
- Keep scrolling of active item cheap (use `scrollIntoView({ block: 'nearest' })`).
|
|
251
|
+
- Avoid reflow on every key by not measuring DOM repeatedly.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 11) Testing Checklist
|
|
256
|
+
|
|
257
|
+
**Unit/Interaction**
|
|
258
|
+
|
|
259
|
+
- Open/close rules; blur timeout click-through works.
|
|
260
|
+
- Keyboard nav including IME composition guard.
|
|
261
|
+
- Ghost acceptance on `Tab/→`.
|
|
262
|
+
- `onSelect` called with correct item.
|
|
263
|
+
- Multiple mode: chip add/remove; backspace behavior.
|
|
264
|
+
- Async: spinner visibility; no local filter unless configured.
|
|
265
|
+
- A11y: roles, aria-\* updates, `aria-activedescendant` correctness.
|
|
266
|
+
|
|
267
|
+
**Visual/Stories**
|
|
268
|
+
|
|
269
|
+
- Default, Ghost Text, Async Loading, Multiple Tags, Custom Renderer, Accessibility story.
|
|
270
|
+
|
|
271
|
+
**Performance**
|
|
272
|
+
|
|
273
|
+
- 500-suggestion interaction within 16ms budget for nav.
|
|
274
|
+
|
|
275
|
+
Run via:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
cd packages/ui
|
|
279
|
+
pnpm check:component inputs Autocomplete
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 12) File & Story Structure
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
packages/ui/src/inputs/autocomplete/
|
|
288
|
+
Autocomplete.tsx
|
|
289
|
+
Autocomplete.types.ts
|
|
290
|
+
Autocomplete.scss (or .css / Tailwind variant)
|
|
291
|
+
index.ts
|
|
292
|
+
__tests__/Autocomplete.stories.tsx
|
|
293
|
+
__tests__/Autocomplete.interactions.test.tsx
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Storybook stories to include:
|
|
297
|
+
|
|
298
|
+
- `Inputs/Autocomplete/Default`
|
|
299
|
+
- `Inputs/Autocomplete/WithGhostText`
|
|
300
|
+
- `Inputs/Autocomplete/AsyncLoading`
|
|
301
|
+
- `Inputs/Autocomplete/MultipleTags`
|
|
302
|
+
- `Inputs/Autocomplete/CustomRenderer`
|
|
303
|
+
- `Inputs/Autocomplete/Accessibility`
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 13) Pseudocode (Core Hooks)
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
if (!showGhostText) return setGhost('');
|
|
312
|
+
const first = suggestions[0];
|
|
313
|
+
const label = first ? getLabel(first) : '';
|
|
314
|
+
const q = value ?? '';
|
|
315
|
+
if (!q || !label) return setGhost('');
|
|
316
|
+
if (label.toLowerCase().startsWith(q.toLowerCase())) setGhost(label.slice(q.length));
|
|
317
|
+
else setGhost('');
|
|
318
|
+
}, [value, suggestions, showGhostText]);
|
|
319
|
+
|
|
320
|
+
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
321
|
+
if (composition) return; // IME guard
|
|
322
|
+
switch (e.key) {
|
|
323
|
+
case 'ArrowDown':
|
|
324
|
+
openIfAny();
|
|
325
|
+
move(+1);
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
break;
|
|
328
|
+
case 'ArrowUp':
|
|
329
|
+
move(-1);
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
break;
|
|
332
|
+
case 'Enter':
|
|
333
|
+
selectActiveOrFreeText();
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
break;
|
|
336
|
+
case 'Tab':
|
|
337
|
+
case 'ArrowRight':
|
|
338
|
+
if (ghost) {
|
|
339
|
+
acceptGhost();
|
|
340
|
+
e.preventDefault();
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case 'Escape':
|
|
344
|
+
close();
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 14) Definition of Done (DOD)
|
|
353
|
+
|
|
354
|
+
- API implemented with defaults; zero ESLint/TS errors.
|
|
355
|
+
- Full ARIA combobox compliance; keyboard-only usable.
|
|
356
|
+
- Stories + interaction tests passing.
|
|
357
|
+
- No memory leaks; event listeners cleaned on unmount.
|
|
358
|
+
- Handles 500 suggestions smoothly; optional virtualization path documented.
|
|
359
|
+
- Docs updated with examples for search, filter, and command palette scenarios.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 15) Testing
|
|
364
|
+
|
|
365
|
+
### Test IDs Overview
|
|
366
|
+
|
|
367
|
+
This section describes all `data-testid` attributes available in the Autocomplete component for testing purposes.
|
|
368
|
+
|
|
369
|
+
### Container Elements
|
|
370
|
+
|
|
371
|
+
#### `autocomplete-container`
|
|
372
|
+
- **Element:** Root container Box
|
|
373
|
+
- **Location:** Main wrapper element containing the entire autocomplete
|
|
374
|
+
- **Usage:** Query the root container to verify component is rendered
|
|
375
|
+
```typescript
|
|
376
|
+
const container = await canvas.findByTestId('autocomplete-container');
|
|
377
|
+
expect(container).toBeInTheDocument();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### `selected-items-container`
|
|
381
|
+
- **Element:** Container for selected item chips (multiple mode only)
|
|
382
|
+
- **Location:** ChipContainer Box that wraps all selected chips
|
|
383
|
+
- **Conditional:** Only rendered when `multiple={true}` and `selectedItems.length > 0`
|
|
384
|
+
- **Usage:** Query container to verify chips are rendered
|
|
385
|
+
```typescript
|
|
386
|
+
const chipContainer = await canvas.findByTestId('selected-items-container');
|
|
387
|
+
expect(chipContainer).toBeInTheDocument();
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Chip Elements (Multiple Mode)
|
|
391
|
+
|
|
392
|
+
#### `selected-chip-${index}`
|
|
393
|
+
- **Element:** Individual selected item chip
|
|
394
|
+
- **Location:** Each Chip component in the selected items container
|
|
395
|
+
- **Conditional:** Only in multiple mode with selected items
|
|
396
|
+
- **Index:** Zero-based index of selected item
|
|
397
|
+
- **Usage:** Query specific chips or all chips
|
|
398
|
+
```typescript
|
|
399
|
+
// Get all chips
|
|
400
|
+
const chips = await canvas.findAllByTestId(/selected-chip-/);
|
|
401
|
+
expect(chips).toHaveLength(2);
|
|
402
|
+
|
|
403
|
+
// Get specific chip
|
|
404
|
+
const firstChip = await canvas.findByTestId('selected-chip-0');
|
|
405
|
+
expect(firstChip).toHaveTextContent('Option 1');
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Dropdown Elements
|
|
409
|
+
|
|
410
|
+
#### `suggestions-dropdown`
|
|
411
|
+
- **Element:** Dropdown Paper container
|
|
412
|
+
- **Location:** MUI Paper component that wraps the suggestions list
|
|
413
|
+
- **Conditional:** Only rendered when dropdown is `open`
|
|
414
|
+
- **Usage:** Query to verify dropdown is visible
|
|
415
|
+
```typescript
|
|
416
|
+
// Wait for dropdown to appear
|
|
417
|
+
const dropdown = await canvas.findByTestId('suggestions-dropdown');
|
|
418
|
+
expect(dropdown).toBeVisible();
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### `suggestions-list`
|
|
422
|
+
- **Element:** List element containing all suggestions
|
|
423
|
+
- **Location:** MUI List component inside the dropdown Paper
|
|
424
|
+
- **Conditional:** Only rendered when dropdown is `open`
|
|
425
|
+
- **Usage:** Query list to get suggestions container
|
|
426
|
+
```typescript
|
|
427
|
+
const list = await canvas.findByTestId('suggestions-list');
|
|
428
|
+
expect(list).toBeInTheDocument();
|
|
429
|
+
|
|
430
|
+
// Count suggestions
|
|
431
|
+
const items = await within(list).findAllByRole('option');
|
|
432
|
+
expect(items).toHaveLength(expectedCount);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### `suggestion-item-${index}`
|
|
436
|
+
- **Element:** Individual suggestion list item
|
|
437
|
+
- **Location:** Each ListItem in the suggestions list
|
|
438
|
+
- **Index:** Zero-based index of suggestion in filtered list
|
|
439
|
+
- **Usage:** Query specific suggestion or all suggestions
|
|
440
|
+
```typescript
|
|
441
|
+
// Get all suggestions
|
|
442
|
+
const suggestions = await canvas.findAllByTestId(/suggestion-item-/);
|
|
443
|
+
expect(suggestions).toHaveLength(5);
|
|
444
|
+
|
|
445
|
+
// Click specific suggestion
|
|
446
|
+
const firstSuggestion = await canvas.findByTestId('suggestion-item-0');
|
|
447
|
+
await userEvent.click(firstSuggestion);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Test Patterns
|
|
451
|
+
|
|
452
|
+
#### Single Selection Test
|
|
453
|
+
```typescript
|
|
454
|
+
play: async ({ canvasElement, args }) => {
|
|
455
|
+
const canvas = within(canvasElement);
|
|
456
|
+
|
|
457
|
+
// Find input and type
|
|
458
|
+
const input = canvas.getByRole('combobox');
|
|
459
|
+
await userEvent.type(input, 'test');
|
|
460
|
+
|
|
461
|
+
// Wait for dropdown to appear
|
|
462
|
+
const dropdown = await canvas.findByTestId('suggestions-dropdown');
|
|
463
|
+
expect(dropdown).toBeVisible();
|
|
464
|
+
|
|
465
|
+
// Get suggestions
|
|
466
|
+
const suggestions = await canvas.findAllByTestId(/suggestion-item-/);
|
|
467
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
468
|
+
|
|
469
|
+
// Click first suggestion
|
|
470
|
+
await userEvent.click(suggestions[0]);
|
|
471
|
+
|
|
472
|
+
// Verify selection
|
|
473
|
+
expect(args.onSelect).toHaveBeenCalled();
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### Multiple Selection Test
|
|
478
|
+
```typescript
|
|
479
|
+
play: async ({ canvasElement, args }) => {
|
|
480
|
+
const canvas = within(canvasElement);
|
|
481
|
+
|
|
482
|
+
const input = canvas.getByRole('combobox');
|
|
483
|
+
|
|
484
|
+
// Select first item
|
|
485
|
+
await userEvent.type(input, 'option 1');
|
|
486
|
+
const dropdown1 = await canvas.findByTestId('suggestions-dropdown');
|
|
487
|
+
const suggestion1 = await within(dropdown1).findByTestId('suggestion-item-0');
|
|
488
|
+
await userEvent.click(suggestion1);
|
|
489
|
+
|
|
490
|
+
// Wait for chip to appear
|
|
491
|
+
await waitFor(async () => {
|
|
492
|
+
const chip = await canvas.findByTestId('selected-chip-0');
|
|
493
|
+
expect(chip).toBeInTheDocument();
|
|
494
|
+
}, { timeout: 1000 });
|
|
495
|
+
|
|
496
|
+
// Clear input
|
|
497
|
+
await userEvent.clear(input);
|
|
498
|
+
|
|
499
|
+
// Select second item
|
|
500
|
+
await userEvent.type(input, 'option 2');
|
|
501
|
+
const dropdown2 = await canvas.findByTestId('suggestions-dropdown');
|
|
502
|
+
const suggestion2 = await within(dropdown2).findByTestId('suggestion-item-0');
|
|
503
|
+
await userEvent.click(suggestion2);
|
|
504
|
+
|
|
505
|
+
// Verify both chips exist
|
|
506
|
+
await waitFor(async () => {
|
|
507
|
+
const chips = await canvas.findAllByTestId(/selected-chip-/);
|
|
508
|
+
expect(chips).toHaveLength(2);
|
|
509
|
+
}, { timeout: 1000 });
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### Remove Selected Item Test
|
|
514
|
+
```typescript
|
|
515
|
+
play: async ({ canvasElement, args }) => {
|
|
516
|
+
const canvas = within(canvasElement);
|
|
517
|
+
|
|
518
|
+
// Wait for chips to render
|
|
519
|
+
const chips = await canvas.findAllByTestId(/selected-chip-/);
|
|
520
|
+
expect(chips).toHaveLength(2);
|
|
521
|
+
|
|
522
|
+
// Click delete button on first chip
|
|
523
|
+
const deleteButton = within(chips[0]).getByTestId('CancelIcon');
|
|
524
|
+
await userEvent.click(deleteButton);
|
|
525
|
+
|
|
526
|
+
// Verify chip was removed
|
|
527
|
+
await waitFor(async () => {
|
|
528
|
+
const remainingChips = await canvas.findAllByTestId(/selected-chip-/);
|
|
529
|
+
expect(remainingChips).toHaveLength(1);
|
|
530
|
+
}, { timeout: 1000 });
|
|
531
|
+
|
|
532
|
+
// Verify callback was called
|
|
533
|
+
expect(args.onSelectedItemsChange).toHaveBeenCalled();
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
#### Dropdown Visibility Test
|
|
538
|
+
```typescript
|
|
539
|
+
play: async ({ canvasElement }) => {
|
|
540
|
+
const canvas = within(canvasElement);
|
|
541
|
+
|
|
542
|
+
const input = canvas.getByRole('combobox');
|
|
543
|
+
|
|
544
|
+
// Initially, dropdown should not exist
|
|
545
|
+
expect(canvas.queryByTestId('suggestions-dropdown')).not.toBeInTheDocument();
|
|
546
|
+
|
|
547
|
+
// Type to trigger dropdown
|
|
548
|
+
await userEvent.type(input, 'test');
|
|
549
|
+
|
|
550
|
+
// Wait for dropdown to appear
|
|
551
|
+
await waitFor(async () => {
|
|
552
|
+
const dropdown = await canvas.findByTestId('suggestions-dropdown');
|
|
553
|
+
expect(dropdown).toBeVisible();
|
|
554
|
+
}, { timeout: 1000 });
|
|
555
|
+
|
|
556
|
+
// Press Escape to close
|
|
557
|
+
await userEvent.keyboard('{Escape}');
|
|
558
|
+
|
|
559
|
+
// Verify dropdown is gone
|
|
560
|
+
await waitFor(() => {
|
|
561
|
+
expect(canvas.queryByTestId('suggestions-dropdown')).not.toBeInTheDocument();
|
|
562
|
+
}, { timeout: 500 });
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
#### Keyboard Navigation Test
|
|
567
|
+
```typescript
|
|
568
|
+
play: async ({ canvasElement }) => {
|
|
569
|
+
const canvas = within(canvasElement);
|
|
570
|
+
|
|
571
|
+
const input = canvas.getByRole('combobox');
|
|
572
|
+
await userEvent.type(input, 'test');
|
|
573
|
+
|
|
574
|
+
// Wait for suggestions
|
|
575
|
+
await canvas.findByTestId('suggestions-dropdown');
|
|
576
|
+
const suggestions = await canvas.findAllByTestId(/suggestion-item-/);
|
|
577
|
+
|
|
578
|
+
// Press ArrowDown to highlight first item
|
|
579
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
580
|
+
|
|
581
|
+
// Verify first item is highlighted (via aria-selected or background color)
|
|
582
|
+
const firstItem = suggestions[0];
|
|
583
|
+
expect(firstItem).toHaveAttribute('aria-selected', 'true');
|
|
584
|
+
|
|
585
|
+
// Press Enter to select
|
|
586
|
+
await userEvent.keyboard('{Enter}');
|
|
587
|
+
|
|
588
|
+
// Verify dropdown closes
|
|
589
|
+
await waitFor(() => {
|
|
590
|
+
expect(canvas.queryByTestId('suggestions-dropdown')).not.toBeInTheDocument();
|
|
591
|
+
}, { timeout: 500 });
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### Ghost Text Test
|
|
596
|
+
```typescript
|
|
597
|
+
play: async ({ canvasElement }) => {
|
|
598
|
+
const canvas = within(canvasElement);
|
|
599
|
+
|
|
600
|
+
const input = canvas.getByRole('combobox');
|
|
601
|
+
|
|
602
|
+
// Type partial match
|
|
603
|
+
await userEvent.type(input, 'opt');
|
|
604
|
+
|
|
605
|
+
// Wait for suggestions
|
|
606
|
+
await canvas.findByTestId('suggestions-dropdown');
|
|
607
|
+
|
|
608
|
+
// Verify input value (ghost text is visual only, not in input value)
|
|
609
|
+
expect(input).toHaveValue('opt');
|
|
610
|
+
|
|
611
|
+
// Press Tab to accept ghost text
|
|
612
|
+
await userEvent.tab();
|
|
613
|
+
|
|
614
|
+
// Input should now have complete value
|
|
615
|
+
expect(input).toHaveValue('option'); // Assuming ghost suggested "option"
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Props That Affect Test Behavior
|
|
620
|
+
|
|
621
|
+
#### `multiple`
|
|
622
|
+
- **Type:** `boolean`
|
|
623
|
+
- **Default:** `false`
|
|
624
|
+
- **Impact:**
|
|
625
|
+
- When `true`: Shows chips, allows multiple selections
|
|
626
|
+
- When `false`: Single selection only, no chips
|
|
627
|
+
- **TestIds affected:**
|
|
628
|
+
- `selected-items-container` (only when true)
|
|
629
|
+
- `selected-chip-${index}` (only when true)
|
|
630
|
+
|
|
631
|
+
#### `allowFreeText`
|
|
632
|
+
- **Type:** `boolean`
|
|
633
|
+
- **Default:** `true`
|
|
634
|
+
- **Impact:** When true, allows user to submit text not in suggestions
|
|
635
|
+
|
|
636
|
+
#### `async`
|
|
637
|
+
- **Type:** `boolean`
|
|
638
|
+
- **Default:** `false`
|
|
639
|
+
- **Impact:** When true, shows loading spinner while fetching suggestions
|
|
640
|
+
|
|
641
|
+
#### `showGhostText`
|
|
642
|
+
- **Type:** `boolean`
|
|
643
|
+
- **Default:** `true`
|
|
644
|
+
- **Impact:** When true, shows inline suggestion completion
|
|
645
|
+
|
|
646
|
+
#### `maxVisibleItems`
|
|
647
|
+
- **Type:** `number`
|
|
648
|
+
- **Default:** `10`
|
|
649
|
+
- **Impact:** Limits number of suggestions shown in dropdown
|
|
650
|
+
|
|
651
|
+
### Common Test Scenarios
|
|
652
|
+
|
|
653
|
+
#### 1. Basic Rendering
|
|
654
|
+
```typescript
|
|
655
|
+
const container = await canvas.findByTestId('autocomplete-container');
|
|
656
|
+
expect(container).toBeInTheDocument();
|
|
657
|
+
|
|
658
|
+
const input = canvas.getByRole('combobox');
|
|
659
|
+
expect(input).toBeInTheDocument();
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
#### 2. Suggestions Appear on Input
|
|
663
|
+
```typescript
|
|
664
|
+
const input = canvas.getByRole('combobox');
|
|
665
|
+
await userEvent.type(input, 'search term');
|
|
666
|
+
|
|
667
|
+
const dropdown = await canvas.findByTestId('suggestions-dropdown');
|
|
668
|
+
expect(dropdown).toBeVisible();
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### 3. Clicking a Suggestion
|
|
672
|
+
```typescript
|
|
673
|
+
await userEvent.type(input, 'test');
|
|
674
|
+
const suggestions = await canvas.findAllByTestId(/suggestion-item-/);
|
|
675
|
+
await userEvent.click(suggestions[0]);
|
|
676
|
+
|
|
677
|
+
expect(args.onSelect).toHaveBeenCalledWith(expectedItem);
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
#### 4. Multiple Selections
|
|
681
|
+
```typescript
|
|
682
|
+
// Make selections...
|
|
683
|
+
const chips = await canvas.findAllByTestId(/selected-chip-/);
|
|
684
|
+
expect(chips).toHaveLength(expectedCount);
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### 5. Removing a Selection
|
|
688
|
+
```typescript
|
|
689
|
+
const chip = await canvas.findByTestId('selected-chip-0');
|
|
690
|
+
const deleteButton = within(chip).getByTestId('CancelIcon');
|
|
691
|
+
await userEvent.click(deleteButton);
|
|
692
|
+
|
|
693
|
+
await waitFor(() => {
|
|
694
|
+
expect(canvas.queryByTestId('selected-chip-0')).not.toBeInTheDocument();
|
|
695
|
+
});
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Troubleshooting
|
|
699
|
+
|
|
700
|
+
#### Issue: "Unable to find dropdown"
|
|
701
|
+
|
|
702
|
+
**Reason:** Dropdown only renders when `open` state is true
|
|
703
|
+
|
|
704
|
+
**Solution:** Type in input first to trigger dropdown:
|
|
705
|
+
```typescript
|
|
706
|
+
const input = canvas.getByRole('combobox');
|
|
707
|
+
await userEvent.type(input, 'text');
|
|
708
|
+
const dropdown = await canvas.findByTestId('suggestions-dropdown');
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Issue: "Chip testIds not found"
|
|
712
|
+
|
|
713
|
+
**Reason:** Chips only render in `multiple` mode with selected items
|
|
714
|
+
|
|
715
|
+
**Solution:** Verify component has `multiple={true}` and items are selected:
|
|
716
|
+
```typescript
|
|
717
|
+
// Only query chips if multiple mode is enabled
|
|
718
|
+
if (args.multiple) {
|
|
719
|
+
const chips = await canvas.findAllByTestId(/selected-chip-/);
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
#### Issue: "Test timing out when finding suggestions"
|
|
724
|
+
|
|
725
|
+
**Reason:** Debounce delay or async loading
|
|
726
|
+
|
|
727
|
+
**Solution:** Increase timeout and wait for dropdown:
|
|
728
|
+
```typescript
|
|
729
|
+
await waitFor(async () => {
|
|
730
|
+
const suggestions = await canvas.findAllByTestId(/suggestion-item-/);
|
|
731
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
732
|
+
}, { timeout: 2000 });
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
#### Issue: "Suggestions not visible after typing"
|
|
736
|
+
|
|
737
|
+
**Reason:** No matching suggestions or `open` state is false
|
|
738
|
+
|
|
739
|
+
**Solution:** Check filtered results and verify suggestions exist:
|
|
740
|
+
```typescript
|
|
741
|
+
// Check if dropdown exists at all
|
|
742
|
+
const dropdown = canvas.queryByTestId('suggestions-dropdown');
|
|
743
|
+
if (!dropdown) {
|
|
744
|
+
// Dropdown didn't open - check why
|
|
745
|
+
// - Is there a matching suggestion?
|
|
746
|
+
// - Was user closed dropdown previously?
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Best Practices
|
|
751
|
+
|
|
752
|
+
1. **Always wait for dropdown** before querying suggestions
|
|
753
|
+
2. **Use regex patterns** for dynamic testIds: `/selected-chip-/`, `/suggestion-item-/`
|
|
754
|
+
3. **Check conditional rendering** before asserting existence (multiple mode, open state)
|
|
755
|
+
4. **Clear input between selections** in multiple mode tests
|
|
756
|
+
5. **Use waitFor** when expecting DOM changes (chips added/removed, dropdown opens/closes)
|
|
757
|
+
6. **Account for debounce** - default 150ms delay before suggestions update
|
|
758
|
+
|
|
759
|
+
### Related Elements Without TestIds
|
|
760
|
+
|
|
761
|
+
#### Input Field
|
|
762
|
+
- **Query Method:** `canvas.getByRole('combobox')`
|
|
763
|
+
- **ARIA Attributes:**
|
|
764
|
+
- `role="combobox"`
|
|
765
|
+
- `aria-expanded` (true when dropdown open)
|
|
766
|
+
- `aria-controls` (points to listbox ID)
|
|
767
|
+
- `aria-activedescendant` (points to active suggestion)
|
|
768
|
+
|
|
769
|
+
#### List Container
|
|
770
|
+
- **Query Method:** `canvas.getByRole('listbox')`
|
|
771
|
+
- **Location:** The List element inside `suggestions-list` testId
|
|
772
|
+
|
|
773
|
+
#### List Items
|
|
774
|
+
- **Query Method:** `canvas.getAllByRole('option')`
|
|
775
|
+
- **Alternative:** Use `suggestion-item-${index}` testIds
|
|
776
|
+
|
|
777
|
+
#### Ghost Text
|
|
778
|
+
- **No direct query** - visual only, not in DOM as queryable element
|
|
779
|
+
- **Test approach:** Verify behavior by Tab/ArrowRight completion
|
|
780
|
+
|
|
781
|
+
### Change Log
|
|
782
|
+
|
|
783
|
+
- **2025-10-08:** Initial testId implementation
|
|
784
|
+
- Added `autocomplete-container`
|
|
785
|
+
- Added `selected-items-container`
|
|
786
|
+
- Added `selected-chip-${index}`
|
|
787
|
+
- Added `suggestions-dropdown`
|
|
788
|
+
- Added `suggestions-list`
|
|
789
|
+
- Added `suggestion-item-${index}`
|