@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,807 @@
|
|
|
1
|
+
# Calendar / DatePicker — AI Implementation Description
|
|
2
|
+
|
|
3
|
+
A reusable calendar that supports **single date selection** and **date range selection (Airbnb-style)** with hover preview, dual-month layout, and full keyboard/a11y support.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
- Let users pick a **single date** or a **start–end range**.
|
|
10
|
+
- Work as an inline calendar, a popover/popup attached to an input, or an always-visible panel.
|
|
11
|
+
- Be locale-aware and accessible.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Core UX
|
|
16
|
+
|
|
17
|
+
- **Header** with month/year and previous/next controls.
|
|
18
|
+
- **Month grid** (role `grid`) with weekday headers and day cells (role `gridcell`).
|
|
19
|
+
- **Range hover preview**: when a start date is chosen, hovering days previews the end.
|
|
20
|
+
- **Dual month** layout for range mode (configurable).
|
|
21
|
+
- **Disabled/unavailable days** visibly blocked and non-interactive.
|
|
22
|
+
- Optional **footer** with Clear/Apply.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Public API (TypeScript)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
type SelectionMode = 'single' | 'range';
|
|
30
|
+
|
|
31
|
+
export interface DateRange {
|
|
32
|
+
start?: Date | null;
|
|
33
|
+
end?: Date | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CalendarProps {
|
|
37
|
+
// Mode
|
|
38
|
+
selectionMode?: SelectionMode; // default: 'single'
|
|
39
|
+
|
|
40
|
+
// Controlled values
|
|
41
|
+
value?: Date | null; // single mode
|
|
42
|
+
range?: DateRange; // range mode
|
|
43
|
+
onChange?: (value: Date | null) => void; // single
|
|
44
|
+
onRangeChange?: (range: Required<DateRange>) => void; // fired when both ends chosen
|
|
45
|
+
onIntermediateRangeChange?: (partial: DateRange) => void; // while hovering/choosing
|
|
46
|
+
|
|
47
|
+
// Display
|
|
48
|
+
numberOfMonths?: number; // default: 1 in single, 2 in range
|
|
49
|
+
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6; // default: locale
|
|
50
|
+
locale?: string; // e.g. 'en-US'
|
|
51
|
+
showOutsideDays?: boolean; // days from prev/next month
|
|
52
|
+
showWeekNumbers?: boolean;
|
|
53
|
+
|
|
54
|
+
// Constraints
|
|
55
|
+
minDate?: Date;
|
|
56
|
+
maxDate?: Date;
|
|
57
|
+
isDateDisabled?: (date: Date) => boolean; // business logic disable
|
|
58
|
+
minRangeLength?: number; // nights, default 1
|
|
59
|
+
maxRangeLength?: number;
|
|
60
|
+
|
|
61
|
+
// Behavior
|
|
62
|
+
allowSameDayRange?: boolean; // default: true
|
|
63
|
+
closeOnSelect?: boolean; // for popover host; default: single=true, range=when both set
|
|
64
|
+
fixedRange?: boolean; // pick start then auto-calc end by minRangeLength
|
|
65
|
+
autoFocus?: boolean;
|
|
66
|
+
|
|
67
|
+
// Rendering hooks
|
|
68
|
+
renderDay?: (args: {
|
|
69
|
+
date: Date;
|
|
70
|
+
inCurrentMonth: boolean;
|
|
71
|
+
selected: boolean;
|
|
72
|
+
inRange: boolean;
|
|
73
|
+
rangeStart: boolean;
|
|
74
|
+
rangeEnd: boolean;
|
|
75
|
+
disabled: boolean;
|
|
76
|
+
today: boolean;
|
|
77
|
+
}) => React.ReactNode;
|
|
78
|
+
renderHeader?: (ctx: { month: number; year: number }) => React.ReactNode;
|
|
79
|
+
renderFooter?: React.ReactNode;
|
|
80
|
+
|
|
81
|
+
// Styling hooks
|
|
82
|
+
className?: string;
|
|
83
|
+
dayClassName?: (args: {
|
|
84
|
+
date: Date;
|
|
85
|
+
inCurrentMonth: boolean;
|
|
86
|
+
selected: boolean;
|
|
87
|
+
inRange: boolean;
|
|
88
|
+
rangeStart: boolean;
|
|
89
|
+
rangeEnd: boolean;
|
|
90
|
+
disabled: boolean;
|
|
91
|
+
today: boolean;
|
|
92
|
+
hovered?: boolean;
|
|
93
|
+
}) => string;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Notes**
|
|
98
|
+
|
|
99
|
+
- Dates are treated as **date-only**. Internally normalize to a safe noon time (`setHours(12,0,0,0)`) to avoid DST edge cases.
|
|
100
|
+
- For popover use, you can wrap this Calendar in an `InputDate`/`InputDateRange` component; this spec focuses on the calendar panel itself.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Visual & Interaction Details
|
|
105
|
+
|
|
106
|
+
### Single mode
|
|
107
|
+
|
|
108
|
+
- Clicking a day sets `value` and triggers `onChange`.
|
|
109
|
+
- If `closeOnSelect` true, host popover should close.
|
|
110
|
+
|
|
111
|
+
### Range mode (Airbnb-like)
|
|
112
|
+
|
|
113
|
+
- First click sets `range.start`.
|
|
114
|
+
- Hovering highlights tentative `[start → hovered]` if valid.
|
|
115
|
+
- Second click sets `range.end` (order-agnostic: clicking an earlier date than start swaps).
|
|
116
|
+
- Enforce `minRangeLength`/`maxRangeLength`. If invalid, ignore or snap (configurable via your handler).
|
|
117
|
+
- Fire `onIntermediateRangeChange` as user hovers. Fire `onRangeChange` once both ends are chosen.
|
|
118
|
+
|
|
119
|
+
### Navigation
|
|
120
|
+
|
|
121
|
+
- Header buttons move month (or year with modifier keys if desired).
|
|
122
|
+
- Month pagination respects `minDate`/`maxDate`.
|
|
123
|
+
|
|
124
|
+
### States & Styles (suggested class hooks)
|
|
125
|
+
|
|
126
|
+
- `.cal-root`, `.cal-header`, `.cal-nav`, `.cal-grid`, `.cal-weekdays`, `.cal-row`, `.cal-cell`
|
|
127
|
+
- Day modifiers: `.is-today`, `.is-selected`, `.is-inRange`, `.is-rangeStart`, `.is-rangeEnd`, `.is-disabled`, `.is-outside`, `.is-hovered`
|
|
128
|
+
- Range hover overlay uses a soft background; start/end get pills with rounded caps.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Accessibility
|
|
133
|
+
|
|
134
|
+
- Container month: `role="grid"`, labelled by month/year (e.g., `aria-labelledby="cal-2025-09"`).
|
|
135
|
+
- Weekday headers: `role="columnheader"`, short names with `abbr` for SR if needed.
|
|
136
|
+
- Day cells: `role="gridcell"`, `aria-selected` true for selected day(s). For ranges, set `aria-selected="true"` on all in range or use `aria-current="date"` for today.
|
|
137
|
+
- Disabled days: `aria-disabled="true"`.
|
|
138
|
+
- Keyboard support (roving focus within the grid):
|
|
139
|
+
- Arrow keys: move by 1 day (left/right) or 1 week (up/down).
|
|
140
|
+
- Home/End: move to start/end of week.
|
|
141
|
+
- PageUp/PageDown: previous/next month (with Shift for year).
|
|
142
|
+
- Enter/Space: select day (first selects start; second sets end in range mode).
|
|
143
|
+
- Escape: clears hover preview; host can close popover.
|
|
144
|
+
|
|
145
|
+
- Focus should remain inside the grid; header nav is reachable via Tab.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Keyboard Selection Logic (range)
|
|
150
|
+
|
|
151
|
+
1. No selection → Enter picks start.
|
|
152
|
+
2. Start set, no end → Enter on a valid day sets end; if chosen day < start, swap.
|
|
153
|
+
3. If `allowSameDayRange=false`, prevent start==end selection.
|
|
154
|
+
4. Enforce range length constraints before commit.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Edge Cases
|
|
159
|
+
|
|
160
|
+
- Dates outside min/max visually disabled; ensure range hover doesn’t cross blocked dates.
|
|
161
|
+
- Crossing month boundaries in dual-month layout keeps continuous preview.
|
|
162
|
+
- RTL locales: reverse arrow navigation horizontally.
|
|
163
|
+
- Week numbers: compute ISO-8601 if enabled.
|
|
164
|
+
- When `fixedRange` is true, second click is optional: end auto-computed from start and `minRangeLength`.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Testing Scenarios
|
|
169
|
+
|
|
170
|
+
- Single date: click selection, keyboard selection, today highlight.
|
|
171
|
+
- Range: start/end selection, hover preview, crossing months, swapping when end < start.
|
|
172
|
+
- Constraints: min/maxDate, disabled dates, min/max range length.
|
|
173
|
+
- Navigation: header prev/next, PageUp/PageDown, year jump.
|
|
174
|
+
- A11y: roles, `aria-selected`, `aria-disabled`, roving focus, SR labels.
|
|
175
|
+
- Locale: first day of week, month/weekday names in different locales.
|
|
176
|
+
- Performance: rendering 2 months, 12 months stress, no excessive reflows.
|
|
177
|
+
- Edge: DST boundaries, year change (Dec→Jan), RTL mode.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Storybook Stories
|
|
182
|
+
|
|
183
|
+
- `Date/Calendar/Single/Default`
|
|
184
|
+
- `Date/Calendar/Range/Airbnb`
|
|
185
|
+
- `Date/Calendar/WithDisabledDates`
|
|
186
|
+
- `Date/Calendar/MinMaxRangeLength`
|
|
187
|
+
- `Date/Calendar/LocaleFirstDayOfWeek`
|
|
188
|
+
- `Date/Calendar/KeyboardOnly`
|
|
189
|
+
- `Date/Calendar/CustomDayRenderer`
|
|
190
|
+
- `Date/Calendar/WithFooterApplyClear`
|
|
191
|
+
- `Date/Calendar/WeekNumbers`
|
|
192
|
+
- `Date/Calendar/RTL`
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Implementation Notes
|
|
197
|
+
|
|
198
|
+
- Generate month matrix once per visible month; memoize by `{year, month, locale, firstDayOfWeek}`.
|
|
199
|
+
- Normalize each date to noon local to avoid DST off-by-one.
|
|
200
|
+
- Keep internal hover state for preview (`hoveredDate`).
|
|
201
|
+
- Provide a small date utility module (startOfDay, isSameDay, isBetween, clampToBounds).
|
|
202
|
+
- For dual months, render two synchronized grids; next/prev move both together.
|
|
203
|
+
- Avoid external heavy date libraries if possible; if used, tree-shake.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## File Structure (suggestion)
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
packages/ui/src/date/calendar/
|
|
211
|
+
Calendar.tsx
|
|
212
|
+
hooks/useMonthMatrix.ts
|
|
213
|
+
utils/date.ts
|
|
214
|
+
Calendar.scss (or Tailwind)
|
|
215
|
+
index.ts
|
|
216
|
+
__tests__/Calendar.stories.tsx
|
|
217
|
+
__tests__/Calendar.interactions.test.tsx
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Definition of Done
|
|
223
|
+
|
|
224
|
+
- Zero ESLint/TS errors.
|
|
225
|
+
- Keyboard and screen reader usable.
|
|
226
|
+
- All scenarios above covered by stories and interaction tests.
|
|
227
|
+
- Accurate range hover and selection across months and constraints.
|
|
228
|
+
- Locale and first-day-of-week honored.
|
|
229
|
+
- No memory leaks; minimal re-renders.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Testing
|
|
234
|
+
|
|
235
|
+
### Test IDs Overview
|
|
236
|
+
All `data-testid` attributes available in the Calendar component for testing purposes.
|
|
237
|
+
|
|
238
|
+
### Container Elements
|
|
239
|
+
|
|
240
|
+
#### `calendar-container`
|
|
241
|
+
- **Element:** Main Calendar container (Paper component)
|
|
242
|
+
- **Location:** Root Paper element that wraps the entire calendar
|
|
243
|
+
- **Usage:** Query the main container to verify Calendar is rendered
|
|
244
|
+
```typescript
|
|
245
|
+
const container = await canvas.findByTestId('calendar-container');
|
|
246
|
+
expect(container).toBeInTheDocument();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Header Elements
|
|
250
|
+
|
|
251
|
+
#### `calendar-header`
|
|
252
|
+
- **Element:** Calendar header containing month name and navigation buttons
|
|
253
|
+
- **Location:** Box element containing month/year display and navigation buttons
|
|
254
|
+
- **Usage:** Query the header to verify it's rendered and contains navigation
|
|
255
|
+
```typescript
|
|
256
|
+
const header = await canvas.findByTestId('calendar-header');
|
|
257
|
+
expect(header).toBeInTheDocument();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `calendar-prev-month`
|
|
261
|
+
- **Element:** Previous month navigation button
|
|
262
|
+
- **Location:** IconButton in the header (only visible in the first month of multi-month view)
|
|
263
|
+
- **Usage:** Click to navigate to the previous month
|
|
264
|
+
```typescript
|
|
265
|
+
const prevButton = await canvas.findByTestId('calendar-prev-month');
|
|
266
|
+
await userEvent.click(prevButton);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `calendar-next-month`
|
|
270
|
+
- **Element:** Next month navigation button
|
|
271
|
+
- **Location:** IconButton in the header (only visible in the last month of multi-month view)
|
|
272
|
+
- **Usage:** Click to navigate to the next month
|
|
273
|
+
```typescript
|
|
274
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
275
|
+
await userEvent.click(nextButton);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Calendar Grid Elements
|
|
279
|
+
|
|
280
|
+
#### `calendar-grid`
|
|
281
|
+
- **Element:** Calendar grid container
|
|
282
|
+
- **Location:** Box element with role="grid" that contains all date cells
|
|
283
|
+
- **Usage:** Query the grid to verify calendar dates are rendered
|
|
284
|
+
```typescript
|
|
285
|
+
const grid = await canvas.findByTestId('calendar-grid');
|
|
286
|
+
expect(grid).toBeInTheDocument();
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### `calendar-weekday-${index}`
|
|
290
|
+
- **Element:** Weekday header (0-6 for Sun-Sat or Mon-Sun depending on `firstDayOfWeek`)
|
|
291
|
+
- **Location:** Box elements in the weekday headers row with role="columnheader"
|
|
292
|
+
- **Usage:** Query weekday headers to verify they're displayed correctly
|
|
293
|
+
```typescript
|
|
294
|
+
// Get all weekday headers
|
|
295
|
+
const weekdays = [];
|
|
296
|
+
for (let i = 0; i < 7; i++) {
|
|
297
|
+
const weekday = await canvas.findByTestId(`calendar-weekday-${i}`);
|
|
298
|
+
weekdays.push(weekday);
|
|
299
|
+
}
|
|
300
|
+
expect(weekdays).toHaveLength(7);
|
|
301
|
+
|
|
302
|
+
// Get specific weekday (e.g., Sunday = 0, Monday = 1)
|
|
303
|
+
const sunday = await canvas.findByTestId('calendar-weekday-0');
|
|
304
|
+
expect(sunday).toBeInTheDocument();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Date Cell Elements
|
|
308
|
+
|
|
309
|
+
#### `calendar-date-${date}`
|
|
310
|
+
- **Element:** Individual date cell button
|
|
311
|
+
- **Location:** Box element with component="button" for each date in the calendar
|
|
312
|
+
- **Usage:** Query and interact with specific date cells
|
|
313
|
+
```typescript
|
|
314
|
+
// Select a specific date (e.g., 15th)
|
|
315
|
+
const date15 = await canvas.findByTestId('calendar-date-15');
|
|
316
|
+
await userEvent.click(date15);
|
|
317
|
+
|
|
318
|
+
// Get all date 1 cells (appears once per visible month)
|
|
319
|
+
const allFirstDates = await canvas.findAllByTestId('calendar-date-1');
|
|
320
|
+
expect(allFirstDates.length).toBeGreaterThan(0);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Note:** The `${date}` portion is the day of the month (1-31). Multiple cells may have the same testId if multiple months are visible or if showing outside days.
|
|
324
|
+
|
|
325
|
+
#### `calendar-selected-date`
|
|
326
|
+
- **Element:** Hidden indicator span inside a selected date cell
|
|
327
|
+
- **Location:** Span element with display:none inside the date button that is selected
|
|
328
|
+
- **Usage:** Query to verify a date is selected
|
|
329
|
+
```typescript
|
|
330
|
+
// Find the selected date cell
|
|
331
|
+
const selectedDate = await canvas.findByTestId('calendar-selected-date');
|
|
332
|
+
expect(selectedDate).toBeInTheDocument();
|
|
333
|
+
|
|
334
|
+
// Verify it's inside the correct date cell
|
|
335
|
+
const parentCell = selectedDate.closest('[role="gridcell"]');
|
|
336
|
+
expect(parentCell).toHaveAttribute('aria-selected', 'true');
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### `calendar-today`
|
|
340
|
+
- **Element:** Hidden indicator span inside today's date cell
|
|
341
|
+
- **Location:** Span element with display:none inside the date button representing today
|
|
342
|
+
- **Usage:** Query to verify today's date is marked
|
|
343
|
+
```typescript
|
|
344
|
+
// Find today's date cell
|
|
345
|
+
const today = await canvas.findByTestId('calendar-today');
|
|
346
|
+
expect(today).toBeInTheDocument();
|
|
347
|
+
|
|
348
|
+
// Verify it's inside the correct date cell
|
|
349
|
+
const parentCell = today.closest('[role="gridcell"]');
|
|
350
|
+
expect(parentCell).toHaveAttribute('aria-current', 'date');
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Test Patterns
|
|
354
|
+
|
|
355
|
+
#### Wait for Calendar to Render
|
|
356
|
+
```typescript
|
|
357
|
+
import { waitFor, within } from 'storybook/test';
|
|
358
|
+
|
|
359
|
+
// Wait for container
|
|
360
|
+
const container = await canvas.findByTestId('calendar-container');
|
|
361
|
+
expect(container).toBeInTheDocument();
|
|
362
|
+
|
|
363
|
+
// Wait for grid to be populated
|
|
364
|
+
const grid = await canvas.findByTestId('calendar-grid');
|
|
365
|
+
const cells = within(grid).getAllByRole('gridcell');
|
|
366
|
+
expect(cells.length).toBeGreaterThan(0);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### Navigate Between Months
|
|
370
|
+
```typescript
|
|
371
|
+
// Navigate to next month
|
|
372
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
373
|
+
await userEvent.click(nextButton);
|
|
374
|
+
|
|
375
|
+
// Wait for new dates to render
|
|
376
|
+
await waitFor(async () => {
|
|
377
|
+
const grid = await canvas.findByTestId('calendar-grid');
|
|
378
|
+
expect(grid).toBeInTheDocument();
|
|
379
|
+
}, { timeout: 3000 });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Select a Date
|
|
383
|
+
```typescript
|
|
384
|
+
// Single date selection
|
|
385
|
+
const date15 = await canvas.findByTestId('calendar-date-15');
|
|
386
|
+
await userEvent.click(date15);
|
|
387
|
+
|
|
388
|
+
// Verify selection
|
|
389
|
+
const selectedIndicator = await canvas.findByTestId('calendar-selected-date');
|
|
390
|
+
expect(selectedIndicator).toBeInTheDocument();
|
|
391
|
+
|
|
392
|
+
// Verify callback was called
|
|
393
|
+
expect(args.onChange).toHaveBeenCalled();
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### Select a Date Range
|
|
397
|
+
```typescript
|
|
398
|
+
// Click start date
|
|
399
|
+
const startDate = await canvas.findByTestId('calendar-date-10');
|
|
400
|
+
await userEvent.click(startDate);
|
|
401
|
+
|
|
402
|
+
// Click end date
|
|
403
|
+
const endDate = await canvas.findByTestId('calendar-date-15');
|
|
404
|
+
await userEvent.click(endDate);
|
|
405
|
+
|
|
406
|
+
// Verify range was set
|
|
407
|
+
expect(args.onRangeChange).toHaveBeenCalledWith({
|
|
408
|
+
start: expect.any(Date),
|
|
409
|
+
end: expect.any(Date),
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### Verify Today's Date is Highlighted
|
|
414
|
+
```typescript
|
|
415
|
+
// Find today's marker
|
|
416
|
+
const todayMarker = await canvas.findByTestId('calendar-today');
|
|
417
|
+
expect(todayMarker).toBeInTheDocument();
|
|
418
|
+
|
|
419
|
+
// Get the parent date cell
|
|
420
|
+
const todayCell = todayMarker.closest('[role="gridcell"]');
|
|
421
|
+
expect(todayCell).toHaveAttribute('aria-current', 'date');
|
|
422
|
+
|
|
423
|
+
// Verify styling (bold text)
|
|
424
|
+
const style = window.getComputedStyle(todayCell);
|
|
425
|
+
expect(style.fontWeight).toBe('bold');
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### Check Weekday Headers
|
|
429
|
+
```typescript
|
|
430
|
+
// Verify all 7 weekdays are present
|
|
431
|
+
for (let i = 0; i < 7; i++) {
|
|
432
|
+
const weekday = await canvas.findByTestId(`calendar-weekday-${i}`);
|
|
433
|
+
expect(weekday).toBeInTheDocument();
|
|
434
|
+
expect(weekday.textContent).toBeTruthy();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Verify weekday order based on firstDayOfWeek
|
|
438
|
+
const firstWeekday = await canvas.findByTestId('calendar-weekday-0');
|
|
439
|
+
// For firstDayOfWeek=0 (Sunday), expect 'S' or 'Su'
|
|
440
|
+
// For firstDayOfWeek=1 (Monday), expect 'M' or 'Mo'
|
|
441
|
+
expect(firstWeekday.textContent).toBeTruthy();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Test Multi-Month Calendar
|
|
445
|
+
```typescript
|
|
446
|
+
// Find all grids (one per month)
|
|
447
|
+
const grids = await canvas.findAllByTestId('calendar-grid');
|
|
448
|
+
expect(grids).toHaveLength(expectedMonthCount);
|
|
449
|
+
|
|
450
|
+
// Verify navigation buttons
|
|
451
|
+
const prevButton = await canvas.findByTestId('calendar-prev-month');
|
|
452
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
453
|
+
expect(prevButton).toBeInTheDocument();
|
|
454
|
+
expect(nextButton).toBeInTheDocument();
|
|
455
|
+
|
|
456
|
+
// Navigate and verify
|
|
457
|
+
await userEvent.click(nextButton);
|
|
458
|
+
await waitFor(() => {
|
|
459
|
+
// Month should have changed
|
|
460
|
+
expect(canvas.getByTestId('calendar-grid')).toBeInTheDocument();
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### Verify Disabled Dates
|
|
465
|
+
```typescript
|
|
466
|
+
// Try to click a disabled date
|
|
467
|
+
const disabledDate = await canvas.findByTestId('calendar-date-5');
|
|
468
|
+
expect(disabledDate).toHaveAttribute('aria-disabled', 'true');
|
|
469
|
+
|
|
470
|
+
// Click should not trigger onChange
|
|
471
|
+
await userEvent.click(disabledDate);
|
|
472
|
+
expect(args.onChange).not.toHaveBeenCalled();
|
|
473
|
+
|
|
474
|
+
// Verify disabled styling
|
|
475
|
+
const style = window.getComputedStyle(disabledDate);
|
|
476
|
+
expect(style.cursor).toBe('not-allowed');
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Props That Affect Test Behavior
|
|
480
|
+
|
|
481
|
+
#### `selectionMode`
|
|
482
|
+
- **Values:** `'single' | 'range'`
|
|
483
|
+
- **Impact:**
|
|
484
|
+
- `single`: Clicking a date selects it and shows `calendar-selected-date` indicator
|
|
485
|
+
- `range`: First click sets start, second click sets end, shows range preview on hover
|
|
486
|
+
|
|
487
|
+
#### `numberOfMonths`
|
|
488
|
+
- **Values:** `number` (1-3)
|
|
489
|
+
- **Impact:** Number of `calendar-grid` elements rendered
|
|
490
|
+
- **Test Tip:** Use `findAllByTestId('calendar-grid')` to count grids
|
|
491
|
+
|
|
492
|
+
#### `firstDayOfWeek`
|
|
493
|
+
- **Values:** `0-6` (0=Sunday, 1=Monday, etc.)
|
|
494
|
+
- **Impact:** Changes order of `calendar-weekday-${index}` elements
|
|
495
|
+
- **Test Tip:** Verify first weekday matches expected day name
|
|
496
|
+
|
|
497
|
+
#### `showOutsideDays`
|
|
498
|
+
- **Values:** `boolean`
|
|
499
|
+
- **Impact:** Whether dates from previous/next months are visible in the grid
|
|
500
|
+
- **Test Tip:** Check if cells with `inCurrentMonth=false` show dates
|
|
501
|
+
|
|
502
|
+
#### `minDate` / `maxDate`
|
|
503
|
+
- **Values:** `Date`
|
|
504
|
+
- **Impact:** Dates outside range have `aria-disabled="true"`
|
|
505
|
+
- **Test Tip:** Verify disabled dates cannot be selected
|
|
506
|
+
|
|
507
|
+
#### `isDateDisabled`
|
|
508
|
+
- **Values:** `(date: Date) => boolean`
|
|
509
|
+
- **Impact:** Custom disabled dates have `aria-disabled="true"`
|
|
510
|
+
- **Test Tip:** Verify callback determines which dates are disabled
|
|
511
|
+
|
|
512
|
+
#### `autoFocus`
|
|
513
|
+
- **Values:** `boolean`
|
|
514
|
+
- **Impact:** Calendar grid receives focus on mount
|
|
515
|
+
- **Test Tip:** Verify `calendar-container` has focus after render
|
|
516
|
+
|
|
517
|
+
### Keyboard Navigation Testing
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
// Test arrow key navigation
|
|
521
|
+
const container = await canvas.findByTestId('calendar-container');
|
|
522
|
+
container.focus();
|
|
523
|
+
|
|
524
|
+
// Navigate with arrow keys
|
|
525
|
+
await userEvent.keyboard('{ArrowRight}'); // Move to next day
|
|
526
|
+
await userEvent.keyboard('{ArrowLeft}'); // Move to previous day
|
|
527
|
+
await userEvent.keyboard('{ArrowDown}'); // Move down one week
|
|
528
|
+
await userEvent.keyboard('{ArrowUp}'); // Move up one week
|
|
529
|
+
|
|
530
|
+
// Navigate months
|
|
531
|
+
await userEvent.keyboard('{PageDown}'); // Next month
|
|
532
|
+
await userEvent.keyboard('{PageUp}'); // Previous month
|
|
533
|
+
|
|
534
|
+
// Select with keyboard
|
|
535
|
+
await userEvent.keyboard('{Enter}'); // Select focused date
|
|
536
|
+
// or
|
|
537
|
+
await userEvent.keyboard(' '); // Select focused date with space
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Common Test Scenarios
|
|
541
|
+
|
|
542
|
+
#### 1. Basic Rendering Test
|
|
543
|
+
```typescript
|
|
544
|
+
play: async ({ canvasElement }) => {
|
|
545
|
+
const canvas = within(canvasElement);
|
|
546
|
+
|
|
547
|
+
// Wait for calendar to render
|
|
548
|
+
const container = await canvas.findByTestId('calendar-container');
|
|
549
|
+
expect(container).toBeInTheDocument();
|
|
550
|
+
|
|
551
|
+
// Verify header exists
|
|
552
|
+
const header = await canvas.findByTestId('calendar-header');
|
|
553
|
+
expect(header).toBeInTheDocument();
|
|
554
|
+
|
|
555
|
+
// Verify grid exists and has cells
|
|
556
|
+
const grid = await canvas.findByTestId('calendar-grid');
|
|
557
|
+
const cells = within(grid).getAllByRole('gridcell');
|
|
558
|
+
expect(cells.length).toBeGreaterThan(0);
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### 2. Single Date Selection Test
|
|
563
|
+
```typescript
|
|
564
|
+
play: async ({ canvasElement, args }) => {
|
|
565
|
+
const canvas = within(canvasElement);
|
|
566
|
+
|
|
567
|
+
// Wait for calendar
|
|
568
|
+
await canvas.findByTestId('calendar-container');
|
|
569
|
+
|
|
570
|
+
// Click a date
|
|
571
|
+
const date15 = await canvas.findByTestId('calendar-date-15');
|
|
572
|
+
await userEvent.click(date15);
|
|
573
|
+
|
|
574
|
+
// Verify selection indicator
|
|
575
|
+
const selected = await canvas.findByTestId('calendar-selected-date');
|
|
576
|
+
expect(selected).toBeInTheDocument();
|
|
577
|
+
|
|
578
|
+
// Verify callback
|
|
579
|
+
expect(args.onChange).toHaveBeenCalled();
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
#### 3. Range Selection Test
|
|
584
|
+
```typescript
|
|
585
|
+
play: async ({ canvasElement, args }) => {
|
|
586
|
+
const canvas = within(canvasElement);
|
|
587
|
+
|
|
588
|
+
// Wait for calendar
|
|
589
|
+
await canvas.findByTestId('calendar-container');
|
|
590
|
+
|
|
591
|
+
// Select range start
|
|
592
|
+
const startDate = await canvas.findByTestId('calendar-date-10');
|
|
593
|
+
await userEvent.click(startDate);
|
|
594
|
+
|
|
595
|
+
// Select range end
|
|
596
|
+
const endDate = await canvas.findByTestId('calendar-date-20');
|
|
597
|
+
await userEvent.click(endDate);
|
|
598
|
+
|
|
599
|
+
// Verify range callback
|
|
600
|
+
await waitFor(() => {
|
|
601
|
+
expect(args.onRangeChange).toHaveBeenCalledWith({
|
|
602
|
+
start: expect.any(Date),
|
|
603
|
+
end: expect.any(Date),
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### 4. Month Navigation Test
|
|
610
|
+
```typescript
|
|
611
|
+
play: async ({ canvasElement }) => {
|
|
612
|
+
const canvas = within(canvasElement);
|
|
613
|
+
|
|
614
|
+
// Wait for calendar
|
|
615
|
+
await canvas.findByTestId('calendar-container');
|
|
616
|
+
|
|
617
|
+
// Get initial month text
|
|
618
|
+
const header = await canvas.findByTestId('calendar-header');
|
|
619
|
+
const initialMonth = within(header).getByRole('heading').textContent;
|
|
620
|
+
|
|
621
|
+
// Navigate to next month
|
|
622
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
623
|
+
await userEvent.click(nextButton);
|
|
624
|
+
|
|
625
|
+
// Verify month changed
|
|
626
|
+
await waitFor(() => {
|
|
627
|
+
const newMonth = within(header).getByRole('heading').textContent;
|
|
628
|
+
expect(newMonth).not.toBe(initialMonth);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
#### 5. Today Indicator Test
|
|
634
|
+
```typescript
|
|
635
|
+
play: async ({ canvasElement }) => {
|
|
636
|
+
const canvas = within(canvasElement);
|
|
637
|
+
|
|
638
|
+
// Wait for calendar
|
|
639
|
+
await canvas.findByTestId('calendar-container');
|
|
640
|
+
|
|
641
|
+
// Find today's marker
|
|
642
|
+
const today = await canvas.findByTestId('calendar-today');
|
|
643
|
+
expect(today).toBeInTheDocument();
|
|
644
|
+
|
|
645
|
+
// Verify parent cell is marked as current date
|
|
646
|
+
const todayCell = today.closest('[role="gridcell"]');
|
|
647
|
+
expect(todayCell).toHaveAttribute('aria-current', 'date');
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
#### 6. Disabled Date Test
|
|
652
|
+
```typescript
|
|
653
|
+
play: async ({ canvasElement, args }) => {
|
|
654
|
+
const canvas = within(canvasElement);
|
|
655
|
+
|
|
656
|
+
// Wait for calendar
|
|
657
|
+
await canvas.findByTestId('calendar-container');
|
|
658
|
+
|
|
659
|
+
// Find a disabled date (depends on minDate/maxDate/isDateDisabled)
|
|
660
|
+
const grid = await canvas.findByTestId('calendar-grid');
|
|
661
|
+
const disabledCell = within(grid).getAllByRole('gridcell')
|
|
662
|
+
.find(cell => cell.getAttribute('aria-disabled') === 'true');
|
|
663
|
+
|
|
664
|
+
if (disabledCell) {
|
|
665
|
+
// Try to click disabled date
|
|
666
|
+
await userEvent.click(disabledCell);
|
|
667
|
+
|
|
668
|
+
// Verify onChange was not called
|
|
669
|
+
expect(args.onChange).not.toHaveBeenCalled();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
#### 7. Weekday Headers Test
|
|
675
|
+
```typescript
|
|
676
|
+
play: async ({ canvasElement }) => {
|
|
677
|
+
const canvas = within(canvasElement);
|
|
678
|
+
|
|
679
|
+
// Wait for calendar
|
|
680
|
+
await canvas.findByTestId('calendar-container');
|
|
681
|
+
|
|
682
|
+
// Verify all weekday headers
|
|
683
|
+
const weekdays = [];
|
|
684
|
+
for (let i = 0; i < 7; i++) {
|
|
685
|
+
const weekday = await canvas.findByTestId(`calendar-weekday-${i}`);
|
|
686
|
+
expect(weekday).toBeInTheDocument();
|
|
687
|
+
weekdays.push(weekday.textContent);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Verify we have 7 unique weekday names
|
|
691
|
+
expect(weekdays).toHaveLength(7);
|
|
692
|
+
expect(new Set(weekdays).size).toBe(7);
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Troubleshooting
|
|
697
|
+
|
|
698
|
+
#### Issue: "Unable to find element with testId calendar-date-X"
|
|
699
|
+
|
|
700
|
+
**Possible Causes:**
|
|
701
|
+
1. Date X is not in the current month view
|
|
702
|
+
2. Date X is disabled and hidden by `showOutsideDays=false`
|
|
703
|
+
3. Calendar hasn't finished rendering
|
|
704
|
+
|
|
705
|
+
**Solution:**
|
|
706
|
+
```typescript
|
|
707
|
+
// Wait for calendar to render
|
|
708
|
+
await waitFor(async () => {
|
|
709
|
+
const container = await canvas.findByTestId('calendar-container');
|
|
710
|
+
expect(container).toBeInTheDocument();
|
|
711
|
+
}, { timeout: 3000 });
|
|
712
|
+
|
|
713
|
+
// Navigate to the correct month if needed
|
|
714
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
715
|
+
await userEvent.click(nextButton);
|
|
716
|
+
|
|
717
|
+
// Try finding the date again
|
|
718
|
+
const date = await canvas.findByTestId('calendar-date-15');
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
#### Issue: "Multiple elements with testId calendar-date-X"
|
|
722
|
+
|
|
723
|
+
**Cause:** Multiple months are visible or outside days are shown, causing duplicate date numbers.
|
|
724
|
+
|
|
725
|
+
**Solution:**
|
|
726
|
+
```typescript
|
|
727
|
+
// Get all instances of the date
|
|
728
|
+
const allDate15 = await canvas.findAllByTestId('calendar-date-15');
|
|
729
|
+
|
|
730
|
+
// Filter to get the one in the current month
|
|
731
|
+
const date15InCurrentMonth = allDate15.find(cell => {
|
|
732
|
+
// Check if cell is in current month (not grayed out)
|
|
733
|
+
const style = window.getComputedStyle(cell);
|
|
734
|
+
return style.opacity === '1';
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
await userEvent.click(date15InCurrentMonth);
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
#### Issue: "Cannot find calendar-selected-date"
|
|
741
|
+
|
|
742
|
+
**Cause:** No date is currently selected.
|
|
743
|
+
|
|
744
|
+
**Solution:**
|
|
745
|
+
```typescript
|
|
746
|
+
// First select a date
|
|
747
|
+
const date15 = await canvas.findByTestId('calendar-date-15');
|
|
748
|
+
await userEvent.click(date15);
|
|
749
|
+
|
|
750
|
+
// Then wait for selection indicator
|
|
751
|
+
await waitFor(async () => {
|
|
752
|
+
const selected = await canvas.findByTestId('calendar-selected-date');
|
|
753
|
+
expect(selected).toBeInTheDocument();
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
#### Issue: "Navigation buttons not found"
|
|
758
|
+
|
|
759
|
+
**Cause:** In multi-month view, prev button only shows on first month, next button only shows on last month.
|
|
760
|
+
|
|
761
|
+
**Solution:**
|
|
762
|
+
```typescript
|
|
763
|
+
// For multi-month calendars, buttons are conditional
|
|
764
|
+
try {
|
|
765
|
+
const prevButton = await canvas.findByTestId('calendar-prev-month');
|
|
766
|
+
// First month is visible
|
|
767
|
+
} catch {
|
|
768
|
+
// First month is not the leftmost visible month
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
try {
|
|
772
|
+
const nextButton = await canvas.findByTestId('calendar-next-month');
|
|
773
|
+
// Last month is visible
|
|
774
|
+
} catch {
|
|
775
|
+
// Last month is not the rightmost visible month
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### Best Practices
|
|
780
|
+
|
|
781
|
+
1. **Always use async/await** with testId queries in Storybook tests
|
|
782
|
+
2. **Use `waitFor`** when testing user interactions that trigger state changes
|
|
783
|
+
3. **Scope queries** with `within()` when looking inside specific containers like grids
|
|
784
|
+
4. **Handle multiple months** by using `findAllByTestId` for elements that appear per-month
|
|
785
|
+
5. **Test keyboard navigation** to ensure accessibility compliance
|
|
786
|
+
6. **Verify ARIA attributes** (`aria-selected`, `aria-disabled`, `aria-current`) alongside testIds
|
|
787
|
+
7. **Account for outside days** when using `showOutsideDays=true` (duplicate date numbers)
|
|
788
|
+
|
|
789
|
+
### Related Components
|
|
790
|
+
|
|
791
|
+
- **Box (Paper):** Main calendar container (`calendar-container`)
|
|
792
|
+
- **IconButton:** Navigation buttons (`calendar-prev-month`, `calendar-next-month`)
|
|
793
|
+
- **Typography:** Month/year header (inside `calendar-header`)
|
|
794
|
+
- **Grid cells:** Date buttons (`calendar-date-${date}`)
|
|
795
|
+
|
|
796
|
+
### Change Log
|
|
797
|
+
|
|
798
|
+
- **2025-10-08:** Initial testId implementation
|
|
799
|
+
- Added `calendar-container`
|
|
800
|
+
- Added `calendar-header`
|
|
801
|
+
- Added `calendar-prev-month`
|
|
802
|
+
- Added `calendar-next-month`
|
|
803
|
+
- Added `calendar-grid`
|
|
804
|
+
- Added `calendar-weekday-${index}`
|
|
805
|
+
- Added `calendar-date-${date}`
|
|
806
|
+
- Added `calendar-selected-date`
|
|
807
|
+
- Added `calendar-today`
|