@12-apps/ui 1.0.0 → 1.3.1
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 +14 -0
- package/package.json +42 -27
- package/src/__tests__/data-grid.test.tsx +125 -0
- package/src/__tests__/package-wiring.test.ts +12 -8
- package/src/__tests__/social-login-button.test.tsx +5 -5
- package/src/__tests__/viewport.test.ts +50 -0
- package/src/charts/index.ts +8 -0
- package/src/components/data-display/Alert/Alert.md +1 -1
- package/src/components/data-display/Alert/Alert.stories.tsx +5 -7
- package/src/components/data-display/Alert/Alert.styles.tsx +255 -0
- package/src/components/data-display/Alert/Alert.test.stories.tsx +11 -13
- package/src/components/data-display/Alert/Alert.tsx +135 -328
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +3 -1
- package/src/components/data-display/AlertDialog/AlertDialog.tsx +191 -78
- package/src/components/data-display/AlertDialog/AlertDialog.types.ts +8 -1
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +5 -5
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +44 -35
- package/src/components/data-display/Avatar/Avatar.stories.tsx +4 -1
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +5 -3
- package/src/components/data-display/Avatar/Avatar.tsx +154 -450
- package/src/components/data-display/Avatar/Avatar.view.tsx +396 -0
- package/src/components/data-display/Badge/Badge.animations.ts +67 -0
- package/src/components/data-display/Badge/Badge.helpers.ts +74 -0
- package/src/components/data-display/Badge/Badge.stories.tsx +14 -16
- package/src/components/data-display/Badge/Badge.styles.ts +261 -0
- package/src/components/data-display/Badge/Badge.test.stories.tsx +32 -31
- package/src/components/data-display/Badge/Badge.tsx +151 -557
- package/src/components/data-display/Badge/Badge.variants.ts +106 -0
- package/src/components/data-display/Badge/BadgeContent.tsx +107 -0
- package/src/components/data-display/Banner/Banner.stories.tsx +5 -1
- package/src/components/data-display/Banner/Banner.styles.ts +120 -0
- package/src/components/data-display/Banner/Banner.tsx +179 -224
- package/src/components/data-display/Chart/Chart.md +48 -1
- package/src/components/data-display/Chart/Chart.tsx +82 -500
- package/src/components/data-display/Chart/Chart.types.ts +2 -0
- package/src/components/data-display/Chart/ChartComposables.tsx +121 -0
- package/src/components/data-display/Chart/ChartSpec.types.ts +70 -0
- package/src/components/data-display/Chart/SpecChart.stories.tsx +150 -0
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +56 -0
- package/src/components/data-display/Chart/SpecChart.tsx +53 -0
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +60 -0
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +43 -0
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +119 -0
- package/src/components/data-display/Chart/chart-format.ts +26 -0
- package/src/components/data-display/Chart/chart-internals.ts +148 -0
- package/src/components/data-display/Chart/chart-renderers.tsx +338 -0
- package/src/components/data-display/Chart/chart-spec-registry.ts +74 -0
- package/src/components/data-display/Chart/chart-spec.ts +171 -0
- package/src/components/data-display/Chart/index.ts +30 -0
- package/src/components/data-display/Chip/Chip.helpers.tsx +131 -0
- package/src/components/data-display/Chip/Chip.stories.tsx +2 -1
- package/src/components/data-display/Chip/Chip.test.stories.tsx +11 -8
- package/src/components/data-display/Chip/Chip.tsx +31 -94
- package/src/components/data-display/DataGrid/DataGrid.body.tsx +141 -0
- package/src/components/data-display/DataGrid/DataGrid.cells.tsx +238 -0
- package/src/components/data-display/DataGrid/DataGrid.model.ts +315 -0
- package/src/components/data-display/DataGrid/DataGrid.rows.ts +112 -0
- package/src/components/data-display/DataGrid/DataGrid.states.tsx +102 -0
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +6 -12
- package/src/components/data-display/DataGrid/DataGrid.tsx +129 -725
- package/src/components/data-display/DataViews/DataViewsGrid.tsx +187 -0
- package/src/components/data-display/DataViews/DataViewsTableBase.tsx +400 -0
- package/src/components/data-display/DataViews/ManageViewsDialog.tsx +140 -0
- package/src/components/data-display/DataViews/SaveViewModal.tsx +289 -0
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +271 -0
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +134 -0
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +83 -0
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +115 -0
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +53 -0
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +114 -0
- package/src/components/data-display/DataViews/base-card.stories.tsx +136 -0
- package/src/components/data-display/DataViews/base-card.tsx +278 -0
- package/src/components/data-display/DataViews/data-views-filter-panel.tsx +184 -0
- package/src/components/data-display/DataViews/data-views-filter.ts +138 -0
- package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +248 -0
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +400 -0
- package/src/components/data-display/DataViews/data-views-inline-bar.tsx +257 -0
- package/src/components/data-display/DataViews/data-views-layout-context.tsx +176 -0
- package/src/components/data-display/DataViews/data-views-pagination.tsx +31 -0
- package/src/components/data-display/DataViews/data-views-preview.ts +53 -0
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +277 -0
- package/src/components/data-display/DataViews/data-views-table-parts.tsx +150 -0
- package/src/components/data-display/DataViews/data-views-toolbar.tsx +149 -0
- package/src/components/data-display/DataViews/data-views-types.ts +312 -0
- package/src/components/data-display/DataViews/data-views-view-mutations.ts +68 -0
- package/src/components/data-display/DataViews/index.ts +33 -0
- package/src/components/data-display/DataViews/use-data-views-state.ts +349 -0
- package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.hooks.ts +104 -0
- package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +68 -118
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +1 -1
- package/src/components/data-display/DescriptionItem/DescriptionItem.tsx +52 -34
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +6 -6
- package/src/components/data-display/EmptyState/EmptyState.tsx +218 -135
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +2 -2
- package/src/components/data-display/ErrorState/ErrorState.tsx +83 -55
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +3 -1
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +5 -5
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.hooks.ts +99 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +87 -0
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +28 -155
- package/src/components/data-display/LazyImage/LazyImage.hooks.ts +203 -0
- package/src/components/data-display/LazyImage/LazyImage.md +1 -1
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +3 -3
- package/src/components/data-display/LazyImage/LazyImage.tsx +144 -243
- package/src/components/data-display/Lightbox/Lightbox.constants.ts +53 -0
- package/src/components/data-display/Lightbox/Lightbox.helpers.ts +39 -0
- package/src/components/data-display/Lightbox/Lightbox.hooks.ts +259 -0
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +28 -35
- package/src/components/data-display/Lightbox/Lightbox.tsx +98 -658
- package/src/components/data-display/Lightbox/LightboxControls.tsx +152 -0
- package/src/components/data-display/Lightbox/LightboxCurrentMedia.tsx +50 -0
- package/src/components/data-display/Lightbox/LightboxMedia.tsx +76 -0
- package/src/components/data-display/Lightbox/LightboxOverlay.tsx +83 -0
- package/src/components/data-display/Lightbox/LightboxStage.tsx +97 -0
- package/src/components/data-display/Lightbox/LightboxThumbnails.tsx +83 -0
- package/src/components/data-display/Lightbox/useLightbox.ts +80 -0
- package/src/components/data-display/LoadingState/LoadingState.helpers.ts +52 -0
- package/src/components/data-display/LoadingState/LoadingState.tsx +117 -87
- package/src/components/data-display/MapPreview/MapControlBar.tsx +116 -0
- package/src/components/data-display/MapPreview/MapDecorations.tsx +155 -0
- package/src/components/data-display/MapPreview/MapLayers.tsx +70 -0
- package/src/components/data-display/MapPreview/MapMarkers.tsx +84 -0
- package/src/components/data-display/MapPreview/MapOverlays.tsx +68 -0
- package/src/components/data-display/MapPreview/MapPreview.constants.ts +19 -0
- package/src/components/data-display/MapPreview/MapPreview.helpers.ts +63 -0
- package/src/components/data-display/MapPreview/MapPreview.hooks.ts +265 -0
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +14 -10
- package/src/components/data-display/MapPreview/MapPreview.tsx +52 -882
- package/src/components/data-display/MapPreview/MapPreview.types.ts +8 -1
- package/src/components/data-display/MapPreview/MapSearchBar.tsx +64 -0
- package/src/components/data-display/MapPreview/MapTiles.tsx +70 -0
- package/src/components/data-display/MapPreview/StaticMapView.tsx +63 -0
- package/src/components/data-display/MapPreview/mapProjection.ts +52 -0
- package/src/components/data-display/MapPreview/mapSurface.ts +37 -0
- package/src/components/data-display/MapPreview/useMapPreview.ts +62 -0
- package/src/components/data-display/Popover/Popover.test.stories.tsx +13 -19
- package/src/components/data-display/Progress/Progress.helpers.ts +37 -0
- package/src/components/data-display/Progress/Progress.styles.ts +101 -0
- package/src/components/data-display/Progress/Progress.tsx +185 -213
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +1 -1
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +1 -1
- package/src/components/data-display/StatCard/StatCard.md +117 -0
- package/src/components/data-display/StatCard/StatCard.stories.tsx +167 -0
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +240 -0
- package/src/components/data-display/StatCard/StatCard.tsx +236 -0
- package/src/components/data-display/StatCard/StatCard.types.ts +62 -0
- package/src/components/data-display/StatCard/index.ts +7 -0
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +12 -19
- package/src/components/data-display/Stepper/Stepper.tsx +1 -1
- package/src/components/data-display/Table/Table.helpers.ts +31 -0
- package/src/components/data-display/Table/Table.hooks.ts +161 -0
- package/src/components/data-display/Table/Table.stories.tsx +3 -1
- package/src/components/data-display/Table/Table.styles.ts +223 -0
- package/src/components/data-display/Table/Table.test.stories.tsx +4 -4
- package/src/components/data-display/Table/Table.tsx +291 -748
- package/src/components/data-display/Table/TableParts.hooks.tsx +112 -0
- package/src/components/data-display/Table/TableParts.tsx +298 -0
- package/src/components/data-display/Table/TableStates.tsx +21 -0
- package/src/components/data-display/Timeline/Timeline.stories.tsx +10 -12
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +30 -44
- package/src/components/data-display/Timeline/Timeline.tsx +23 -284
- package/src/components/data-display/Timeline/TimelineRow.tsx +346 -0
- package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +38 -311
- package/src/components/data-display/TimingDiagram/TimingDiagram.views.tsx +346 -0
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +4 -1
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +44 -58
- package/src/components/data-display/Tooltip/Tooltip.tsx +67 -62
- package/src/components/data-display/index.ts +1 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +96 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.md +140 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +154 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +146 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +114 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.tsx +46 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +111 -0
- package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +18 -0
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +288 -0
- package/src/components/feedback/ConfirmAction/index.ts +16 -0
- package/src/components/feedback/ConfirmAction/with-confirmation.tsx +112 -0
- package/src/components/feedback/Dialog/Dialog.styles.ts +4 -2
- package/src/components/feedback/Dialog/Dialog.tsx +26 -1
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +66 -0
- package/src/components/feedback/Modal/Modal.md +1 -1
- package/src/components/feedback/Modal/Modal.stories.tsx +2 -2
- package/src/components/feedback/Sonner/Sonner.tsx +104 -288
- package/src/components/feedback/Sonner/Sonner.types.ts +7 -0
- package/src/components/feedback/Sonner/SonnerToast.tsx +216 -0
- package/src/components/feedback/StackedModal/StackedModal.hooks.ts +226 -0
- package/src/components/feedback/StackedModal/StackedModal.parts.tsx +311 -0
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +13 -15
- package/src/components/feedback/StackedModal/StackedModal.styles.ts +193 -0
- package/src/components/feedback/StackedModal/StackedModal.tsx +100 -619
- package/src/components/feedback/StackedModal/StackedModal.types.ts +17 -3
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +104 -0
- package/src/components/feedback/StackedModal/modal-stack.tsx +119 -0
- package/src/components/feedback/Toast/Toast.tsx +92 -81
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.geometry.ts +65 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.hooks.ts +185 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +2 -2
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +156 -414
- package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +255 -0
- package/src/components/feedback/index.ts +1 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.helpers.ts +58 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.hooks.ts +93 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.mapper.ts +57 -0
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +4 -2
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +20 -16
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.tsx +26 -829
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +11 -0
- package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +86 -0
- package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +64 -0
- package/src/components/form/AddressAutocomplete/AddressInputField.tsx +80 -0
- package/src/components/form/AddressAutocomplete/AddressOption.tsx +83 -0
- package/src/components/form/AddressAutocomplete/AddressSuggestionsPaper.tsx +24 -0
- package/src/components/form/AddressAutocomplete/AddressTextField.tsx +43 -0
- package/src/components/form/AddressAutocomplete/mockPlaces.ts +208 -0
- package/src/components/form/AddressAutocomplete/useAddressPredictions.ts +136 -0
- package/src/components/form/AddressAutocomplete/useAddressSelection.ts +113 -0
- package/src/components/form/AddressAutocomplete/useCurrentLocation.ts +116 -0
- package/src/components/form/AddressAutocomplete/useGoogleMapsAPI.ts +75 -0
- package/src/components/form/Button/Button.helpers.ts +36 -0
- package/src/components/form/Button/Button.stories.tsx +4 -1
- package/src/components/form/Button/Button.styles.ts +225 -0
- package/src/components/form/Button/Button.test.stories.tsx +25 -21
- package/src/components/form/Button/Button.tsx +45 -252
- package/src/components/form/Calendar/Calendar.dates.ts +100 -0
- package/src/components/form/Calendar/Calendar.helpers.ts +32 -0
- package/src/components/form/Calendar/Calendar.hooks.ts +278 -0
- package/src/components/form/Calendar/Calendar.keyboard.ts +32 -0
- package/src/components/form/Calendar/Calendar.selection.ts +156 -0
- package/src/components/form/Calendar/Calendar.test.stories.tsx +33 -34
- package/src/components/form/Calendar/Calendar.tsx +63 -551
- package/src/components/form/Calendar/Calendar.types.ts +16 -0
- package/src/components/form/Calendar/CalendarDay.tsx +127 -0
- package/src/components/form/Calendar/CalendarDayCell.tsx +52 -0
- package/src/components/form/Calendar/CalendarMonth.tsx +128 -0
- package/src/components/form/CepField/CepField.md +138 -0
- package/src/components/form/CepField/CepField.stories.tsx +138 -0
- package/src/components/form/CepField/CepField.test.stories.tsx +281 -0
- package/src/components/form/CepField/CepField.tsx +211 -0
- package/src/components/form/CepField/CepField.types.ts +73 -0
- package/src/components/form/CepField/index.ts +2 -0
- package/src/components/form/Checkbox/Checkbox.helpers.ts +28 -0
- package/src/components/form/Checkbox/Checkbox.tsx +19 -16
- package/src/components/form/CodeEditor/CodeEditor.helpers.ts +45 -0
- package/src/components/form/CodeEditor/CodeEditor.hooks.ts +104 -0
- package/src/components/form/CodeEditor/CodeEditor.monaco.ts +109 -0
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +17 -18
- package/src/components/form/CodeEditor/CodeEditor.tsx +81 -330
- package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +168 -0
- package/src/components/form/Command/Command.hooks.ts +207 -0
- package/src/components/form/Command/Command.parts.tsx +58 -0
- package/src/components/form/Command/Command.styles.ts +83 -0
- package/src/components/form/Command/Command.tsx +61 -270
- package/src/components/form/CreatableSelect/CreatableSelect.md +1 -1
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +1 -1
- package/src/components/form/CreatableSelect/CreatableSelect.tsx +152 -71
- package/src/components/form/CreatableSelect/CreatableSelect.types.ts +13 -0
- package/src/components/form/HeaderButton/HeaderButton.tsx +80 -0
- package/src/components/form/HeaderButton/index.ts +2 -0
- package/src/components/form/Input/Input.stories.tsx +5 -1
- package/src/components/form/Input/Input.styles.ts +176 -0
- package/src/components/form/Input/Input.test.stories.tsx +30 -23
- package/src/components/form/Input/Input.tsx +71 -211
- package/src/components/form/Input/Input.types.ts +11 -0
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +9 -17
- package/src/components/form/Menubar/Menubar.test.stories.tsx +4 -8
- package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +117 -0
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +3 -3
- package/src/components/form/PasswordStrength/PasswordStrength.tsx +202 -255
- package/src/components/form/PhoneInput/PhoneInput.hooks.ts +134 -0
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +4 -4
- package/src/components/form/PhoneInput/PhoneInput.tsx +279 -292
- package/src/components/form/PhoneInput/countries.ts +54 -0
- package/src/components/form/PhoneInput/phone.ts +47 -0
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +9 -9
- package/src/components/form/RichTextEditor/RichTextEditor.dom.ts +124 -0
- package/src/components/form/RichTextEditor/RichTextEditor.format.ts +61 -0
- package/src/components/form/RichTextEditor/RichTextEditor.hooks.ts +142 -0
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +5 -12
- package/src/components/form/RichTextEditor/RichTextEditor.tsx +98 -410
- package/src/components/form/RichTextEditor/RichTextEditorToolbar.tsx +187 -0
- package/src/components/form/SearchPalette/SearchPalette.parts.tsx +22 -0
- package/src/components/form/SearchPalette/SearchPalette.tsx +13 -9
- package/src/components/form/Select/Select.test.stories.tsx +35 -27
- package/src/components/form/Slider/Slider.test.stories.tsx +9 -11
- package/src/components/form/Switch/Switch.md +2 -1
- package/src/components/form/Switch/Switch.test.stories.tsx +6 -6
- package/src/components/form/Textarea/Textarea.styles.ts +143 -0
- package/src/components/form/Textarea/Textarea.test.stories.tsx +20 -26
- package/src/components/form/Textarea/Textarea.tsx +114 -535
- package/src/components/form/Textarea/TextareaRichEditor.tsx +377 -0
- package/src/components/form/Toggle/Toggle.styles.ts +211 -0
- package/src/components/form/Toggle/Toggle.test.stories.tsx +19 -27
- package/src/components/form/Toggle/Toggle.tsx +10 -185
- package/src/components/form/UploadButton/UploadButton.hooks.ts +323 -0
- package/src/components/form/UploadButton/UploadButton.parts.tsx +210 -0
- package/src/components/form/UploadButton/UploadButton.stories.tsx +2 -1
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +123 -4
- package/src/components/form/UploadButton/UploadButton.tsx +76 -305
- package/src/components/form/UploadButton/UploadButton.types.ts +16 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/form/total-form/Fields/CepField.tsx +143 -0
- package/src/components/form/total-form/Fields/ToggleField.tsx +61 -0
- package/src/components/form/total-form/Fields/index.ts +4 -0
- package/src/components/form/total-form/FormContainer.tsx +3 -3
- package/src/components/form/total-form/SubmitButton.tsx +25 -2
- package/src/components/form/total-form/total-form.test.tsx +118 -1
- package/src/components/layout/Accordion/Accordion.md +1 -1
- package/src/components/layout/Accordion/Accordion.stories.tsx +5 -1
- package/src/components/layout/Accordion/Accordion.styles.ts +80 -0
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +32 -50
- package/src/components/layout/Accordion/Accordion.tsx +2 -75
- package/src/components/layout/Card/Card.helpers.ts +30 -0
- package/src/components/layout/Card/Card.stories.tsx +9 -11
- package/src/components/layout/Card/Card.styles.ts +182 -0
- package/src/components/layout/Card/Card.test.stories.tsx +46 -46
- package/src/components/layout/Card/Card.tsx +24 -241
- package/src/components/layout/CardGrid/CardGrid.md +96 -0
- package/src/components/layout/CardGrid/CardGrid.tsx +140 -0
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +92 -0
- package/src/components/layout/CardGrid/index.ts +2 -0
- package/src/components/layout/Collapsible/Collapsible.helpers.ts +83 -0
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +7 -3
- package/src/components/layout/Collapsible/Collapsible.tsx +111 -107
- package/src/components/layout/Container/Container.test.stories.tsx +11 -16
- package/src/components/layout/ContentToolbar/ColumnsMenu.tsx +1 -1
- package/src/components/layout/ContentToolbar/ContentToolbar.tsx +120 -44
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +9 -0
- package/src/components/layout/ContentToolbar/FilterTrigger.tsx +1 -1
- package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +58 -43
- package/src/components/layout/ContentToolbar/MultiSelectTriggers.tsx +139 -0
- package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +10 -3
- package/src/components/layout/ContentToolbar/SortByDropdown.tsx +35 -11
- package/src/components/layout/ContentToolbar/ViewSelector.tsx +5 -6
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +21 -0
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +27 -1
- package/src/components/layout/Dashboard/Dashboard.tsx +27 -8
- package/src/components/layout/Dashboard/DashboardParts.tsx +12 -19
- package/src/components/layout/Dashboard/index.ts +2 -1
- package/src/components/layout/Drawer/Drawer.stories.tsx +8 -10
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +18 -14
- package/src/components/layout/Drawer/Drawer.tsx +67 -53
- package/src/components/layout/Resizable/Resizable.helpers.ts +133 -0
- package/src/components/layout/Resizable/Resizable.hooks.ts +68 -0
- package/src/components/layout/Resizable/Resizable.tsx +38 -153
- package/src/components/layout/ScrollArea/ScrollArea.helpers.ts +51 -0
- package/src/components/layout/ScrollArea/ScrollArea.styles.ts +248 -0
- package/src/components/layout/ScrollArea/ScrollArea.tsx +183 -374
- package/src/components/layout/ScrollArea/ScrollAreaContent.tsx +70 -0
- package/src/components/layout/ScrollArea/ScrollViewport.tsx +90 -0
- package/src/components/layout/Separator/Separator.styles.ts +128 -0
- package/src/components/layout/Separator/Separator.test.stories.tsx +3 -12
- package/src/components/layout/Separator/Separator.tsx +6 -132
- package/src/components/layout/SettingsLayout/SettingsLayout.parts.tsx +2 -1
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +6 -8
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +10 -12
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +35 -37
- package/src/components/layout/Skeleton/Skeleton.helpers.ts +33 -0
- package/src/components/layout/Skeleton/Skeleton.styles.ts +114 -0
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +3 -3
- package/src/components/layout/Skeleton/Skeleton.tsx +67 -155
- package/src/components/layout/TableFilter/TableFilter.tsx +2 -1
- package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +305 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.md +2 -1
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +12 -14
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +212 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +62 -61
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +116 -429
- package/src/components/navigation/CommandPalette/CommandPalette.helpers.ts +82 -0
- package/src/components/navigation/CommandPalette/CommandPalette.hooks.ts +202 -0
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +3 -1
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +3 -5
- package/src/components/navigation/CommandPalette/CommandPalette.tsx +108 -341
- package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +182 -0
- package/src/components/navigation/CommandPalette/ShortcutChip.tsx +18 -0
- package/src/components/navigation/ContextMenu/ContextMenu.md +3 -1
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +4 -1
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +19 -24
- package/src/components/navigation/ContextMenu/ContextMenu.tsx +70 -54
- package/src/components/navigation/DropdownMenu/DropdownMenu.md +3 -1
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +17 -19
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +26 -26
- package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +189 -111
- package/src/components/navigation/NavigationMenu/NavigationMegaSection.tsx +66 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.md +7 -2
- package/src/components/navigation/NavigationMenu/NavigationMenu.shell.tsx +218 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.styles.ts +223 -0
- package/src/components/navigation/NavigationMenu/NavigationMenu.tsx +119 -808
- package/src/components/navigation/NavigationMenu/NavigationMenuItem.hooks.ts +126 -0
- package/src/components/navigation/NavigationMenu/NavigationMenuItem.tsx +352 -0
- package/src/components/navigation/NavigationMenu/NavigationStandardBody.tsx +148 -0
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +15 -17
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +27 -23
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +11 -13
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +9 -11
- package/src/components/navigation/Pagination/Pagination.helpers.tsx +85 -0
- package/src/components/navigation/Pagination/Pagination.md +2 -1
- package/src/components/navigation/Pagination/Pagination.stories.tsx +4 -6
- package/src/components/navigation/Pagination/Pagination.styles.ts +135 -0
- package/src/components/navigation/Pagination/Pagination.tsx +178 -313
- package/src/components/navigation/Pagination/PaginationParts.tsx +81 -0
- package/src/components/navigation/Tabs/Tabs.md +3 -1
- package/src/components/navigation/Tabs/Tabs.stories.tsx +21 -23
- package/src/components/navigation/Tabs/Tabs.styles.ts +163 -0
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +37 -37
- package/src/components/navigation/Tabs/Tabs.tsx +192 -361
- package/src/components/navigation/Tabs/TabsPanel.tsx +100 -0
- package/src/components/typography/Blockquote/Blockquote.tsx +1 -1
- package/src/components/typography/Code/Code.test.stories.tsx +5 -5
- package/src/components/typography/Code/Code.tsx +74 -49
- package/src/components/typography/Heading/Heading.test.stories.tsx +8 -8
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +5 -12
- package/src/components/typography/Text/Text.tsx +84 -77
- package/src/components/utility/AnimatedIcon/AnimatedIcon.md +4 -4
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +19 -21
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +19 -12
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +9 -9
- package/src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts +118 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +54 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +12 -9
- package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +29 -148
- package/src/components/utility/InstallPrompt/InstallPrompt.helpers.ts +124 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.md +113 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +118 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.styles.ts +50 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +83 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.tsx +192 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +78 -0
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +65 -0
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +271 -0
- package/src/components/utility/InstallPrompt/index.ts +10 -0
- package/src/components/utility/InstallPrompt/usePwaInstall.ts +171 -0
- package/src/components/utility/Transition/Transition.helpers.ts +40 -0
- package/src/components/utility/Transition/Transition.tsx +17 -68
- package/src/components/utility/VirtualList/VirtualList.hooks.ts +319 -0
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +49 -38
- package/src/components/utility/VirtualList/VirtualList.tsx +110 -315
- package/src/components/utility/WorkflowStep/WorkflowStep.helpers.ts +151 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.parts.tsx +371 -0
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +9 -11
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +7 -7
- package/src/components/utility/WorkflowStep/WorkflowStep.tsx +51 -449
- package/src/components/utility/WorkflowStep/WorkflowStepRow.tsx +96 -0
- package/src/components/utility/index.ts +1 -0
- package/src/mui/Link.ts +2 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/viewport.ts +65 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material';
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import type { FC } from 'react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import type { DayRenderArgs } from './Calendar.types';
|
|
7
|
+
|
|
8
|
+
const CELL_SIZE = 40;
|
|
9
|
+
|
|
10
|
+
interface DayState extends DayRenderArgs {
|
|
11
|
+
outside: boolean;
|
|
12
|
+
focused: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Order matters: these land on the element in the order listed.
|
|
16
|
+
const CLASS_FLAGS: Array<[keyof DayState, string]> = [
|
|
17
|
+
['selected', 'selected'],
|
|
18
|
+
['inRange', 'in-range'],
|
|
19
|
+
['rangeStart', 'range-start'],
|
|
20
|
+
['rangeEnd', 'range-end'],
|
|
21
|
+
['disabled', 'disabled'],
|
|
22
|
+
['today', 'today'],
|
|
23
|
+
['outside', 'outside'],
|
|
24
|
+
['hovered', 'hovered'],
|
|
25
|
+
['focused', 'focused'],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const dayClassNames = (state: DayState, custom?: string): string =>
|
|
29
|
+
[
|
|
30
|
+
'calendar-day',
|
|
31
|
+
...CLASS_FLAGS.filter(([flag]) => state[flag]).map(([, name]) => name),
|
|
32
|
+
...(custom ? [custom] : []),
|
|
33
|
+
].join(' ');
|
|
34
|
+
|
|
35
|
+
const isEndpoint = (day: DayRenderArgs): boolean =>
|
|
36
|
+
day.selected || day.rangeStart || day.rangeEnd;
|
|
37
|
+
|
|
38
|
+
const backgroundFor = (day: DayRenderArgs): string => {
|
|
39
|
+
if (isEndpoint(day)) return 'primary.main';
|
|
40
|
+
if (day.inRange || day.hovered) return 'primary.light';
|
|
41
|
+
return 'transparent';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const colorFor = (day: DayRenderArgs, visible: boolean): string => {
|
|
45
|
+
if (isEndpoint(day)) return 'primary.contrastText';
|
|
46
|
+
if (day.disabled) return 'text.disabled';
|
|
47
|
+
// Outside days that are switched off still occupy the grid, they just have
|
|
48
|
+
// nothing to show.
|
|
49
|
+
if (!visible) return 'transparent';
|
|
50
|
+
return 'text.primary';
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const hoverBackgroundFor = (day: DayRenderArgs): string | undefined => {
|
|
54
|
+
if (day.disabled) return undefined;
|
|
55
|
+
return day.selected ? 'primary.dark' : 'action.hover';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const dayCellSx = (day: DayRenderArgs, visible: boolean): SxProps<Theme> => ({
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
justifyContent: 'center',
|
|
62
|
+
width: CELL_SIZE,
|
|
63
|
+
height: CELL_SIZE,
|
|
64
|
+
border: 'none',
|
|
65
|
+
borderRadius: 1,
|
|
66
|
+
cursor: day.disabled ? 'not-allowed' : 'pointer',
|
|
67
|
+
backgroundColor: backgroundFor(day),
|
|
68
|
+
color: colorFor(day, visible),
|
|
69
|
+
opacity: !day.inCurrentMonth && visible ? 0.5 : 1,
|
|
70
|
+
fontWeight: day.today ? 'bold' : 'normal',
|
|
71
|
+
'&:hover': {
|
|
72
|
+
backgroundColor: hoverBackgroundFor(day),
|
|
73
|
+
},
|
|
74
|
+
'&:focus': {
|
|
75
|
+
outline: '2px solid',
|
|
76
|
+
outlineColor: 'primary.main',
|
|
77
|
+
outlineOffset: 1,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export interface CalendarDayProps {
|
|
82
|
+
day: DayRenderArgs;
|
|
83
|
+
focused: boolean;
|
|
84
|
+
showOutsideDays: boolean;
|
|
85
|
+
dayClassName?: (args: DayRenderArgs) => string;
|
|
86
|
+
onSelect: (date: Date) => void;
|
|
87
|
+
onHover: (date: Date) => void;
|
|
88
|
+
onHoverEnd: () => void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const CalendarDay: FC<CalendarDayProps> = ({
|
|
92
|
+
day,
|
|
93
|
+
focused,
|
|
94
|
+
showOutsideDays,
|
|
95
|
+
dayClassName,
|
|
96
|
+
onSelect,
|
|
97
|
+
onHover,
|
|
98
|
+
onHoverEnd,
|
|
99
|
+
}) => {
|
|
100
|
+
const visible = day.inCurrentMonth || showOutsideDays;
|
|
101
|
+
const state: DayState = { ...day, outside: !day.inCurrentMonth, focused };
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Box
|
|
105
|
+
component="button"
|
|
106
|
+
role="gridcell"
|
|
107
|
+
tabIndex={focused ? 0 : -1}
|
|
108
|
+
aria-selected={day.selected}
|
|
109
|
+
aria-disabled={day.disabled}
|
|
110
|
+
aria-current={day.today ? 'date' : undefined}
|
|
111
|
+
className={dayClassNames(state, dayClassName?.(day))}
|
|
112
|
+
data-testid={`calendar-date-${day.date.getDate()}`}
|
|
113
|
+
onClick={() => onSelect(day.date)}
|
|
114
|
+
onMouseEnter={() => onHover(day.date)}
|
|
115
|
+
onMouseLeave={onHoverEnd}
|
|
116
|
+
sx={dayCellSx(day, visible)}
|
|
117
|
+
>
|
|
118
|
+
{day.selected && (
|
|
119
|
+
<Box component="span" data-testid="calendar-selected-date" sx={{ display: 'none' }} />
|
|
120
|
+
)}
|
|
121
|
+
{day.today && (
|
|
122
|
+
<Box component="span" data-testid="calendar-today" sx={{ display: 'none' }} />
|
|
123
|
+
)}
|
|
124
|
+
{visible ? day.date.getDate() : ''}
|
|
125
|
+
</Box>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { isSameDay } from './Calendar.dates';
|
|
5
|
+
import { buildDayArgs } from './Calendar.selection';
|
|
6
|
+
import type { CalendarProps, DateRange, MonthMatrixDay, SelectionMode } from './Calendar.types';
|
|
7
|
+
import { CalendarDay } from './CalendarDay';
|
|
8
|
+
|
|
9
|
+
// Everything a cell needs that is the same for every cell in the calendar.
|
|
10
|
+
export interface DayCellContext {
|
|
11
|
+
selectionMode: SelectionMode;
|
|
12
|
+
value: Date | null;
|
|
13
|
+
range: DateRange;
|
|
14
|
+
hoveredDate: Date | null;
|
|
15
|
+
focusedDate: Date;
|
|
16
|
+
today: Date;
|
|
17
|
+
showOutsideDays: boolean;
|
|
18
|
+
isDisabled: (date: Date) => boolean;
|
|
19
|
+
renderDay?: CalendarProps['renderDay'];
|
|
20
|
+
dayClassName?: CalendarProps['dayClassName'];
|
|
21
|
+
onSelect: (date: Date) => void;
|
|
22
|
+
onHover: (date: Date) => void;
|
|
23
|
+
onHoverEnd: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CalendarDayCell: FC<DayCellContext & { day: MonthMatrixDay }> = ({
|
|
27
|
+
day,
|
|
28
|
+
focusedDate,
|
|
29
|
+
showOutsideDays,
|
|
30
|
+
renderDay,
|
|
31
|
+
dayClassName,
|
|
32
|
+
onSelect,
|
|
33
|
+
onHover,
|
|
34
|
+
onHoverEnd,
|
|
35
|
+
...context
|
|
36
|
+
}) => {
|
|
37
|
+
const args = buildDayArgs({ day, ...context });
|
|
38
|
+
|
|
39
|
+
if (renderDay) return <>{renderDay(args)}</>;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<CalendarDay
|
|
43
|
+
day={args}
|
|
44
|
+
focused={isSameDay(args.date, focusedDate)}
|
|
45
|
+
showOutsideDays={showOutsideDays}
|
|
46
|
+
dayClassName={dayClassName}
|
|
47
|
+
onSelect={onSelect}
|
|
48
|
+
onHover={onHover}
|
|
49
|
+
onHoverEnd={onHoverEnd}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import ChevronLeft from '@mui/icons-material/ChevronLeft';
|
|
2
|
+
import ChevronRight from '@mui/icons-material/ChevronRight';
|
|
3
|
+
import { Box, IconButton, Typography } from '@mui/material';
|
|
4
|
+
import type { FC } from 'react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { getMonthName } from './Calendar.dates';
|
|
8
|
+
import type { CalendarProps, MonthMatrix } from './Calendar.types';
|
|
9
|
+
import type { DayCellContext } from './CalendarDayCell';
|
|
10
|
+
import { CalendarDayCell } from './CalendarDayCell';
|
|
11
|
+
|
|
12
|
+
const SPACER_WIDTH = 32;
|
|
13
|
+
|
|
14
|
+
const GRID_SX = { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 0.5 } as const;
|
|
15
|
+
|
|
16
|
+
// Only the outermost months carry arrows; the ones between get a spacer so their
|
|
17
|
+
// titles stay centred.
|
|
18
|
+
const MonthNavHeader: FC<{
|
|
19
|
+
title: React.ReactNode;
|
|
20
|
+
showPrevious: boolean;
|
|
21
|
+
showNext: boolean;
|
|
22
|
+
onPreviousMonth: () => void;
|
|
23
|
+
onNextMonth: () => void;
|
|
24
|
+
}> = ({ title, showPrevious, showNext, onPreviousMonth, onNextMonth }) => (
|
|
25
|
+
<Box
|
|
26
|
+
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}
|
|
27
|
+
data-testid="calendar-header"
|
|
28
|
+
>
|
|
29
|
+
{showPrevious && (
|
|
30
|
+
<IconButton onClick={onPreviousMonth} size="small" data-testid="calendar-prev-month">
|
|
31
|
+
<ChevronLeft />
|
|
32
|
+
</IconButton>
|
|
33
|
+
)}
|
|
34
|
+
|
|
35
|
+
<Typography
|
|
36
|
+
variant="h6"
|
|
37
|
+
component="h2"
|
|
38
|
+
sx={{ fontWeight: 'bold', flex: 1, textAlign: 'center' }}
|
|
39
|
+
>
|
|
40
|
+
{title}
|
|
41
|
+
</Typography>
|
|
42
|
+
|
|
43
|
+
{showNext && (
|
|
44
|
+
<IconButton onClick={onNextMonth} size="small" data-testid="calendar-next-month">
|
|
45
|
+
<ChevronRight />
|
|
46
|
+
</IconButton>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
{!showPrevious && !showNext && <Box sx={{ width: SPACER_WIDTH }} />}
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const WeekdayHeader: FC<{ weekdayNames: string[] }> = ({ weekdayNames }) => (
|
|
54
|
+
<Box sx={{ ...GRID_SX, mb: 1 }}>
|
|
55
|
+
{weekdayNames.map((weekday, index) => (
|
|
56
|
+
<Box
|
|
57
|
+
// Narrow weekday names repeat within a week ('S', 'T'), so position is
|
|
58
|
+
// the only stable key here.
|
|
59
|
+
key={`weekday-${index}`}
|
|
60
|
+
role="columnheader"
|
|
61
|
+
data-testid={`calendar-weekday-${index}`}
|
|
62
|
+
sx={{
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
justifyContent: 'center',
|
|
66
|
+
height: SPACER_WIDTH,
|
|
67
|
+
fontSize: '0.875rem',
|
|
68
|
+
fontWeight: 'bold',
|
|
69
|
+
color: 'text.secondary',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{weekday}
|
|
73
|
+
</Box>
|
|
74
|
+
))}
|
|
75
|
+
</Box>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export interface CalendarMonthProps {
|
|
79
|
+
matrix: MonthMatrix;
|
|
80
|
+
monthIndex: number;
|
|
81
|
+
monthsToShow: number;
|
|
82
|
+
locale: string;
|
|
83
|
+
weekdayNames: string[];
|
|
84
|
+
renderHeader?: CalendarProps['renderHeader'];
|
|
85
|
+
onPreviousMonth: () => void;
|
|
86
|
+
onNextMonth: () => void;
|
|
87
|
+
dayContext: DayCellContext;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const CalendarMonth: FC<CalendarMonthProps> = ({
|
|
91
|
+
matrix,
|
|
92
|
+
monthIndex,
|
|
93
|
+
monthsToShow,
|
|
94
|
+
locale,
|
|
95
|
+
weekdayNames,
|
|
96
|
+
renderHeader,
|
|
97
|
+
onPreviousMonth,
|
|
98
|
+
onNextMonth,
|
|
99
|
+
dayContext,
|
|
100
|
+
}) => {
|
|
101
|
+
const monthName = getMonthName(matrix.month, matrix.year, locale);
|
|
102
|
+
const title = renderHeader ? renderHeader({ month: matrix.month, year: matrix.year }) : monthName;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Box sx={{ minWidth: 280 }}>
|
|
106
|
+
<MonthNavHeader
|
|
107
|
+
title={title}
|
|
108
|
+
showPrevious={monthIndex === 0}
|
|
109
|
+
showNext={monthIndex === monthsToShow - 1}
|
|
110
|
+
onPreviousMonth={onPreviousMonth}
|
|
111
|
+
onNextMonth={onNextMonth}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<WeekdayHeader weekdayNames={weekdayNames} />
|
|
115
|
+
|
|
116
|
+
<Box
|
|
117
|
+
role="grid"
|
|
118
|
+
aria-labelledby={`month-${monthIndex}`}
|
|
119
|
+
data-testid="calendar-grid"
|
|
120
|
+
sx={GRID_SX}
|
|
121
|
+
>
|
|
122
|
+
{matrix.weeks.flat().map((day) => (
|
|
123
|
+
<CalendarDayCell key={day.key} day={day} {...dayContext} />
|
|
124
|
+
))}
|
|
125
|
+
</Box>
|
|
126
|
+
</Box>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# CepField Component
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A CEP input that fills the rest of the address in — the autofill pattern every
|
|
6
|
+
Brazilian form has used for twenty years. The user types eight digits and the
|
|
7
|
+
logradouro, bairro, cidade and UF arrive on their own; only `número` and
|
|
8
|
+
`complemento` are left to type, because no CEP database holds them.
|
|
9
|
+
|
|
10
|
+
The component owns the *interaction* — masking, validation, debouncing,
|
|
11
|
+
race-safety, status — and takes the *lookup* as a prop. It therefore knows
|
|
12
|
+
nothing about providers, transport or authentication, and the same component
|
|
13
|
+
serves a supplier form, a store-location form or a delivery checkout.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Masks to `#####-###` on every keystroke, tolerant of pasted/messy input
|
|
18
|
+
- Fires the lookup only once the CEP is complete, after a debounce
|
|
19
|
+
- Never re-queries a CEP it already resolved
|
|
20
|
+
- Race-safe: a slow response for a since-edited CEP can never overwrite a newer one
|
|
21
|
+
- Degrades to a plain masked input when the lookup fails, throws, or is absent
|
|
22
|
+
- Accessible status via `role="status"` / `aria-live="polite"` and `aria-busy`
|
|
23
|
+
- Works uncontrolled-by-a-form (props) or bound to `total-form` (`Fields.CepField`)
|
|
24
|
+
|
|
25
|
+
## Props Documentation
|
|
26
|
+
|
|
27
|
+
### Core Props
|
|
28
|
+
|
|
29
|
+
- `value`: Current value, masked or bare — the field re-masks it
|
|
30
|
+
- `onChange`: Called on every keystroke with the **masked** value
|
|
31
|
+
- `onLookup`: `(cep: string) => Promise<CepAddress | null>` — resolve a complete
|
|
32
|
+
CEP. Omit for a masked, validated input with no lookup at all
|
|
33
|
+
- `onResolved`: `(address: CepAddress) => void` — called once a CEP resolves.
|
|
34
|
+
This is where the caller fills its address fields
|
|
35
|
+
- `onNotFound`: `() => void` — called when a **complete** CEP fails to resolve.
|
|
36
|
+
The counterpart to `onResolved`: a caller that auto-filled from a previous CEP
|
|
37
|
+
should undo that here (see *Behaviour notes*)
|
|
38
|
+
|
|
39
|
+
### Display Props
|
|
40
|
+
|
|
41
|
+
- `label`: Visible label (default `CEP`)
|
|
42
|
+
- `placeholder`: Placeholder text (default `00000-000`)
|
|
43
|
+
- `name`: Field name / input id (default `postalCode`)
|
|
44
|
+
- `dataTestId`: Test id for the input (default `cep-field-<name>`)
|
|
45
|
+
|
|
46
|
+
### State Props
|
|
47
|
+
|
|
48
|
+
- `error`: Validation error from the caller's form, shown below the input
|
|
49
|
+
- `disabled`: Disables the input and suppresses lookups
|
|
50
|
+
- `debounceMs`: Delay after the last keystroke before looking up (default `500`)
|
|
51
|
+
|
|
52
|
+
### The `CepAddress` shape
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface CepAddress {
|
|
56
|
+
cep: string; // bare digits
|
|
57
|
+
street: string | null; // logradouro
|
|
58
|
+
neighborhood: string | null; // bairro
|
|
59
|
+
city: string | null; // município
|
|
60
|
+
state: string | null; // UF
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### Standalone
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const [cep, setCep] = useState('');
|
|
70
|
+
|
|
71
|
+
<CepField
|
|
72
|
+
value={cep}
|
|
73
|
+
onChange={setCep}
|
|
74
|
+
onLookup={(c) =>
|
|
75
|
+
fetch(`/api/admin/${slug}/lookup/cep/${c}`)
|
|
76
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
77
|
+
.then((body) => body?.data ?? null)
|
|
78
|
+
}
|
|
79
|
+
onResolved={(address) => {
|
|
80
|
+
setStreet(address.street ?? '');
|
|
81
|
+
setCity(address.city ?? '');
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Bound to `total-form`
|
|
87
|
+
|
|
88
|
+
`Fields.CepField` wires the field to the surrounding `FormContainer` and writes
|
|
89
|
+
the resolved parts straight into sibling fields:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<Fields.CepField
|
|
93
|
+
name="postalCode"
|
|
94
|
+
lookup={lookupCep}
|
|
95
|
+
fills={{
|
|
96
|
+
street: 'addressLine1',
|
|
97
|
+
neighborhood: 'neighborhood',
|
|
98
|
+
city: 'city',
|
|
99
|
+
state: 'state',
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Omit a key from `fills` to leave that part alone — a form with no bairro column
|
|
105
|
+
simply doesn't map `neighborhood`.
|
|
106
|
+
|
|
107
|
+
## Behaviour notes
|
|
108
|
+
|
|
109
|
+
**Autofill never blocks.** A provider outage, an unknown CEP, a timeout or a
|
|
110
|
+
lookup that throws all land in the same place: a `notfound` status and a field
|
|
111
|
+
that behaves exactly like a plain text input. Saving is never gated on a
|
|
112
|
+
third-party service being up.
|
|
113
|
+
|
|
114
|
+
**It will not overwrite the user's work.** `Fields.CepField` writes a target
|
|
115
|
+
field only when it is empty, or when it still holds the exact value that the
|
|
116
|
+
component put there. So correcting the CEP re-fills the address, but a
|
|
117
|
+
hand-edited street survives the next lookup.
|
|
118
|
+
|
|
119
|
+
**It will not leave one CEP's address under another's.** When a complete CEP
|
|
120
|
+
fails to resolve, `Fields.CepField` undoes its own autofill (again, only the
|
|
121
|
+
fields still holding what it wrote). Otherwise typing CEP A, getting a hit, then
|
|
122
|
+
correcting to an unknown CEP B would silently save A's street and city against
|
|
123
|
+
B — a plausible wrong address, which is worse than an empty one. The same rule
|
|
124
|
+
governs the CNPJ autofill on the fornecedores form.
|
|
125
|
+
|
|
126
|
+
**Validation is shape-only.** A CEP is a postal range, not a checksummed
|
|
127
|
+
document — there is no check digit to verify. Whether it *exists* is answered by
|
|
128
|
+
the lookup. The pure helpers (`formatCep`, `isValidCep`) live in
|
|
129
|
+
`@12-apps/forms-core` so the browser mask and any server-side guard share one rule.
|
|
130
|
+
|
|
131
|
+
## Accessibility
|
|
132
|
+
|
|
133
|
+
- The status line is a `role="status"` live region, so screen readers announce
|
|
134
|
+
"Buscando endereço…" / "Endereço preenchido pelo CEP" without stealing focus
|
|
135
|
+
- `aria-busy` reflects the in-flight lookup
|
|
136
|
+
- `aria-describedby` links the input to its status region
|
|
137
|
+
- `inputMode="numeric"` brings up the numeric keypad on mobile
|
|
138
|
+
- `autoComplete="postal-code"` lets the browser's own autofill participate
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Paper, Stack, Typography } from '@mui/material';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { CepField } from './CepField';
|
|
6
|
+
import type { CepAddress } from './CepField.types';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof CepField> = {
|
|
9
|
+
title: 'Form/CepField',
|
|
10
|
+
component: CepField,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'padded',
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
'A CEP input that fills the rest of the address in. Masks to `#####-###`, waits for a complete CEP, then calls the injected `onLookup` and hands the result to `onResolved`. The lookup is injected, so the component works with any backend — or none.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
tags: ['autodocs', 'component:CepField'],
|
|
21
|
+
argTypes: {
|
|
22
|
+
value: { control: 'text', description: 'Current value (masked or bare)' },
|
|
23
|
+
label: { control: 'text', description: 'Visible label' },
|
|
24
|
+
placeholder: { control: 'text', description: 'Placeholder text' },
|
|
25
|
+
error: { control: 'text', description: 'Validation error from the caller' },
|
|
26
|
+
disabled: { control: 'boolean', description: 'Disable input and lookups' },
|
|
27
|
+
debounceMs: { control: 'number', description: 'Delay before looking up' },
|
|
28
|
+
name: { control: 'text', description: 'Field name / input id' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof meta>;
|
|
34
|
+
|
|
35
|
+
/** Stand-in for a real provider — resolves one CEP, rejects everything else. */
|
|
36
|
+
const FIXTURES: Record<string, CepAddress> = {
|
|
37
|
+
'01310100': {
|
|
38
|
+
cep: '01310100',
|
|
39
|
+
street: 'Avenida Paulista',
|
|
40
|
+
neighborhood: 'Bela Vista',
|
|
41
|
+
city: 'São Paulo',
|
|
42
|
+
state: 'SP',
|
|
43
|
+
},
|
|
44
|
+
'20040020': {
|
|
45
|
+
cep: '20040020',
|
|
46
|
+
street: 'Avenida Rio Branco',
|
|
47
|
+
neighborhood: 'Centro',
|
|
48
|
+
city: 'Rio de Janeiro',
|
|
49
|
+
state: 'RJ',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const fakeLookup = async (cep: string): Promise<CepAddress | null> => {
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
55
|
+
return FIXTURES[cep] ?? null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** The whole point: type a CEP, watch the address fields fill themselves. */
|
|
59
|
+
function Demo({
|
|
60
|
+
lookup = fakeLookup,
|
|
61
|
+
...props
|
|
62
|
+
}: {
|
|
63
|
+
lookup?: (cep: string) => Promise<CepAddress | null>;
|
|
64
|
+
label?: string;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
}): React.ReactElement {
|
|
67
|
+
const [cep, setCep] = useState('');
|
|
68
|
+
const [address, setAddress] = useState<CepAddress | null>(null);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Stack spacing={2} sx={{ maxWidth: 420 }}>
|
|
72
|
+
<CepField
|
|
73
|
+
{...props}
|
|
74
|
+
value={cep}
|
|
75
|
+
onChange={setCep}
|
|
76
|
+
onLookup={lookup}
|
|
77
|
+
onResolved={setAddress}
|
|
78
|
+
/>
|
|
79
|
+
<Paper variant="outlined" sx={{ p: 2 }}>
|
|
80
|
+
<Typography variant="caption" color="text.secondary">
|
|
81
|
+
Endereço resolvido
|
|
82
|
+
</Typography>
|
|
83
|
+
<Typography variant="body2">{address?.street ?? '—'}</Typography>
|
|
84
|
+
<Typography variant="body2">{address?.neighborhood ?? '—'}</Typography>
|
|
85
|
+
<Typography variant="body2">
|
|
86
|
+
{address ? `${address.city ?? '—'} / ${address.state ?? '—'}` : '—'}
|
|
87
|
+
</Typography>
|
|
88
|
+
</Paper>
|
|
89
|
+
<Typography variant="caption" color="text.secondary">
|
|
90
|
+
Try 01310-100 or 20040-020. Anything else resolves to “não encontrado”.
|
|
91
|
+
</Typography>
|
|
92
|
+
</Stack>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Default: Story = {
|
|
97
|
+
render: () => <Demo />,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const CustomLabel: Story = {
|
|
101
|
+
render: () => <Demo label="CEP do fornecedor" />,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const Disabled: Story = {
|
|
105
|
+
render: () => <Demo disabled />,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Every provider is down. The field degrades to a plain masked input. */
|
|
109
|
+
export const LookupUnavailable: Story = {
|
|
110
|
+
render: () => <Demo lookup={async () => null} />,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** No `onLookup` at all — just the mask and validation. */
|
|
114
|
+
export const MaskOnly: Story = {
|
|
115
|
+
render: function MaskOnlyStory() {
|
|
116
|
+
const [cep, setCep] = useState('');
|
|
117
|
+
return (
|
|
118
|
+
<Stack sx={{ maxWidth: 420 }}>
|
|
119
|
+
<CepField value={cep} onChange={setCep} />
|
|
120
|
+
</Stack>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const WithError: Story = {
|
|
126
|
+
render: function WithErrorStory() {
|
|
127
|
+
const [cep, setCep] = useState('123');
|
|
128
|
+
return (
|
|
129
|
+
<Stack sx={{ maxWidth: 420 }}>
|
|
130
|
+
<CepField
|
|
131
|
+
value={cep}
|
|
132
|
+
onChange={setCep}
|
|
133
|
+
error="CEP inválido — use o formato 00000-000."
|
|
134
|
+
/>
|
|
135
|
+
</Stack>
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
};
|