@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,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
import { type SortFieldDefinition } from "../../layout/ContentToolbar";
|
|
6
|
+
|
|
7
|
+
import { GridShell } from "./data-views-grid-parts";
|
|
8
|
+
import type { DataViewCardSelection } from "./data-views-types";
|
|
9
|
+
import {
|
|
10
|
+
type DataViewColumn,
|
|
11
|
+
type DataViewServer,
|
|
12
|
+
type DataViewState,
|
|
13
|
+
type DataViewSyncState,
|
|
14
|
+
type FilterFieldConfig,
|
|
15
|
+
type RangeFieldConfig,
|
|
16
|
+
type RowAction,
|
|
17
|
+
} from "./data-views-types";
|
|
18
|
+
import { useDataViewsState } from "./use-data-views-state";
|
|
19
|
+
|
|
20
|
+
interface DataViewsGridProps<T extends Record<string, unknown>> {
|
|
21
|
+
rows: T[];
|
|
22
|
+
columns: DataViewColumn<T>[];
|
|
23
|
+
fields: FilterFieldConfig<T>[];
|
|
24
|
+
/** Optional numeric min/max range filters (advanced "filter by amount"). */
|
|
25
|
+
rangeFields?: RangeFieldConfig<T>[];
|
|
26
|
+
getRowId: (row: T) => string | number;
|
|
27
|
+
/** Row click opens the row; clicks in the actions column are ignored. */
|
|
28
|
+
onRowClick?: (row: T) => void;
|
|
29
|
+
emptyState?: React.ReactNode;
|
|
30
|
+
dataTestId?: string;
|
|
31
|
+
testIdPrefix?: string;
|
|
32
|
+
/** Seed/replace the view state. A NEW reference re-applies (used to apply a saved view). */
|
|
33
|
+
appliedState?: DataViewState;
|
|
34
|
+
/**
|
|
35
|
+
* URL-driven controls (search/pills/ranges/sort) that RE-APPLY on reference
|
|
36
|
+
* change while preserving column visibility — for server-mode lists that keep
|
|
37
|
+
* their filter/sort/page in the address bar (see {@link DataViewSyncState}).
|
|
38
|
+
*/
|
|
39
|
+
syncState?: DataViewSyncState;
|
|
40
|
+
/** Notified on every view-state change (so a save-view modal can read it). */
|
|
41
|
+
onStateChange?: (state: DataViewState) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Notified with the currently-visible (search + filter matched) rows whenever
|
|
44
|
+
* they change — so a page-level Export exports what the user sees, not the
|
|
45
|
+
* full dataset.
|
|
46
|
+
*/
|
|
47
|
+
onVisibleRowsChange?: (rows: T[]) => void;
|
|
48
|
+
/** Saved-views UI injected into the toolbar (FUT-89). */
|
|
49
|
+
toolbarRightSlot?: React.ReactNode;
|
|
50
|
+
/** Big page title in the header row (next to the funnel + gear). */
|
|
51
|
+
title?: string;
|
|
52
|
+
/** Primary page actions rendered at the header's right. */
|
|
53
|
+
headerActions?: React.ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Sort options for the toolbar's "Sort By" dropdown. Defaults to the sortable
|
|
56
|
+
* columns (asc/desc each) when omitted.
|
|
57
|
+
*/
|
|
58
|
+
sortFields?: SortFieldDefinition[];
|
|
59
|
+
/**
|
|
60
|
+
* A single, reusable list of row actions that drives BOTH the per-row "⋮"
|
|
61
|
+
* kebab AND the bulk-actions menu (tabwoah model). When provided, the grid
|
|
62
|
+
* appends a kebab actions column automatically (unless the page already
|
|
63
|
+
* defines one) and shows an "Ações" bulk menu when rows are selected.
|
|
64
|
+
*/
|
|
65
|
+
rowActions?: RowAction<T>[];
|
|
66
|
+
/**
|
|
67
|
+
* Extra leading control rendered in the auto kebab column, before the "⋮"
|
|
68
|
+
* (e.g. a favourite star). Only used when `rowActions` is set.
|
|
69
|
+
*/
|
|
70
|
+
rowActionsLeading?: (row: T) => React.ReactNode;
|
|
71
|
+
/**
|
|
72
|
+
* Low-level override for the selection bulk control. Prefer `rowActions`.
|
|
73
|
+
* Receives the selected rows + a `clearSelection` callback.
|
|
74
|
+
*/
|
|
75
|
+
bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;
|
|
76
|
+
/**
|
|
77
|
+
* Render a bespoke per-row menu in the actions column instead of the auto
|
|
78
|
+
* kebab — typically an entity's self-contained 3-dots menu that owns its own
|
|
79
|
+
* popups, so the SAME menu component drives both the table row and the card.
|
|
80
|
+
* `rowActions`/`bulkActions` still drive the multi-row bulk menu.
|
|
81
|
+
*/
|
|
82
|
+
renderRowMenu?: (row: T) => React.ReactNode;
|
|
83
|
+
/**
|
|
84
|
+
* Opt-in "Grade" (cards) layout. When provided, a Grade/Tabela toggle appears in
|
|
85
|
+
* the toolbar and the cards body renders each visible row via this function
|
|
86
|
+
* (reusing the same search/filter/sort). Omit ⇒ table only (unchanged).
|
|
87
|
+
*/
|
|
88
|
+
renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;
|
|
89
|
+
/** Which layout to show first when cards are available (default "table"). */
|
|
90
|
+
defaultLayout?: "cards" | "table";
|
|
91
|
+
/**
|
|
92
|
+
* Opt into the responsive inline filter UX (collapsible filter row on wide
|
|
93
|
+
* screens, modal on narrow). Default `false` keeps the classic slide-in panel.
|
|
94
|
+
*/
|
|
95
|
+
inlineFilters?: boolean;
|
|
96
|
+
/** Keep the inline search visible with no filter fields and no active search. */
|
|
97
|
+
alwaysShowSearch?: boolean;
|
|
98
|
+
/** Server-mode wiring; when set the grid is backend-driven (no client filter/sort/paginate). */
|
|
99
|
+
server?: DataViewServer;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The reusable admin DataViews table (FUT-88): the shared ContentToolbar +
|
|
104
|
+
* TableFilter panel over `@12-apps/ui` DataGrid. State + derived data live in
|
|
105
|
+
* {@link useDataViewsState}; the render lives in `GridShell`. This component just
|
|
106
|
+
* connects the two. Reused across every admin list page.
|
|
107
|
+
*/
|
|
108
|
+
export function DataViewsGrid<T extends Record<string, unknown>>({
|
|
109
|
+
rows,
|
|
110
|
+
columns,
|
|
111
|
+
fields,
|
|
112
|
+
rangeFields = [],
|
|
113
|
+
getRowId,
|
|
114
|
+
onRowClick,
|
|
115
|
+
emptyState,
|
|
116
|
+
dataTestId,
|
|
117
|
+
testIdPrefix = "table",
|
|
118
|
+
appliedState,
|
|
119
|
+
syncState,
|
|
120
|
+
onStateChange,
|
|
121
|
+
onVisibleRowsChange,
|
|
122
|
+
toolbarRightSlot,
|
|
123
|
+
sortFields,
|
|
124
|
+
rowActions,
|
|
125
|
+
rowActionsLeading,
|
|
126
|
+
bulkActions,
|
|
127
|
+
renderRowMenu,
|
|
128
|
+
renderCard,
|
|
129
|
+
defaultLayout,
|
|
130
|
+
inlineFilters,
|
|
131
|
+
alwaysShowSearch,
|
|
132
|
+
server,
|
|
133
|
+
}: DataViewsGridProps<T>): React.JSX.Element {
|
|
134
|
+
const controller = useDataViewsState({
|
|
135
|
+
rows,
|
|
136
|
+
columns,
|
|
137
|
+
fields,
|
|
138
|
+
rangeFields,
|
|
139
|
+
getRowId,
|
|
140
|
+
onRowClick,
|
|
141
|
+
appliedState,
|
|
142
|
+
syncState,
|
|
143
|
+
onStateChange,
|
|
144
|
+
testIdPrefix,
|
|
145
|
+
sortFields,
|
|
146
|
+
rowActions,
|
|
147
|
+
rowActionsLeading,
|
|
148
|
+
renderRowMenu,
|
|
149
|
+
server,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Surface the visible (filtered) rows so a page-level Export matches the view. Pages pass
|
|
153
|
+
// inline column/field arrays, so `matched` is a fresh reference on every render even when
|
|
154
|
+
// its contents are unchanged. Guard on a signature over the rows' *contents* (not just ids)
|
|
155
|
+
// so we only notify — and trigger the parent's setState — when the visible data actually
|
|
156
|
+
// changes. Ids alone are not enough: a server refresh can change a visible row's values
|
|
157
|
+
// while keeping its id, and the export must reflect those new values. Content-equal renders
|
|
158
|
+
// keep the same signature, so the effect + a parent `useState` never loop.
|
|
159
|
+
const { matched } = controller;
|
|
160
|
+
const visibleSignature = JSON.stringify(matched);
|
|
161
|
+
const lastSignature = useRef<string | null>(null);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (lastSignature.current === visibleSignature) return;
|
|
164
|
+
lastSignature.current = visibleSignature;
|
|
165
|
+
onVisibleRowsChange?.(matched);
|
|
166
|
+
}, [visibleSignature, matched, onVisibleRowsChange]);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<GridShell
|
|
170
|
+
c={controller}
|
|
171
|
+
rows={rows}
|
|
172
|
+
fields={fields}
|
|
173
|
+
rangeFields={rangeFields}
|
|
174
|
+
getRowId={getRowId}
|
|
175
|
+
testIdPrefix={testIdPrefix}
|
|
176
|
+
dataTestId={dataTestId}
|
|
177
|
+
emptyState={emptyState}
|
|
178
|
+
toolbarRightSlot={toolbarRightSlot}
|
|
179
|
+
rowActions={rowActions}
|
|
180
|
+
bulkActions={bulkActions}
|
|
181
|
+
renderCard={renderCard}
|
|
182
|
+
defaultLayout={defaultLayout}
|
|
183
|
+
inlineFilters={inlineFilters}
|
|
184
|
+
alwaysShowSearch={alwaysShowSearch}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { SortFieldDefinition } from "../../layout/ContentToolbar";
|
|
4
|
+
import { useMemo, useRef, useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { DataViewsGrid } from "./DataViewsGrid";
|
|
7
|
+
import { ViewDialogs, ViewMutationErrorAlert, ViewsMenuSlot } from "./data-views-table-parts";
|
|
8
|
+
import { useViewMutations, type ViewMutations } from "./data-views-view-mutations";
|
|
9
|
+
import {
|
|
10
|
+
coerceViewState,
|
|
11
|
+
emptyViewState,
|
|
12
|
+
type DataViewColumn,
|
|
13
|
+
type DataViewServer,
|
|
14
|
+
type DataViewState,
|
|
15
|
+
type DataViewSyncState,
|
|
16
|
+
type DataViewPersistence,
|
|
17
|
+
type DataViewRouter,
|
|
18
|
+
type FilterFieldConfig,
|
|
19
|
+
type RangeFieldConfig,
|
|
20
|
+
type DataViewCardSelection,
|
|
21
|
+
type RowAction,
|
|
22
|
+
type SavedViewSummary,
|
|
23
|
+
} from "./data-views-types";
|
|
24
|
+
import { type SaveViewPayload } from "./SaveViewModal";
|
|
25
|
+
|
|
26
|
+
export interface DataViewsTableBaseProps<T extends Record<string, unknown>> {
|
|
27
|
+
/** The applied-view id read from the URL (`?view=`) on load, or null. */
|
|
28
|
+
initialViewId?: string | null;
|
|
29
|
+
/** Injected saved-view persistence (create/update/delete). */
|
|
30
|
+
persistence: DataViewPersistence;
|
|
31
|
+
/** Injected router side-effects (URL sync + refresh). */
|
|
32
|
+
router: DataViewRouter;
|
|
33
|
+
rows: T[];
|
|
34
|
+
columns: DataViewColumn<T>[];
|
|
35
|
+
fields: FilterFieldConfig<T>[];
|
|
36
|
+
/** Optional numeric min/max range filters (advanced "filter by amount"). */
|
|
37
|
+
rangeFields?: RangeFieldConfig<T>[];
|
|
38
|
+
getRowId: (row: T) => string | number;
|
|
39
|
+
onRowClick?: (row: T) => void;
|
|
40
|
+
emptyState?: React.ReactNode;
|
|
41
|
+
dataTestId?: string;
|
|
42
|
+
testIdPrefix?: string;
|
|
43
|
+
/** The user's + shared views for this table (from the server). */
|
|
44
|
+
views: SavedViewSummary[];
|
|
45
|
+
/** Big page title in the table header, beside the funnel + gear (GTA layout). */
|
|
46
|
+
title?: string;
|
|
47
|
+
/** Primary page actions (e.g. "Novo produto") rendered at the header's right. */
|
|
48
|
+
headerActions?: React.ReactNode;
|
|
49
|
+
/** Sort options for the toolbar's "Sort By" dropdown (defaults to sortable columns). */
|
|
50
|
+
sortFields?: SortFieldDefinition[];
|
|
51
|
+
/** Notified with the currently-visible (filtered) rows, so Export matches the view. */
|
|
52
|
+
onVisibleRowsChange?: (rows: T[]) => void;
|
|
53
|
+
/** Reusable row actions driving both the row "⋮" kebab and the bulk menu. */
|
|
54
|
+
rowActions?: RowAction<T>[];
|
|
55
|
+
/** Leading control in the auto kebab column (e.g. a favourite star). */
|
|
56
|
+
rowActionsLeading?: (row: T) => React.ReactNode;
|
|
57
|
+
/** Low-level bulk-action override; prefer `rowActions`. */
|
|
58
|
+
bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;
|
|
59
|
+
/** Bespoke per-row menu (an entity's self-contained 3-dots menu) instead of the auto kebab. */
|
|
60
|
+
renderRowMenu?: (row: T) => React.ReactNode;
|
|
61
|
+
/** Opt-in "Grade" (cards) layout — a card renderer per row; adds a Grade/Tabela toggle. */
|
|
62
|
+
renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;
|
|
63
|
+
/** Which layout to show first when cards are available (default "table"). */
|
|
64
|
+
defaultLayout?: "cards" | "table";
|
|
65
|
+
/** Opt into the responsive inline filter UX (collapsible row / modal). Default keeps the panel. */
|
|
66
|
+
inlineFilters?: boolean;
|
|
67
|
+
/** Keep the inline search visible with no filter fields and no active search. */
|
|
68
|
+
alwaysShowSearch?: boolean;
|
|
69
|
+
/** Server-mode wiring (FUT-180): backend-driven rows/total + re-fetch. Omit for client mode. */
|
|
70
|
+
server?: DataViewServer;
|
|
71
|
+
/** Seed the initial view state (search/pills/ranges/sort) from the URL when no saved view applies. */
|
|
72
|
+
initialState?: DataViewState;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Derive the grid's reactive `syncState` from the URL-seeded `initialState` in
|
|
77
|
+
* server mode so browser back/forward RE-APPLIES the search/pills/sort controls
|
|
78
|
+
* (merging over live state, preserving hidden columns — see {@link DataViewSyncState}).
|
|
79
|
+
* Client-mode tables (no `server`) get `undefined`. Callers memoize `initialState`
|
|
80
|
+
* on the URL, so this reference only changes on genuine navigations.
|
|
81
|
+
*/
|
|
82
|
+
function useUrlSyncState(
|
|
83
|
+
server: DataViewServer | undefined,
|
|
84
|
+
initialState: DataViewState | undefined,
|
|
85
|
+
): DataViewSyncState | undefined {
|
|
86
|
+
const isServerMode = server !== undefined;
|
|
87
|
+
return useMemo<DataViewSyncState | undefined>(
|
|
88
|
+
() =>
|
|
89
|
+
isServerMode && initialState
|
|
90
|
+
? {
|
|
91
|
+
search: initialState.search,
|
|
92
|
+
pills: initialState.pills,
|
|
93
|
+
ranges: initialState.ranges,
|
|
94
|
+
sortBy: initialState.sortBy,
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
[isServerMode, initialState],
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the initial applied view: a `?view=<id>` shortcut (FUT-90) wins, else
|
|
103
|
+
* the user's default view, else none. Drives the seed state + trigger label.
|
|
104
|
+
*/
|
|
105
|
+
function resolveInitialView(
|
|
106
|
+
views: SavedViewSummary[],
|
|
107
|
+
requestedId: string | null,
|
|
108
|
+
): SavedViewSummary | undefined {
|
|
109
|
+
const requested = requestedId ? views.find((view) => view.id === requestedId) : undefined;
|
|
110
|
+
return requested ?? views.find((view) => view.isDefault);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface SavedViewsController {
|
|
114
|
+
applied: DataViewState;
|
|
115
|
+
/** Name of the currently-applied saved view, if any (drives the trigger label). */
|
|
116
|
+
activeViewName?: string;
|
|
117
|
+
/** Reset to the built-in no-filter "Main vision" default (clears the URL param). */
|
|
118
|
+
selectMain: () => void;
|
|
119
|
+
currentRef: React.MutableRefObject<DataViewState>;
|
|
120
|
+
saveOpen: boolean;
|
|
121
|
+
editing: SavedViewSummary | null;
|
|
122
|
+
manageOpen: boolean;
|
|
123
|
+
setSaveOpen: (open: boolean) => void;
|
|
124
|
+
setEditing: (view: SavedViewSummary | null) => void;
|
|
125
|
+
setManageOpen: (open: boolean) => void;
|
|
126
|
+
applyView: (view: SavedViewSummary) => void;
|
|
127
|
+
openCreate: () => void;
|
|
128
|
+
openEdit: (view: SavedViewSummary) => void;
|
|
129
|
+
/** Close the save modal and clear the editing target. */
|
|
130
|
+
closeSave: () => void;
|
|
131
|
+
/** Close the manage dialog and open the edit modal for `view`. */
|
|
132
|
+
editFromManage: (view: SavedViewSummary) => void;
|
|
133
|
+
patchView: ViewMutations["patchView"];
|
|
134
|
+
handleDelete: ViewMutations["handleDelete"];
|
|
135
|
+
handleSave: (payload: SaveViewPayload) => Promise<void>;
|
|
136
|
+
/** Last pin/share/default/delete failure — shown as an Alert, never silent. */
|
|
137
|
+
mutationError: string | null;
|
|
138
|
+
clearMutationError: () => void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface AppliedView {
|
|
142
|
+
applied: DataViewState;
|
|
143
|
+
currentRef: React.MutableRefObject<DataViewState>;
|
|
144
|
+
activeViewName?: string;
|
|
145
|
+
applyView: (view: SavedViewSummary) => void;
|
|
146
|
+
selectMain: () => void;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The applied grid state + which saved view it came from, kept in sync with the
|
|
151
|
+
* URL (`?view=<id>`) so a refresh restores it. "Main vision" resets + clears it.
|
|
152
|
+
*/
|
|
153
|
+
function useAppliedView(
|
|
154
|
+
router: DataViewRouter,
|
|
155
|
+
columnIds: string[],
|
|
156
|
+
views: SavedViewSummary[],
|
|
157
|
+
initialView: SavedViewSummary | undefined,
|
|
158
|
+
initialState: DataViewState | undefined,
|
|
159
|
+
): AppliedView {
|
|
160
|
+
// A saved view wins; otherwise seed from the host-supplied initial state (e.g.
|
|
161
|
+
// filters/sort read off the URL, FUT-180) so a bookmarked filtered URL is
|
|
162
|
+
// reflected in the grid — column visibility isn't URL-carried, so show all.
|
|
163
|
+
const seed = initialView
|
|
164
|
+
? coerceViewState(initialView.state, columnIds)
|
|
165
|
+
: initialState
|
|
166
|
+
? { ...initialState, visibleColumns: columnIds }
|
|
167
|
+
: emptyViewState(columnIds);
|
|
168
|
+
const [applied, setApplied] = useState<DataViewState>(seed);
|
|
169
|
+
const [appliedViewId, setAppliedViewId] = useState<string | null>(initialView?.id ?? null);
|
|
170
|
+
const currentRef = useRef<DataViewState>(seed);
|
|
171
|
+
|
|
172
|
+
const applyView = (view: SavedViewSummary): void => {
|
|
173
|
+
setApplied(coerceViewState(view.state, columnIds));
|
|
174
|
+
setAppliedViewId(view.id);
|
|
175
|
+
router.syncViewParam(view.id);
|
|
176
|
+
};
|
|
177
|
+
const selectMain = (): void => {
|
|
178
|
+
setApplied(emptyViewState(columnIds));
|
|
179
|
+
setAppliedViewId(null);
|
|
180
|
+
router.syncViewParam(null);
|
|
181
|
+
};
|
|
182
|
+
const activeViewName = views.find((view) => view.id === appliedViewId)?.name;
|
|
183
|
+
return { applied, currentRef, activeViewName, applyView, selectMain };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Owns the saved-views state + persistence for {@link DataViewsTable}: the applied
|
|
188
|
+
* grid state, the live-state ref, the modal/dialog flags, and the create/edit/
|
|
189
|
+
* delete/patch handlers. Extracted so the component render stays within budget.
|
|
190
|
+
*/
|
|
191
|
+
function useSavedViewsController(
|
|
192
|
+
persistence: DataViewPersistence,
|
|
193
|
+
router: DataViewRouter,
|
|
194
|
+
columnIds: string[],
|
|
195
|
+
views: SavedViewSummary[],
|
|
196
|
+
initialView: SavedViewSummary | undefined,
|
|
197
|
+
initialState: DataViewState | undefined,
|
|
198
|
+
): SavedViewsController {
|
|
199
|
+
const { applied, currentRef, activeViewName, applyView, selectMain } =
|
|
200
|
+
useAppliedView(router, columnIds, views, initialView, initialState);
|
|
201
|
+
const [saveOpen, setSaveOpen] = useState(false);
|
|
202
|
+
const [editing, setEditing] = useState<SavedViewSummary | null>(null);
|
|
203
|
+
const [manageOpen, setManageOpen] = useState(false);
|
|
204
|
+
|
|
205
|
+
const openCreate = (): void => {
|
|
206
|
+
setEditing(null);
|
|
207
|
+
setSaveOpen(true);
|
|
208
|
+
};
|
|
209
|
+
const openEdit = (view: SavedViewSummary): void => {
|
|
210
|
+
setEditing(view);
|
|
211
|
+
setSaveOpen(true);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const [mutationError, setMutationError] = useState<string | null>(null);
|
|
215
|
+
const refreshAfter = (ok: boolean, error?: string): void => {
|
|
216
|
+
if (ok) {
|
|
217
|
+
setMutationError(null);
|
|
218
|
+
router.refresh();
|
|
219
|
+
} else {
|
|
220
|
+
// A rejected pin/share/default/delete must be visible (FUT-100) — the
|
|
221
|
+
// menus close on click, so an inline Alert is the only surviving signal.
|
|
222
|
+
setMutationError(error ?? "Não foi possível salvar a alteração da visão.");
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const { handleSave: saveView, patchView, handleDelete } = useViewMutations(
|
|
227
|
+
persistence,
|
|
228
|
+
editing,
|
|
229
|
+
currentRef,
|
|
230
|
+
refreshAfter,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const handleSave = async (payload: SaveViewPayload): Promise<void> => {
|
|
234
|
+
await saveView(payload);
|
|
235
|
+
setSaveOpen(false);
|
|
236
|
+
setEditing(null);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
applied,
|
|
241
|
+
activeViewName,
|
|
242
|
+
selectMain,
|
|
243
|
+
currentRef,
|
|
244
|
+
saveOpen,
|
|
245
|
+
editing,
|
|
246
|
+
manageOpen,
|
|
247
|
+
setSaveOpen,
|
|
248
|
+
setEditing,
|
|
249
|
+
setManageOpen,
|
|
250
|
+
applyView,
|
|
251
|
+
openCreate,
|
|
252
|
+
openEdit,
|
|
253
|
+
closeSave: () => {
|
|
254
|
+
setSaveOpen(false);
|
|
255
|
+
setEditing(null);
|
|
256
|
+
},
|
|
257
|
+
editFromManage: (view: SavedViewSummary) => {
|
|
258
|
+
setManageOpen(false);
|
|
259
|
+
openEdit(view);
|
|
260
|
+
},
|
|
261
|
+
patchView,
|
|
262
|
+
handleDelete,
|
|
263
|
+
handleSave,
|
|
264
|
+
mutationError,
|
|
265
|
+
clearMutationError: () => setMutationError(null),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The saved-views dropdown wired to the controller — the toolbar's right slot.
|
|
271
|
+
* Extracted so the {@link DataViewsTable} render stays within the size budget.
|
|
272
|
+
*/
|
|
273
|
+
function ViewsMenu(props: {
|
|
274
|
+
views: SavedViewSummary[];
|
|
275
|
+
ctl: SavedViewsController;
|
|
276
|
+
testIdPrefix: string;
|
|
277
|
+
}): React.JSX.Element {
|
|
278
|
+
const { views, ctl, testIdPrefix } = props;
|
|
279
|
+
return (
|
|
280
|
+
<ViewsMenuSlot
|
|
281
|
+
views={views}
|
|
282
|
+
activeViewName={ctl.activeViewName}
|
|
283
|
+
applyView={ctl.applyView}
|
|
284
|
+
selectMain={ctl.selectMain}
|
|
285
|
+
openCreate={ctl.openCreate}
|
|
286
|
+
openEdit={ctl.openEdit}
|
|
287
|
+
handleDelete={ctl.handleDelete}
|
|
288
|
+
patchView={ctl.patchView}
|
|
289
|
+
onManageAll={() => ctl.setManageOpen(true)}
|
|
290
|
+
testIdPrefix={testIdPrefix}
|
|
291
|
+
/>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The reusable admin table (FUT-89): the DataViews grid plus the full saved-views
|
|
297
|
+
* experience — a views dropdown, a save/edit modal (with preview), and a manage
|
|
298
|
+
* dialog. Framework-agnostic: saved-view persistence and router side-effects are
|
|
299
|
+
* INJECTED (`persistence`, `router`, `initialViewId`) so the host app wires them
|
|
300
|
+
* to its backend + framework router. A default view auto-applies on load; applying
|
|
301
|
+
* a view restores its filters + columns + sort. Each table supplies its own columns
|
|
302
|
+
* + filter fields; everything else is shared.
|
|
303
|
+
*/
|
|
304
|
+
export function DataViewsTableBase<T extends Record<string, unknown>>({
|
|
305
|
+
initialViewId = null,
|
|
306
|
+
persistence,
|
|
307
|
+
router,
|
|
308
|
+
rows,
|
|
309
|
+
columns,
|
|
310
|
+
fields,
|
|
311
|
+
rangeFields,
|
|
312
|
+
getRowId,
|
|
313
|
+
onRowClick,
|
|
314
|
+
emptyState,
|
|
315
|
+
dataTestId,
|
|
316
|
+
testIdPrefix = "table",
|
|
317
|
+
views,
|
|
318
|
+
title,
|
|
319
|
+
headerActions,
|
|
320
|
+
sortFields,
|
|
321
|
+
onVisibleRowsChange,
|
|
322
|
+
rowActions,
|
|
323
|
+
rowActionsLeading,
|
|
324
|
+
bulkActions,
|
|
325
|
+
renderRowMenu,
|
|
326
|
+
renderCard,
|
|
327
|
+
defaultLayout,
|
|
328
|
+
inlineFilters,
|
|
329
|
+
alwaysShowSearch,
|
|
330
|
+
server,
|
|
331
|
+
initialState,
|
|
332
|
+
}: DataViewsTableBaseProps<T>): React.JSX.Element {
|
|
333
|
+
const initialView = resolveInitialView(views, initialViewId);
|
|
334
|
+
const columnIds = columns.map((col) => col.id);
|
|
335
|
+
const ctl = useSavedViewsController(persistence, router, columnIds, views, initialView, initialState);
|
|
336
|
+
// Server-mode lists re-apply URL-derived controls on back/forward (not just the mount seed).
|
|
337
|
+
const syncState = useUrlSyncState(server, initialState);
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<>
|
|
341
|
+
<ViewMutationErrorAlert error={ctl.mutationError} onClose={ctl.clearMutationError} testIdPrefix={testIdPrefix} />
|
|
342
|
+
<DataViewsGrid<T>
|
|
343
|
+
rows={rows}
|
|
344
|
+
columns={columns}
|
|
345
|
+
fields={fields}
|
|
346
|
+
rangeFields={rangeFields}
|
|
347
|
+
getRowId={getRowId}
|
|
348
|
+
onRowClick={onRowClick}
|
|
349
|
+
emptyState={emptyState}
|
|
350
|
+
dataTestId={dataTestId}
|
|
351
|
+
testIdPrefix={testIdPrefix}
|
|
352
|
+
title={title}
|
|
353
|
+
headerActions={headerActions}
|
|
354
|
+
sortFields={sortFields}
|
|
355
|
+
rowActions={rowActions}
|
|
356
|
+
rowActionsLeading={rowActionsLeading}
|
|
357
|
+
bulkActions={bulkActions}
|
|
358
|
+
renderRowMenu={renderRowMenu}
|
|
359
|
+
renderCard={renderCard}
|
|
360
|
+
defaultLayout={defaultLayout}
|
|
361
|
+
inlineFilters={inlineFilters}
|
|
362
|
+
alwaysShowSearch={alwaysShowSearch}
|
|
363
|
+
server={server}
|
|
364
|
+
appliedState={ctl.applied}
|
|
365
|
+
syncState={syncState}
|
|
366
|
+
onStateChange={(state) => (ctl.currentRef.current = state)}
|
|
367
|
+
onVisibleRowsChange={onVisibleRowsChange}
|
|
368
|
+
toolbarRightSlot={<ViewsMenu views={views} ctl={ctl} testIdPrefix={testIdPrefix} />}
|
|
369
|
+
/>
|
|
370
|
+
{renderViewDialogs(ctl, fields, columns, views, testIdPrefix)}
|
|
371
|
+
</>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** The save + manage saved-view dialogs, split out to keep the base thin. */
|
|
376
|
+
function renderViewDialogs<T extends Record<string, unknown>>(
|
|
377
|
+
ctl: SavedViewsController,
|
|
378
|
+
fields: DataViewsTableBaseProps<T>["fields"],
|
|
379
|
+
columns: DataViewsTableBaseProps<T>["columns"],
|
|
380
|
+
views: SavedViewSummary[],
|
|
381
|
+
testIdPrefix: string,
|
|
382
|
+
): React.JSX.Element {
|
|
383
|
+
return (
|
|
384
|
+
<ViewDialogs<T>
|
|
385
|
+
saveOpen={ctl.saveOpen}
|
|
386
|
+
onCloseSave={ctl.closeSave}
|
|
387
|
+
currentState={ctl.editing ? ctl.editing.state : ctl.currentRef.current}
|
|
388
|
+
fields={fields}
|
|
389
|
+
columns={columns}
|
|
390
|
+
editing={ctl.editing}
|
|
391
|
+
onSave={ctl.handleSave}
|
|
392
|
+
manageOpen={ctl.manageOpen}
|
|
393
|
+
onCloseManage={() => ctl.setManageOpen(false)}
|
|
394
|
+
views={views}
|
|
395
|
+
onEditFromManage={ctl.editFromManage}
|
|
396
|
+
onDelete={(view) => void ctl.handleDelete(view)}
|
|
397
|
+
testIdPrefix={testIdPrefix}
|
|
398
|
+
/>
|
|
399
|
+
);
|
|
400
|
+
}
|