@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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from '@mui/material';
|
|
9
9
|
import React from 'react';
|
|
10
10
|
|
|
11
|
+
import { transitionDuration, transitionEasing } from './Transition.helpers';
|
|
11
12
|
import type { CustomTransitionProps } from './Transition.types';
|
|
12
13
|
|
|
13
14
|
export const Transition: React.FC<CustomTransitionProps> = ({
|
|
@@ -22,58 +23,10 @@ export const Transition: React.FC<CustomTransitionProps> = ({
|
|
|
22
23
|
}) => {
|
|
23
24
|
const theme = useTheme();
|
|
24
25
|
|
|
25
|
-
const getDuration = () => {
|
|
26
|
-
if (typeof duration === 'number') {
|
|
27
|
-
return duration;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof duration === 'object') {
|
|
31
|
-
return duration;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Default durations based on variant
|
|
35
|
-
switch (variant) {
|
|
36
|
-
case 'fade':
|
|
37
|
-
return theme.transitions.duration.shorter;
|
|
38
|
-
case 'slide':
|
|
39
|
-
return theme.transitions.duration.enteringScreen;
|
|
40
|
-
case 'scale':
|
|
41
|
-
case 'grow':
|
|
42
|
-
case 'zoom':
|
|
43
|
-
return theme.transitions.duration.shorter;
|
|
44
|
-
case 'collapse':
|
|
45
|
-
return theme.transitions.duration.standard;
|
|
46
|
-
default:
|
|
47
|
-
return theme.transitions.duration.standard;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const getEasing = () => {
|
|
52
|
-
if (typeof easing === 'string') {
|
|
53
|
-
return easing;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (typeof easing === 'object') {
|
|
57
|
-
return easing;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Default easing based on variant
|
|
61
|
-
switch (variant) {
|
|
62
|
-
case 'slide':
|
|
63
|
-
return theme.transitions.easing.easeOut;
|
|
64
|
-
case 'scale':
|
|
65
|
-
case 'grow':
|
|
66
|
-
case 'zoom':
|
|
67
|
-
return theme.transitions.easing.easeInOut;
|
|
68
|
-
default:
|
|
69
|
-
return theme.transitions.easing.easeInOut;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
26
|
const transitionProps = {
|
|
74
27
|
in: inProp,
|
|
75
|
-
timeout:
|
|
76
|
-
easing:
|
|
28
|
+
timeout: transitionDuration(theme, variant, duration),
|
|
29
|
+
easing: transitionEasing(theme, variant, easing),
|
|
77
30
|
unmountOnExit: true,
|
|
78
31
|
style: {
|
|
79
32
|
transitionDelay: `${delay}ms`,
|
|
@@ -81,22 +34,26 @@ export const Transition: React.FC<CustomTransitionProps> = ({
|
|
|
81
34
|
...props,
|
|
82
35
|
};
|
|
83
36
|
|
|
37
|
+
// Every variant wraps the same three-deep structure; only the MUI transition
|
|
38
|
+
// component differs.
|
|
39
|
+
const content = (
|
|
40
|
+
<div data-testid="transition-element">
|
|
41
|
+
<div data-testid="transition-content">{children}</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
|
|
84
45
|
switch (variant) {
|
|
85
46
|
case 'fade':
|
|
86
47
|
return (
|
|
87
48
|
<Fade {...transitionProps} data-testid="transition-wrapper">
|
|
88
|
-
|
|
89
|
-
<div data-testid="transition-content">{children}</div>
|
|
90
|
-
</div>
|
|
49
|
+
{content}
|
|
91
50
|
</Fade>
|
|
92
51
|
);
|
|
93
52
|
|
|
94
53
|
case 'slide':
|
|
95
54
|
return (
|
|
96
55
|
<Slide direction={direction} {...transitionProps} data-testid="transition-wrapper">
|
|
97
|
-
|
|
98
|
-
<div data-testid="transition-content">{children}</div>
|
|
99
|
-
</div>
|
|
56
|
+
{content}
|
|
100
57
|
</Slide>
|
|
101
58
|
);
|
|
102
59
|
|
|
@@ -104,36 +61,28 @@ export const Transition: React.FC<CustomTransitionProps> = ({
|
|
|
104
61
|
case 'grow':
|
|
105
62
|
return (
|
|
106
63
|
<Grow {...transitionProps} data-testid="transition-wrapper">
|
|
107
|
-
|
|
108
|
-
<div data-testid="transition-content">{children}</div>
|
|
109
|
-
</div>
|
|
64
|
+
{content}
|
|
110
65
|
</Grow>
|
|
111
66
|
);
|
|
112
67
|
|
|
113
68
|
case 'collapse':
|
|
114
69
|
return (
|
|
115
70
|
<Collapse {...transitionProps} data-testid="transition-wrapper">
|
|
116
|
-
|
|
117
|
-
<div data-testid="transition-content">{children}</div>
|
|
118
|
-
</div>
|
|
71
|
+
{content}
|
|
119
72
|
</Collapse>
|
|
120
73
|
);
|
|
121
74
|
|
|
122
75
|
case 'zoom':
|
|
123
76
|
return (
|
|
124
77
|
<Zoom {...transitionProps} data-testid="transition-wrapper">
|
|
125
|
-
|
|
126
|
-
<div data-testid="transition-content">{children}</div>
|
|
127
|
-
</div>
|
|
78
|
+
{content}
|
|
128
79
|
</Zoom>
|
|
129
80
|
);
|
|
130
81
|
|
|
131
82
|
default:
|
|
132
83
|
return (
|
|
133
84
|
<Fade {...transitionProps} data-testid="transition-wrapper">
|
|
134
|
-
|
|
135
|
-
<div data-testid="transition-content">{children}</div>
|
|
136
|
-
</div>
|
|
85
|
+
{content}
|
|
137
86
|
</Fade>
|
|
138
87
|
);
|
|
139
88
|
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { VirtualGridProps, VirtualListProps } from './VirtualList.types';
|
|
4
|
+
|
|
5
|
+
type ScrollEvent = React.UIEvent<HTMLDivElement> | Event;
|
|
6
|
+
type ListItems = VirtualListProps['items'];
|
|
7
|
+
type ListVariant = NonNullable<VirtualListProps['variant']>;
|
|
8
|
+
type ItemHeight = (index: number) => number;
|
|
9
|
+
|
|
10
|
+
// Handles both React synthetic events and native DOM events, since the same
|
|
11
|
+
// handler serves an internal onScroll and an external container's listener.
|
|
12
|
+
const scrollOffsets = (event: ScrollEvent) => {
|
|
13
|
+
const target = (event as React.UIEvent<HTMLDivElement>).currentTarget || (event as Event).target;
|
|
14
|
+
const element = target as HTMLElement;
|
|
15
|
+
return { top: element.scrollTop, left: element.scrollLeft };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* When the caller owns the scroll container, the component cannot attach
|
|
20
|
+
* `onScroll` through JSX and has to subscribe to that element directly.
|
|
21
|
+
*/
|
|
22
|
+
const useExternalScrollListener = (
|
|
23
|
+
scrollContainerRef: VirtualListProps['scrollContainerRef'],
|
|
24
|
+
handleScroll: (event: Event) => void,
|
|
25
|
+
) => {
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!scrollContainerRef?.current) return;
|
|
28
|
+
|
|
29
|
+
const element = scrollContainerRef.current;
|
|
30
|
+
const scrollHandler = (e: Event) => handleScroll(e);
|
|
31
|
+
|
|
32
|
+
element.addEventListener('scroll', scrollHandler);
|
|
33
|
+
return () => element.removeEventListener('scroll', scrollHandler);
|
|
34
|
+
}, [scrollContainerRef, handleScroll]);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tracks the scroll offset whether it arrives through our own `onScroll` or
|
|
39
|
+
* through a container the caller owns, and hands both offsets to `report` so a
|
|
40
|
+
* grid can pass on the horizontal one too.
|
|
41
|
+
*/
|
|
42
|
+
const useScrollTop = (
|
|
43
|
+
scrollContainerRef: VirtualListProps['scrollContainerRef'],
|
|
44
|
+
report: (top: number, left: number) => void,
|
|
45
|
+
) => {
|
|
46
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
47
|
+
|
|
48
|
+
const handleScroll = useCallback(
|
|
49
|
+
(event: ScrollEvent) => {
|
|
50
|
+
const { top, left } = scrollOffsets(event);
|
|
51
|
+
setScrollTop(top);
|
|
52
|
+
report(top, left);
|
|
53
|
+
},
|
|
54
|
+
[report],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
useExternalScrollListener(scrollContainerRef, handleScroll);
|
|
58
|
+
|
|
59
|
+
return { scrollTop, handleScroll };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
interface HeightArgs {
|
|
63
|
+
items: ListItems;
|
|
64
|
+
variant: ListVariant;
|
|
65
|
+
itemHeight: number;
|
|
66
|
+
estimatedItemHeight: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fixed lists know every height up front. Variable ones prefer the item's own
|
|
71
|
+
* declared height, fall back to a measurement taken on a previous render, and
|
|
72
|
+
* only then guess.
|
|
73
|
+
*/
|
|
74
|
+
const useItemHeight = ({
|
|
75
|
+
items,
|
|
76
|
+
variant,
|
|
77
|
+
itemHeight,
|
|
78
|
+
estimatedItemHeight,
|
|
79
|
+
}: HeightArgs): ItemHeight => {
|
|
80
|
+
const measured = useRef<Map<number, number>>(new Map());
|
|
81
|
+
|
|
82
|
+
return useCallback(
|
|
83
|
+
(index: number): number => {
|
|
84
|
+
if (variant !== 'variable') {
|
|
85
|
+
return itemHeight;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return items[index]?.height || measured.current.get(index) || estimatedItemHeight;
|
|
89
|
+
},
|
|
90
|
+
[variant, itemHeight, estimatedItemHeight, items],
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const useTotalHeight = (
|
|
95
|
+
items: ListItems,
|
|
96
|
+
variant: ListVariant,
|
|
97
|
+
itemHeight: number,
|
|
98
|
+
getItemHeight: ItemHeight,
|
|
99
|
+
) =>
|
|
100
|
+
useMemo(() => {
|
|
101
|
+
if (variant === 'fixed') {
|
|
102
|
+
return items.length * itemHeight;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let sum = 0;
|
|
106
|
+
for (let i = 0; i < items.length; i++) {
|
|
107
|
+
sum += getItemHeight(i);
|
|
108
|
+
}
|
|
109
|
+
return sum;
|
|
110
|
+
}, [items.length, variant, itemHeight, getItemHeight]);
|
|
111
|
+
|
|
112
|
+
interface RangeArgs {
|
|
113
|
+
count: number;
|
|
114
|
+
scrollTop: number;
|
|
115
|
+
height: number;
|
|
116
|
+
overscan: number;
|
|
117
|
+
itemHeight: number;
|
|
118
|
+
getItemHeight: ItemHeight;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Walks forward accumulating heights until it passes the viewport's top edge, then
|
|
122
|
+
// its bottom edge. Linear in the number of items, which is why fixed lists take
|
|
123
|
+
// the arithmetic shortcut instead.
|
|
124
|
+
const variableRange = ({ count, scrollTop, height, overscan, getItemHeight }: RangeArgs) => {
|
|
125
|
+
let accumulated = 0;
|
|
126
|
+
let startIndex = 0;
|
|
127
|
+
let endIndex = count - 1;
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < count; i++) {
|
|
130
|
+
if (accumulated + getItemHeight(i) > scrollTop) {
|
|
131
|
+
startIndex = Math.max(0, i - overscan);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
accumulated += getItemHeight(i);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
accumulated = 0;
|
|
138
|
+
for (let i = 0; i <= startIndex; i++) {
|
|
139
|
+
accumulated += getItemHeight(i);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (let i = startIndex; i < count; i++) {
|
|
143
|
+
if (accumulated > scrollTop + height) {
|
|
144
|
+
endIndex = Math.min(count - 1, i + overscan);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
accumulated += getItemHeight(i);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { startIndex, endIndex };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const fixedRange = ({ count, scrollTop, height, overscan, itemHeight }: RangeArgs) => ({
|
|
154
|
+
startIndex: Math.max(0, Math.floor(scrollTop / itemHeight) - overscan),
|
|
155
|
+
endIndex: Math.min(count - 1, Math.ceil((scrollTop + height) / itemHeight) + overscan),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const useItemOffset = (
|
|
159
|
+
variant: ListVariant,
|
|
160
|
+
itemHeight: number,
|
|
161
|
+
getItemHeight: ItemHeight,
|
|
162
|
+
): ItemHeight =>
|
|
163
|
+
useCallback(
|
|
164
|
+
(index: number): number => {
|
|
165
|
+
if (variant === 'fixed') {
|
|
166
|
+
return index * itemHeight;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let offset = 0;
|
|
170
|
+
for (let i = 0; i < index; i++) {
|
|
171
|
+
offset += getItemHeight(i);
|
|
172
|
+
}
|
|
173
|
+
return offset;
|
|
174
|
+
},
|
|
175
|
+
[variant, itemHeight, getItemHeight],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
interface ListArgs {
|
|
179
|
+
items: ListItems;
|
|
180
|
+
variant: ListVariant;
|
|
181
|
+
height: number;
|
|
182
|
+
itemHeight: number;
|
|
183
|
+
estimatedItemHeight: number;
|
|
184
|
+
overscan: number;
|
|
185
|
+
onScroll: VirtualListProps['onScroll'];
|
|
186
|
+
scrollContainerRef: VirtualListProps['scrollContainerRef'];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const useVirtualList = ({
|
|
190
|
+
items,
|
|
191
|
+
variant,
|
|
192
|
+
height,
|
|
193
|
+
itemHeight,
|
|
194
|
+
estimatedItemHeight,
|
|
195
|
+
overscan,
|
|
196
|
+
onScroll,
|
|
197
|
+
scrollContainerRef,
|
|
198
|
+
}: ListArgs) => {
|
|
199
|
+
const getItemHeight = useItemHeight({ items, variant, itemHeight, estimatedItemHeight });
|
|
200
|
+
const getItemOffset = useItemOffset(variant, itemHeight, getItemHeight);
|
|
201
|
+
const totalHeight = useTotalHeight(items, variant, itemHeight, getItemHeight);
|
|
202
|
+
|
|
203
|
+
const report = useCallback((top: number) => onScroll?.(top), [onScroll]);
|
|
204
|
+
const { scrollTop, handleScroll } = useScrollTop(scrollContainerRef, report);
|
|
205
|
+
|
|
206
|
+
const visibleItems = useMemo(() => {
|
|
207
|
+
const args = { count: items.length, scrollTop, height, overscan, itemHeight, getItemHeight };
|
|
208
|
+
const { startIndex, endIndex } = variant === 'fixed' ? fixedRange(args) : variableRange(args);
|
|
209
|
+
const result = [];
|
|
210
|
+
|
|
211
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
212
|
+
const item = items[i];
|
|
213
|
+
if (!item) continue;
|
|
214
|
+
|
|
215
|
+
result.push({
|
|
216
|
+
item,
|
|
217
|
+
index: i,
|
|
218
|
+
style: {
|
|
219
|
+
position: 'absolute' as const,
|
|
220
|
+
top: getItemOffset(i),
|
|
221
|
+
left: 0,
|
|
222
|
+
width: '100%',
|
|
223
|
+
height: getItemHeight(i),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
}, [items, scrollTop, height, overscan, variant, itemHeight, getItemOffset, getItemHeight]);
|
|
230
|
+
|
|
231
|
+
return { totalHeight, visibleItems, handleScroll };
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
interface GridArgs {
|
|
235
|
+
items: VirtualGridProps['items'];
|
|
236
|
+
height: number;
|
|
237
|
+
width: number | string;
|
|
238
|
+
columnCount: number;
|
|
239
|
+
rowHeight: number;
|
|
240
|
+
columnWidth?: number;
|
|
241
|
+
gap: number;
|
|
242
|
+
overscan: number;
|
|
243
|
+
onScroll: VirtualGridProps['onScroll'];
|
|
244
|
+
scrollContainerRef: VirtualGridProps['scrollContainerRef'];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const useColumnWidth = (
|
|
248
|
+
columnWidth: number | undefined,
|
|
249
|
+
width: number | string,
|
|
250
|
+
columnCount: number,
|
|
251
|
+
gap: number,
|
|
252
|
+
) =>
|
|
253
|
+
useMemo(() => {
|
|
254
|
+
if (columnWidth) return columnWidth;
|
|
255
|
+
|
|
256
|
+
const containerWidth = typeof width === 'number' ? width : 300; // fallback
|
|
257
|
+
return (containerWidth - (columnCount - 1) * gap) / columnCount;
|
|
258
|
+
}, [columnWidth, width, columnCount, gap]);
|
|
259
|
+
|
|
260
|
+
export const useVirtualGrid = ({
|
|
261
|
+
items,
|
|
262
|
+
height,
|
|
263
|
+
width,
|
|
264
|
+
columnCount,
|
|
265
|
+
rowHeight,
|
|
266
|
+
columnWidth,
|
|
267
|
+
gap,
|
|
268
|
+
overscan,
|
|
269
|
+
onScroll,
|
|
270
|
+
scrollContainerRef,
|
|
271
|
+
}: GridArgs) => {
|
|
272
|
+
const rowCount = Math.ceil(items.length / columnCount);
|
|
273
|
+
const totalHeight = rowCount * (rowHeight + gap) - gap;
|
|
274
|
+
const computedColumnWidth = useColumnWidth(columnWidth, width, columnCount, gap);
|
|
275
|
+
|
|
276
|
+
const report = useCallback(
|
|
277
|
+
(top: number, left: number) => onScroll?.(top, left),
|
|
278
|
+
[onScroll],
|
|
279
|
+
);
|
|
280
|
+
const { scrollTop, handleScroll } = useScrollTop(scrollContainerRef, report);
|
|
281
|
+
|
|
282
|
+
const visibleItems = useMemo(() => {
|
|
283
|
+
const pitch = rowHeight + gap;
|
|
284
|
+
const startRow = Math.max(0, Math.floor(scrollTop / pitch) - overscan);
|
|
285
|
+
const endRow = Math.min(rowCount - 1, Math.ceil((scrollTop + height) / pitch) + overscan);
|
|
286
|
+
|
|
287
|
+
// Rows are contiguous in `items`, so the visible block is one index range
|
|
288
|
+
// rather than a row loop wrapping a column loop.
|
|
289
|
+
const firstIndex = startRow * columnCount;
|
|
290
|
+
const lastIndex = Math.min((endRow + 1) * columnCount - 1, items.length - 1);
|
|
291
|
+
const result = [];
|
|
292
|
+
|
|
293
|
+
for (let index = firstIndex; index <= lastIndex; index++) {
|
|
294
|
+
const item = items[index];
|
|
295
|
+
if (!item) continue;
|
|
296
|
+
|
|
297
|
+
const rowIndex = Math.floor(index / columnCount);
|
|
298
|
+
const columnIndex = index % columnCount;
|
|
299
|
+
|
|
300
|
+
result.push({
|
|
301
|
+
item,
|
|
302
|
+
index,
|
|
303
|
+
columnIndex,
|
|
304
|
+
rowIndex,
|
|
305
|
+
style: {
|
|
306
|
+
position: 'absolute' as const,
|
|
307
|
+
top: rowIndex * pitch,
|
|
308
|
+
left: columnIndex * (computedColumnWidth + gap),
|
|
309
|
+
width: computedColumnWidth,
|
|
310
|
+
height: rowHeight,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return result;
|
|
316
|
+
}, [items, scrollTop, height, overscan, rowCount, columnCount, rowHeight, gap, computedColumnWidth]);
|
|
317
|
+
|
|
318
|
+
return { totalHeight, visibleItems, handleScroll };
|
|
319
|
+
};
|
|
@@ -40,11 +40,12 @@ const generateItems = (count: number): VirtualListItem[] => Array.from({ length:
|
|
|
40
40
|
|
|
41
41
|
const generateVariableItems = (count: number): VirtualListItem[] => Array.from({ length: count }, (_, i) => ({
|
|
42
42
|
id: i,
|
|
43
|
-
|
|
43
|
+
// Cycles 60/95/130/165 so the mix of heights is fixed per run.
|
|
44
|
+
height: 60 + (i % 4) * 35,
|
|
44
45
|
data: {
|
|
45
46
|
name: `Variable Item ${i + 1}`,
|
|
46
47
|
description: `This item has a variable height. ${
|
|
47
|
-
|
|
48
|
+
i % 2 === 0
|
|
48
49
|
? 'It contains additional content that makes it taller than other items in the list.'
|
|
49
50
|
: 'Short description.'
|
|
50
51
|
}`,
|
|
@@ -52,6 +53,22 @@ const generateVariableItems = (count: number): VirtualListItem[] => Array.from({
|
|
|
52
53
|
},
|
|
53
54
|
}));
|
|
54
55
|
|
|
56
|
+
// A virtual list's job is to swap which items are mounted as the container
|
|
57
|
+
// scrolls, so these tests assert on the rendered window. The raw scrollTop they
|
|
58
|
+
// checked before depends on the viewport and says nothing about virtualisation.
|
|
59
|
+
const renderedIndices = (root: HTMLElement): number[] =>
|
|
60
|
+
Array.from(root.querySelectorAll('[data-testid^="virtual-item-"]')).map((element) =>
|
|
61
|
+
Number((element.getAttribute('data-testid') ?? '').replace('virtual-item-', '')),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const firstRenderedIndex = (root: HTMLElement): number => {
|
|
65
|
+
const indices = renderedIndices(root);
|
|
66
|
+
return indices.length > 0 ? Math.min(...indices) : -1;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Larger than any fixture's total height, so the container lands at its end.
|
|
70
|
+
const SCROLL_TO_END = 50_000;
|
|
71
|
+
|
|
55
72
|
const SimpleItemRenderer = ({
|
|
56
73
|
item,
|
|
57
74
|
index,
|
|
@@ -147,7 +164,7 @@ export const BasicInteraction: Story = {
|
|
|
147
164
|
|
|
148
165
|
// Test that scroll callback is called
|
|
149
166
|
await waitFor(() => {
|
|
150
|
-
expect(virtualList
|
|
167
|
+
expect(firstRenderedIndex(virtualList)).toBeGreaterThan(0);
|
|
151
168
|
});
|
|
152
169
|
},
|
|
153
170
|
};
|
|
@@ -193,18 +210,19 @@ export const GridInteraction: Story = {
|
|
|
193
210
|
// Test scrolling in grid
|
|
194
211
|
fireEvent.scroll(virtualGrid, { target: { scrollTop: 300 } });
|
|
195
212
|
await waitFor(() => {
|
|
196
|
-
expect(virtualGrid
|
|
213
|
+
expect(firstRenderedIndex(virtualGrid)).toBeGreaterThan(0);
|
|
197
214
|
});
|
|
198
215
|
},
|
|
199
216
|
};
|
|
200
217
|
|
|
201
218
|
// Form Interaction Tests (Scroll behaviors)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
// The read-out was a plain `let` mutated from the scroll callback, so it never
|
|
220
|
+
// re-rendered and always showed 0px.
|
|
221
|
+
const ScrollInteractionDemo: React.FC = () => {
|
|
222
|
+
const items = React.useMemo(() => generateItems(1000), []);
|
|
223
|
+
const [scrollPosition, setScrollPosition] = React.useState(0);
|
|
206
224
|
|
|
207
|
-
|
|
225
|
+
return (
|
|
208
226
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
209
227
|
<Paper sx={{ width: 400, height: 300 }}>
|
|
210
228
|
<VirtualList
|
|
@@ -212,9 +230,7 @@ export const ScrollInteraction: Story = {
|
|
|
212
230
|
variant="fixed"
|
|
213
231
|
height={300}
|
|
214
232
|
itemHeight={50}
|
|
215
|
-
onScroll={
|
|
216
|
-
scrollPosition = scrollTop;
|
|
217
|
-
}}
|
|
233
|
+
onScroll={setScrollPosition}
|
|
218
234
|
renderItem={SimpleItemRenderer}
|
|
219
235
|
data-testid="scrollable-list"
|
|
220
236
|
/>
|
|
@@ -223,34 +239,37 @@ export const ScrollInteraction: Story = {
|
|
|
223
239
|
<Typography variant="body2" data-testid="scroll-info">
|
|
224
240
|
Scroll Position: {Math.round(scrollPosition)}px
|
|
225
241
|
</Typography>
|
|
226
|
-
</Box>
|
|
227
242
|
</Box>
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
</Box>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const ScrollInteraction: Story = {
|
|
248
|
+
render: () => <ScrollInteractionDemo />,
|
|
230
249
|
play: async ({ canvasElement }) => {
|
|
231
250
|
const canvas = within(canvasElement);
|
|
232
251
|
const scrollableList = canvas.getByTestId('scrollable-list');
|
|
233
252
|
|
|
234
253
|
// Test initial state
|
|
235
254
|
await expect(scrollableList).toBeInTheDocument();
|
|
236
|
-
expect(scrollableList
|
|
255
|
+
expect(firstRenderedIndex(scrollableList)).toBe(0);
|
|
237
256
|
|
|
238
257
|
// Test scroll to middle
|
|
239
258
|
fireEvent.scroll(scrollableList, { target: { scrollTop: 500 } });
|
|
240
259
|
await waitFor(() => {
|
|
241
|
-
expect(scrollableList
|
|
260
|
+
expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
|
|
242
261
|
});
|
|
243
262
|
|
|
244
263
|
// Test scroll to bottom
|
|
245
|
-
scrollableList
|
|
264
|
+
fireEvent.scroll(scrollableList, { target: { scrollTop: SCROLL_TO_END } });
|
|
246
265
|
await waitFor(() => {
|
|
247
|
-
expect(scrollableList
|
|
266
|
+
expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
|
|
248
267
|
});
|
|
249
268
|
|
|
250
269
|
// Test scroll back to top
|
|
251
|
-
scrollableList
|
|
270
|
+
fireEvent.scroll(scrollableList, { target: { scrollTop: 0 } });
|
|
252
271
|
await waitFor(() => {
|
|
253
|
-
expect(scrollableList
|
|
272
|
+
expect(firstRenderedIndex(scrollableList)).toBe(0);
|
|
254
273
|
});
|
|
255
274
|
},
|
|
256
275
|
};
|
|
@@ -315,7 +334,7 @@ export const KeyboardNavigation: Story = {
|
|
|
315
334
|
await waitFor(
|
|
316
335
|
() => {
|
|
317
336
|
// Check the scroll position was applied
|
|
318
|
-
expect(list
|
|
337
|
+
expect(firstRenderedIndex(list)).toBeGreaterThan(0);
|
|
319
338
|
},
|
|
320
339
|
{ timeout: 2000 },
|
|
321
340
|
);
|
|
@@ -324,7 +343,7 @@ export const KeyboardNavigation: Story = {
|
|
|
324
343
|
fireEvent.scroll(list, { target: { scrollTop: 0 } });
|
|
325
344
|
await waitFor(
|
|
326
345
|
() => {
|
|
327
|
-
expect(list
|
|
346
|
+
expect(firstRenderedIndex(list)).toBe(0);
|
|
328
347
|
},
|
|
329
348
|
{ timeout: 1000 },
|
|
330
349
|
);
|
|
@@ -432,7 +451,7 @@ export const FocusManagement: Story = {
|
|
|
432
451
|
// Test external focus
|
|
433
452
|
const externalButton = canvas.getByTestId('external-button');
|
|
434
453
|
await userEvent.click(externalButton);
|
|
435
|
-
expect(externalButton).toHaveFocus();
|
|
454
|
+
await waitFor(() => expect(externalButton).toHaveFocus());
|
|
436
455
|
|
|
437
456
|
// Test tab into list
|
|
438
457
|
await userEvent.tab();
|
|
@@ -807,7 +826,7 @@ export const Performance: Story = {
|
|
|
807
826
|
fireEvent.scroll(performanceList, { target: { scrollTop: 500 } });
|
|
808
827
|
await waitFor(
|
|
809
828
|
() => {
|
|
810
|
-
expect(performanceList
|
|
829
|
+
expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
|
|
811
830
|
},
|
|
812
831
|
{ timeout: 2000 },
|
|
813
832
|
);
|
|
@@ -815,16 +834,14 @@ export const Performance: Story = {
|
|
|
815
834
|
// Test multiple scroll events
|
|
816
835
|
for (let i = 1; i <= 3; i++) {
|
|
817
836
|
fireEvent.scroll(performanceList, { target: { scrollTop: i * 200 } });
|
|
818
|
-
await
|
|
819
|
-
setTimeout(resolve, 50);
|
|
820
|
-
});
|
|
837
|
+
await waitFor(() => expect(performanceList).toBeInTheDocument());
|
|
821
838
|
}
|
|
822
839
|
|
|
823
840
|
// Verify list is still responsive
|
|
824
841
|
await expect(performanceList).toBeInTheDocument();
|
|
825
842
|
await waitFor(
|
|
826
843
|
() => {
|
|
827
|
-
expect(performanceList
|
|
844
|
+
expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
|
|
828
845
|
},
|
|
829
846
|
{ timeout: 1000 },
|
|
830
847
|
);
|
|
@@ -927,7 +944,7 @@ export const EdgeCases: Story = {
|
|
|
927
944
|
fireEvent.scroll(variableHeightList, { target: { scrollTop: 100 } });
|
|
928
945
|
await waitFor(
|
|
929
946
|
() => {
|
|
930
|
-
expect(variableHeightList
|
|
947
|
+
expect(firstRenderedIndex(variableHeightList)).toBeGreaterThan(0);
|
|
931
948
|
},
|
|
932
949
|
{ timeout: 1000 },
|
|
933
950
|
);
|
|
@@ -965,9 +982,6 @@ export const Integration: Story = {
|
|
|
965
982
|
<ListItem
|
|
966
983
|
button
|
|
967
984
|
onClick={() => {
|
|
968
|
-
// Integration with external handlers
|
|
969
|
-
// eslint-disable-next-line no-console
|
|
970
|
-
console.log('Clicked item:', item.id);
|
|
971
985
|
}}
|
|
972
986
|
>
|
|
973
987
|
<ListItemAvatar>
|
|
@@ -1008,9 +1022,6 @@ export const Integration: Story = {
|
|
|
1008
1022
|
},
|
|
1009
1023
|
}}
|
|
1010
1024
|
onClick={() => {
|
|
1011
|
-
// Integration with external handlers
|
|
1012
|
-
// eslint-disable-next-line no-console
|
|
1013
|
-
console.log('Clicked grid item:', item.id);
|
|
1014
1025
|
}}
|
|
1015
1026
|
>
|
|
1016
1027
|
<Typography variant="body2" color="white">
|
|
@@ -1067,8 +1078,8 @@ export const Integration: Story = {
|
|
|
1067
1078
|
fireEvent.scroll(integrationGrid, { target: { scrollTop: 200 } });
|
|
1068
1079
|
|
|
1069
1080
|
await waitFor(() => {
|
|
1070
|
-
expect(integrationList
|
|
1071
|
-
expect(integrationGrid
|
|
1081
|
+
expect(firstRenderedIndex(integrationList)).toBeGreaterThan(0);
|
|
1082
|
+
expect(firstRenderedIndex(integrationGrid)).toBeGreaterThan(0);
|
|
1072
1083
|
});
|
|
1073
1084
|
},
|
|
1074
1085
|
};
|