@12-apps/ui 6.7.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3X3JVF6E.js +438 -0
- package/dist/chunk-3X3JVF6E.js.map +1 -0
- package/dist/chunk-AL3W7TXN.js +465 -0
- package/dist/chunk-AL3W7TXN.js.map +1 -0
- package/dist/{chunk-QLBYAYUG.js → chunk-KR2BP7NK.js} +4 -4
- package/dist/chunk-KR2BP7NK.js.map +1 -0
- package/dist/{chunk-DFVRZ3UK.js → chunk-KXARS46L.js} +3 -3
- package/dist/chunk-KXARS46L.js.map +1 -0
- package/dist/{chunk-JP55QWVR.js → chunk-L7L6SX64.js} +5 -2
- package/dist/chunk-L7L6SX64.js.map +1 -0
- package/dist/{chunk-QZZVHY6U.js → chunk-PVLDKXKR.js} +7 -3
- package/dist/chunk-PVLDKXKR.js.map +1 -0
- package/dist/{chunk-H7UAGTVW.js → chunk-TSG5C7MP.js} +4 -3
- package/dist/chunk-TSG5C7MP.js.map +1 -0
- package/dist/{chunk-NUCLEOHF.js → chunk-UNM7KFDX.js} +44 -14
- package/dist/chunk-UNM7KFDX.js.map +1 -0
- package/dist/{chunk-CEKPLUQV.js → chunk-UY7UF7IN.js} +19 -13
- package/dist/chunk-UY7UF7IN.js.map +1 -0
- package/dist/data-display/DataGrid.js +1 -1
- package/dist/data-display/DataViews.js +88 -65
- package/dist/data-display/DataViews.js.map +1 -1
- package/dist/data-display/HoverCard.js +4 -1
- package/dist/data-display/HoverCard.js.map +1 -1
- package/dist/data-display/Table.js +4 -2
- package/dist/data-display/Table.js.map +1 -1
- package/dist/data-display/TimingDiagram.js +3 -2
- package/dist/data-display/TimingDiagram.js.map +1 -1
- package/dist/en-US.js +56 -0
- package/dist/en-US.js.map +1 -0
- package/dist/feedback/Sonner.js +5 -4
- package/dist/feedback/Sonner.js.map +1 -1
- package/dist/feedback/StackedModal.js +1 -1
- package/dist/feedback/TutorialOverlay.js +19 -5
- package/dist/feedback/TutorialOverlay.js.map +1 -1
- package/dist/form/AddressAutocomplete.js +16 -8
- package/dist/form/AddressAutocomplete.js.map +1 -1
- package/dist/form/Autocomplete.js +9 -7
- package/dist/form/Autocomplete.js.map +1 -1
- package/dist/form/Calendar.js +1 -1
- package/dist/form/CodeEditor.js +14 -9
- package/dist/form/CodeEditor.js.map +1 -1
- package/dist/form/DateRangePicker.js +10 -3
- package/dist/form/DateRangePicker.js.map +1 -1
- package/dist/form/Menubar.js +4 -1
- package/dist/form/Menubar.js.map +1 -1
- package/dist/form/PasswordStrength.js +48 -28
- package/dist/form/PasswordStrength.js.map +1 -1
- package/dist/form/PhoneInput.js +11 -5
- package/dist/form/PhoneInput.js.map +1 -1
- package/dist/form/SearchPalette.js +4 -1
- package/dist/form/SearchPalette.js.map +1 -1
- package/dist/form/Textarea.js +21 -19
- package/dist/form/Textarea.js.map +1 -1
- package/dist/form/UploadButton.js +16 -11
- package/dist/form/UploadButton.js.map +1 -1
- package/dist/layout/ContentToolbar.js +1 -1
- package/dist/layout/Dashboard.js +2 -1
- package/dist/layout/Dashboard.js.map +1 -1
- package/dist/layout/ScrollArea.js +9 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/TableFilter.js +1 -1
- package/dist/locales.js +171 -0
- package/dist/locales.js.map +1 -0
- package/dist/navigation/Breadcrumbs.js +9 -3
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/CommandPalette.js +20 -7
- package/dist/navigation/CommandPalette.js.map +1 -1
- package/dist/navigation/Pagination.js +1 -1
- package/dist/navigation/Tabs.js +7 -3
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/pt-BR.js +42 -274
- package/dist/pt-BR.js.map +1 -1
- package/dist/types/components/data-display/DataGrid/DataGrid.states.d.ts +1 -0
- package/dist/types/components/data-display/DataGrid/DataGrid.states.d.ts.map +1 -1
- package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts +5 -0
- package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-category-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +27 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts +12 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-filter-panel.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-pagination.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-toolbar.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.content.d.ts +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.content.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.types.d.ts +2 -0
- package/dist/types/components/data-display/HoverCard/HoverCard.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/Table.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/Table.types.d.ts +5 -0
- package/dist/types/components/data-display/Table/Table.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/TableStates.d.ts +3 -1
- package/dist/types/components/data-display/Table/TableStates.d.ts.map +1 -1
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.d.ts.map +1 -1
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.types.d.ts +3 -0
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.types.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/Sonner.d.ts +8 -0
- package/dist/types/components/feedback/Sonner/Sonner.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/Sonner.types.d.ts +2 -0
- package/dist/types/components/feedback/Sonner/Sonner.types.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/SonnerToast.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.parts.d.ts +2 -0
- package/dist/types/components/feedback/StackedModal/StackedModal.parts.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.types.d.ts +2 -0
- package/dist/types/components/feedback/StackedModal/StackedModal.types.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.types.d.ts +3 -0
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.types.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialStepBody.d.ts +2 -0
- package/dist/types/components/feedback/TutorialOverlay/TutorialStepBody.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressAutocomplete.types.d.ts +7 -0
- package/dist/types/components/form/AddressAutocomplete/AddressAutocomplete.types.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressAutocompleteField.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressFallbackField.d.ts +1 -0
- package/dist/types/components/form/AddressAutocomplete/AddressFallbackField.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressInputField.d.ts +1 -0
- package/dist/types/components/form/AddressAutocomplete/AddressInputField.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.parts.d.ts +9 -3
- package/dist/types/components/form/Autocomplete/Autocomplete.parts.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.types.d.ts +7 -0
- package/dist/types/components/form/Autocomplete/Autocomplete.types.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/Calendar.types.d.ts +6 -0
- package/dist/types/components/form/Calendar/Calendar.types.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditor.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditor.types.d.ts +7 -0
- package/dist/types/components/form/CodeEditor/CodeEditor.types.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditorToolbar.d.ts +2 -0
- package/dist/types/components/form/CodeEditor/CodeEditorToolbar.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.range.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.types.d.ts +11 -0
- package/dist/types/components/form/DateRangePicker/DateRangePicker.types.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePickerFields.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.toolbar.d.ts +1 -0
- package/dist/types/components/form/Menubar/Menubar.toolbar.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.types.d.ts +5 -0
- package/dist/types/components/form/Menubar/Menubar.types.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.helpers.d.ts +4 -2
- package/dist/types/components/form/PasswordStrength/PasswordStrength.helpers.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.types.d.ts +8 -0
- package/dist/types/components/form/PasswordStrength/PasswordStrength.types.d.ts.map +1 -1
- package/dist/types/components/form/PhoneInput/PhoneInput.d.ts.map +1 -1
- package/dist/types/components/form/PhoneInput/PhoneInput.types.d.ts +7 -0
- package/dist/types/components/form/PhoneInput/PhoneInput.types.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.parts.d.ts +2 -0
- package/dist/types/components/form/SearchPalette/SearchPalette.parts.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.types.d.ts +6 -0
- package/dist/types/components/form/SearchPalette/SearchPalette.types.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/Textarea.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/Textarea.types.d.ts +16 -1
- package/dist/types/components/form/Textarea/Textarea.types.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/TextareaRichEditor.d.ts +7 -0
- package/dist/types/components/form/Textarea/TextareaRichEditor.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.parts.d.ts +6 -3
- package/dist/types/components/form/UploadButton/UploadButton.parts.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.types.d.ts +7 -0
- package/dist/types/components/form/UploadButton/UploadButton.types.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts +12 -0
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/MultiSelectDropdown.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/MultiSelectDropdown.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/SortByDropdown.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/SortByDropdown.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ViewSelector.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/ViewSelector.d.ts.map +1 -1
- package/dist/types/components/layout/Dashboard/Dashboard.d.ts +1 -1
- package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts +4 -0
- package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts.map +1 -1
- package/dist/types/components/layout/Dashboard/DashboardParts.d.ts +1 -1
- package/dist/types/components/layout/Dashboard/DashboardParts.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollArea.types.d.ts +21 -3
- package/dist/types/components/layout/ScrollArea/ScrollArea.types.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollViewport.d.ts +2 -0
- package/dist/types/components/layout/ScrollArea/ScrollViewport.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.d.ts +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.types.d.ts +7 -0
- package/dist/types/components/layout/TableFilter/TableFilter.types.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilterContext.d.ts +9 -0
- package/dist/types/components/layout/TableFilter/TableFilterContext.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/BreadcrumbEntry.d.ts +2 -0
- package/dist/types/components/navigation/Breadcrumbs/BreadcrumbEntry.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.types.d.ts +3 -0
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.types.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPalette.types.d.ts +3 -0
- package/dist/types/components/navigation/CommandPalette/CommandPalette.types.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPaletteResults.d.ts +2 -0
- package/dist/types/components/navigation/CommandPalette/CommandPaletteResults.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/Pagination.types.d.ts +2 -0
- package/dist/types/components/navigation/Pagination/Pagination.types.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/PaginationParts.d.ts +2 -0
- package/dist/types/components/navigation/Pagination/PaginationParts.d.ts.map +1 -1
- package/dist/types/components/navigation/Tabs/Tabs.d.ts.map +1 -1
- package/dist/types/components/navigation/Tabs/Tabs.types.d.ts +2 -0
- package/dist/types/components/navigation/Tabs/Tabs.types.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.parts.d.ts +6 -2
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.parts.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.types.d.ts +6 -0
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.types.d.ts.map +1 -1
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.d.ts +4 -2
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.d.ts.map +1 -1
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.types.d.ts +8 -7
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.types.d.ts.map +1 -1
- package/dist/types/copy/data-display.d.ts +56 -0
- package/dist/types/copy/data-display.d.ts.map +1 -0
- package/dist/types/copy/feedback.d.ts +38 -0
- package/dist/types/copy/feedback.d.ts.map +1 -0
- package/dist/types/copy/form.d.ts +146 -0
- package/dist/types/copy/form.d.ts.map +1 -0
- package/dist/types/copy/layout.d.ts +18 -0
- package/dist/types/copy/layout.d.ts.map +1 -0
- package/dist/types/copy/navigation.d.ts +24 -0
- package/dist/types/copy/navigation.d.ts.map +1 -0
- package/dist/types/copy/shared.d.ts +119 -0
- package/dist/types/copy/shared.d.ts.map +1 -0
- package/dist/types/copy/utility.d.ts +35 -0
- package/dist/types/copy/utility.d.ts.map +1 -0
- package/dist/types/copy.d.ts +12 -165
- package/dist/types/copy.d.ts.map +1 -1
- package/dist/types/en-US.d.ts +24 -0
- package/dist/types/en-US.d.ts.map +1 -0
- package/dist/types/en-US.data-display.d.ts +13 -0
- package/dist/types/en-US.data-display.d.ts.map +1 -0
- package/dist/types/en-US.feedback.d.ts +8 -0
- package/dist/types/en-US.feedback.d.ts.map +1 -0
- package/dist/types/en-US.form.d.ts +13 -0
- package/dist/types/en-US.form.d.ts.map +1 -0
- package/dist/types/en-US.layout.d.ts +7 -0
- package/dist/types/en-US.layout.d.ts.map +1 -0
- package/dist/types/en-US.navigation.d.ts +8 -0
- package/dist/types/en-US.navigation.d.ts.map +1 -0
- package/dist/types/en-US.shared.d.ts +26 -0
- package/dist/types/en-US.shared.d.ts.map +1 -0
- package/dist/types/en-US.utility.d.ts +8 -0
- package/dist/types/en-US.utility.d.ts.map +1 -0
- package/dist/types/locales.d.ts +53 -0
- package/dist/types/locales.d.ts.map +1 -0
- package/dist/types/pt-BR.d.ts +15 -22
- package/dist/types/pt-BR.d.ts.map +1 -1
- package/dist/types/pt-BR.data-display.d.ts +13 -0
- package/dist/types/pt-BR.data-display.d.ts.map +1 -0
- package/dist/types/pt-BR.feedback.d.ts +8 -0
- package/dist/types/pt-BR.feedback.d.ts.map +1 -0
- package/dist/types/pt-BR.form.d.ts +13 -0
- package/dist/types/pt-BR.form.d.ts.map +1 -0
- package/dist/types/pt-BR.layout.d.ts +7 -0
- package/dist/types/pt-BR.layout.d.ts.map +1 -0
- package/dist/types/pt-BR.navigation.d.ts +8 -0
- package/dist/types/pt-BR.navigation.d.ts.map +1 -0
- package/dist/types/pt-BR.shared.d.ts +21 -0
- package/dist/types/pt-BR.shared.d.ts.map +1 -0
- package/dist/types/pt-BR.utility.d.ts +8 -0
- package/dist/types/pt-BR.utility.d.ts.map +1 -0
- package/dist/types/user-avatar/user-avatar.d.ts +7 -1
- package/dist/types/user-avatar/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +2 -2
- package/dist/user-avatar.js.map +1 -1
- package/dist/utility/InfiniteScroll.js +7 -5
- package/dist/utility/InfiniteScroll.js.map +1 -1
- package/dist/utility/InstallPrompt.js +14 -13
- package/dist/utility/InstallPrompt.js.map +1 -1
- package/package.json +10 -1
- package/src/components/data-display/DataGrid/DataGrid.states.tsx +3 -3
- package/src/components/data-display/DataGrid/DataGrid.tsx +1 -1
- package/src/components/data-display/DataGrid/DataGrid.types.ts +5 -0
- package/src/components/data-display/DataViews/data-views-category-pill.tsx +2 -0
- package/src/components/data-display/DataViews/data-views-copy.ts +27 -1
- package/src/components/data-display/DataViews/data-views-day-input.tsx +14 -3
- package/src/components/data-display/DataViews/data-views-filter-panel.tsx +2 -1
- package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +3 -0
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +9 -10
- package/src/components/data-display/DataViews/data-views-more-fields.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-pagination.tsx +3 -0
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-toolbar.tsx +2 -0
- package/src/components/data-display/HoverCard/HoverCard.content.tsx +10 -2
- package/src/components/data-display/HoverCard/HoverCard.tsx +2 -3
- package/src/components/data-display/HoverCard/HoverCard.types.ts +2 -0
- package/src/components/data-display/Table/Table.tsx +7 -2
- package/src/components/data-display/Table/Table.types.ts +5 -0
- package/src/components/data-display/Table/TableStates.tsx +2 -2
- package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +3 -2
- package/src/components/data-display/TimingDiagram/TimingDiagram.types.ts +3 -0
- package/src/components/feedback/Sonner/Sonner.tsx +15 -3
- package/src/components/feedback/Sonner/Sonner.types.ts +2 -0
- package/src/components/feedback/Sonner/SonnerToast.tsx +2 -1
- package/src/components/feedback/StackedModal/StackedModal.parts.tsx +10 -3
- package/src/components/feedback/StackedModal/StackedModal.tsx +2 -1
- package/src/components/feedback/StackedModal/StackedModal.types.ts +2 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +13 -1
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.types.ts +3 -0
- package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +10 -4
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +7 -0
- package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +2 -0
- package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +4 -2
- package/src/components/form/AddressAutocomplete/AddressInputField.tsx +13 -6
- package/src/components/form/Autocomplete/Autocomplete.parts.tsx +9 -6
- package/src/components/form/Autocomplete/Autocomplete.tsx +2 -1
- package/src/components/form/Autocomplete/Autocomplete.types.ts +7 -0
- package/src/components/form/Calendar/Calendar.tsx +2 -2
- package/src/components/form/Calendar/Calendar.types.ts +7 -0
- package/src/components/form/CodeEditor/CodeEditor.tsx +6 -4
- package/src/components/form/CodeEditor/CodeEditor.types.ts +7 -0
- package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +12 -5
- package/src/components/form/DateRangePicker/DateRangePicker.range.ts +2 -0
- package/src/components/form/DateRangePicker/DateRangePicker.tsx +4 -0
- package/src/components/form/DateRangePicker/DateRangePicker.types.ts +11 -0
- package/src/components/form/DateRangePicker/DateRangePickerFields.tsx +2 -0
- package/src/components/form/Menubar/Menubar.toolbar.tsx +3 -1
- package/src/components/form/Menubar/Menubar.tsx +2 -1
- package/src/components/form/Menubar/Menubar.types.ts +5 -0
- package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +33 -17
- package/src/components/form/PasswordStrength/PasswordStrength.tsx +29 -13
- package/src/components/form/PasswordStrength/PasswordStrength.types.ts +9 -0
- package/src/components/form/PhoneInput/PhoneInput.tsx +15 -5
- package/src/components/form/PhoneInput/PhoneInput.types.ts +7 -0
- package/src/components/form/SearchPalette/SearchPalette.parts.tsx +4 -1
- package/src/components/form/SearchPalette/SearchPalette.tsx +4 -1
- package/src/components/form/SearchPalette/SearchPalette.types.ts +6 -0
- package/src/components/form/Textarea/Textarea.tsx +6 -1
- package/src/components/form/Textarea/Textarea.types.ts +14 -1
- package/src/components/form/Textarea/TextareaRichEditor.tsx +28 -18
- package/src/components/form/UploadButton/UploadButton.parts.tsx +16 -7
- package/src/components/form/UploadButton/UploadButton.tsx +4 -1
- package/src/components/form/UploadButton/UploadButton.types.ts +8 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.tsx +10 -2
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +12 -0
- package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +15 -4
- package/src/components/layout/ContentToolbar/SortByDropdown.tsx +12 -3
- package/src/components/layout/ContentToolbar/ViewSelector.tsx +2 -1
- package/src/components/layout/Dashboard/Dashboard.types.ts +4 -0
- package/src/components/layout/Dashboard/DashboardParts.tsx +2 -1
- package/src/components/layout/ScrollArea/ScrollArea.tsx +16 -3
- package/src/components/layout/ScrollArea/ScrollArea.types.ts +19 -4
- package/src/components/layout/ScrollArea/ScrollViewport.tsx +4 -1
- package/src/components/layout/TableFilter/TableFilter.tsx +19 -11
- package/src/components/layout/TableFilter/TableFilter.types.ts +8 -0
- package/src/components/layout/TableFilter/TableFilterContext.tsx +10 -0
- package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +11 -4
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +2 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +3 -0
- package/src/components/navigation/CommandPalette/CommandPalette.tsx +15 -5
- package/src/components/navigation/CommandPalette/CommandPalette.types.ts +3 -0
- package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +5 -2
- package/src/components/navigation/Pagination/Pagination.tsx +2 -0
- package/src/components/navigation/Pagination/Pagination.types.ts +2 -0
- package/src/components/navigation/Pagination/PaginationParts.tsx +4 -1
- package/src/components/navigation/Tabs/Tabs.tsx +8 -3
- package/src/components/navigation/Tabs/Tabs.types.ts +2 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +4 -4
- package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +4 -2
- package/src/components/utility/InfiniteScroll/InfiniteScroll.types.ts +6 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.tsx +18 -13
- package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +9 -7
- package/src/copy/data-display.ts +60 -0
- package/src/copy/feedback.ts +39 -0
- package/src/copy/form.ts +154 -0
- package/src/copy/layout.ts +18 -0
- package/src/copy/navigation.ts +25 -0
- package/src/copy/shared.ts +122 -0
- package/src/copy/utility.ts +36 -0
- package/src/copy.ts +12 -173
- package/src/en-US.data-display.ts +244 -0
- package/src/en-US.feedback.ts +25 -0
- package/src/en-US.form.ts +103 -0
- package/src/en-US.layout.ts +15 -0
- package/src/en-US.navigation.ts +23 -0
- package/src/en-US.shared.ts +105 -0
- package/src/en-US.ts +24 -0
- package/src/en-US.utility.ts +24 -0
- package/src/locales.ts +151 -0
- package/src/pt-BR.data-display.ts +236 -0
- package/src/pt-BR.feedback.ts +25 -0
- package/src/pt-BR.form.ts +96 -0
- package/src/pt-BR.layout.ts +15 -0
- package/src/pt-BR.navigation.ts +21 -0
- package/src/pt-BR.shared.ts +94 -0
- package/src/pt-BR.ts +15 -307
- package/src/pt-BR.utility.ts +21 -0
- package/src/user-avatar/user-avatar.tsx +8 -2
- package/dist/chunk-CEKPLUQV.js.map +0 -1
- package/dist/chunk-DFVRZ3UK.js.map +0 -1
- package/dist/chunk-H7UAGTVW.js.map +0 -1
- package/dist/chunk-JP55QWVR.js.map +0 -1
- package/dist/chunk-NUCLEOHF.js.map +0 -1
- package/dist/chunk-QLBYAYUG.js.map +0 -1
- package/dist/chunk-QZZVHY6U.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/utility/InstallPrompt/InstallPrompt.tsx","../../src/components/utility/InstallPrompt/InstallPrompt.styles.ts","../../src/components/utility/InstallPrompt/usePwaInstall.ts","../../src/components/utility/InstallPrompt/InstallPrompt.earlyCapture.ts","../../src/components/utility/InstallPrompt/InstallPrompt.helpers.ts"],"sourcesContent":["import Close from '@mui/icons-material/Close';\nimport InstallMobile from '@mui/icons-material/InstallMobile';\nimport IosShare from '@mui/icons-material/IosShare';\nimport { Box, Button, IconButton, Typography } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React from 'react';\n\nimport { installPromptStyles } from './InstallPrompt.styles';\nimport type { InstallPlatform, InstallPromptProps } from './InstallPrompt.types';\nimport { usePwaInstall } from './usePwaInstall';\n\nconst StyledInstallPrompt = styled(Box)(({ theme }) => installPromptStyles(theme));\n\nconst testIdFor = (base: string | undefined, suffix: string) =>\n base ? `${base}-${suffix}` : `install-prompt-${suffix}`;\n\n/**\n * The iOS branch renders instructions rather than a button, because Safari\n * exposes no API to open its own Add to Home Screen sheet. A button here would\n * be a control that does nothing when pressed.\n */\nconst IosInstructions: React.FC<{\n instructions?: React.ReactNode;\n dataTestId?: string;\n}> = ({ instructions, dataTestId }) => (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'ios-instructions')}\n sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}\n >\n {instructions ?? (\n <>\n Tap <IosShare fontSize=\"small\" aria-label=\"Share\" /> then "Add to Home Screen"\n </>\n )}\n </Typography>\n);\n\nconst InstallPromptBody: React.FC<{\n icon?: React.ReactNode;\n title: string;\n description?: string;\n iosInstructions?: React.ReactNode;\n platform: InstallPlatform;\n dataTestId?: string;\n}> = ({ icon, title, description, iosInstructions, platform, dataTestId }) => (\n <>\n <Box className=\"install-prompt-icon\" aria-hidden=\"true\">\n {icon ?? <InstallMobile />}\n </Box>\n\n <Box className=\"install-prompt-content\">\n <Typography\n className=\"install-prompt-title\"\n variant=\"subtitle2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'title')}\n >\n {title}\n </Typography>\n\n {description && (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'description')}\n >\n {description}\n </Typography>\n )}\n\n {platform === 'ios' && (\n <IosInstructions instructions={iosInstructions} dataTestId={dataTestId} />\n )}\n </Box>\n </>\n);\n\nconst InstallPromptActions: React.FC<{\n platform: InstallPlatform;\n installLabel: string;\n dismissLabel: string;\n onInstallClick: () => void;\n onDismissClick: () => void;\n dataTestId?: string;\n}> = ({ platform, installLabel, dismissLabel, onInstallClick, onDismissClick, dataTestId }) => (\n <Box className=\"install-prompt-actions\">\n {platform === 'prompt' && (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={onInstallClick}\n data-testid={testIdFor(dataTestId, 'install')}\n >\n {installLabel}\n </Button>\n )}\n\n <IconButton\n size=\"small\"\n onClick={onDismissClick}\n aria-label={dismissLabel}\n data-testid={testIdFor(dataTestId, 'dismiss')}\n >\n <Close fontSize=\"small\" />\n </IconButton>\n </Box>\n);\n\n/**\n * A dismissible invitation to install the app, rendered only when the browser\n * is actually able to install it and the user has not recently said no.\n *\n * Returns `null` in every other case — already installed, dismissed, or a\n * browser with no install route at all — so it is safe to mount unconditionally\n * in a layout rather than gating it at the call site.\n *\n * The copy defaults to English and every string is a prop: this package stays\n * locale-neutral and the consuming app supplies its own wording.\n */\nexport const InstallPrompt = React.forwardRef<HTMLDivElement, InstallPromptProps>(\n (\n {\n title = 'Install this app',\n description,\n installLabel = 'Install',\n iosInstructions,\n dismissLabel = 'Dismiss install prompt',\n icon,\n onInstall,\n onDismiss,\n storageKey,\n dismissForDays,\n className,\n 'data-testid': dataTestId = 'install-prompt',\n ...props\n },\n ref,\n ) => {\n const { canInstall, platform, promptInstall, dismiss } = usePwaInstall({\n storageKey,\n dismissForDays,\n });\n\n const handleInstall = React.useCallback(() => {\n void promptInstall().then((outcome) => onInstall?.(outcome));\n }, [promptInstall, onInstall]);\n\n const handleDismiss = React.useCallback(() => {\n dismiss();\n onDismiss?.();\n }, [dismiss, onDismiss]);\n\n if (!canInstall) {\n return null;\n }\n\n return (\n <StyledInstallPrompt\n ref={ref}\n className={className}\n role=\"region\"\n aria-label={title}\n data-testid={dataTestId}\n {...props}\n >\n <InstallPromptBody\n icon={icon}\n title={title}\n description={description}\n iosInstructions={iosInstructions}\n platform={platform}\n dataTestId={dataTestId}\n />\n\n <InstallPromptActions\n platform={platform}\n installLabel={installLabel}\n dismissLabel={dismissLabel}\n onInstallClick={handleInstall}\n onDismissClick={handleDismiss}\n dataTestId={dataTestId}\n />\n </StyledInstallPrompt>\n );\n },\n);\n\nInstallPrompt.displayName = 'InstallPrompt';\n","import { alpha } from '@mui/material';\nimport type { CSSObject, Theme } from '@mui/material';\n\nexport const installPromptStyles = (theme: Theme): CSSObject => ({\n display: 'flex',\n alignItems: 'flex-start',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1.5, 2),\n borderRadius: theme.spacing(1),\n backgroundColor: alpha(theme.palette.primary.main, 0.08),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,\n\n '.install-prompt-icon': {\n flexShrink: 0,\n marginTop: theme.spacing(0.25),\n display: 'flex',\n color: theme.palette.primary.main,\n },\n\n '.install-prompt-content': {\n flex: 1,\n minWidth: 0,\n },\n\n '.install-prompt-title': {\n fontWeight: 600,\n },\n\n '.install-prompt-description': {\n opacity: 0.9,\n },\n\n '.install-prompt-actions': {\n display: 'flex',\n flexShrink: 0,\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n\n // The actions drop to their own full-width row on a phone, where this\n // component does most of its work.\n [theme.breakpoints.down('sm')]: {\n flexWrap: 'wrap',\n\n '.install-prompt-actions': {\n width: '100%',\n marginTop: theme.spacing(1),\n },\n },\n});\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { PWA_INSTALL_AVAILABLE_EVENT, readPwaInstallStash } from './InstallPrompt.earlyCapture';\nimport {\n DEFAULT_DISMISS_DAYS,\n DEFAULT_STORAGE_KEY,\n isDismissalActive,\n isStandaloneDisplay,\n readDismissedAt,\n resolveInstallPlatform,\n writeDismissedAt,\n} from './InstallPrompt.helpers';\nimport type {\n BeforeInstallPromptEvent,\n InstallOutcome,\n InstallPlatform,\n PwaInstallState,\n UsePwaInstallOptions,\n} from './InstallPrompt.types';\n\n/**\n * Headless PWA install machinery.\n *\n * A manifest and a service worker make a site installABLE; they do not make it\n * install. Chromium announces its willingness by firing `beforeinstallprompt`\n * and then does nothing further on its own beyond a small address-bar icon most\n * users never notice. Unless a page calls `preventDefault()` on that event and\n * keeps it, the handle is gone and the app can never ask.\n *\n * That is the whole reason this hook exists. It captures the event, tracks the\n * three states that should suppress an install invitation (already installed,\n * recently dismissed, browser cannot install), and hands back a\n * gesture-callable `promptInstall`.\n *\n * Every environment probe runs in an effect rather than in a state initialiser:\n * touching `window` during render breaks SSR and desynchronises hydration, so\n * the first paint deliberately reports `canInstall: false` and corrects itself\n * on mount.\n */\n\n/** Tracks whether the page is running as an installed app. */\nconst useInstalledState = (onInstalled?: () => void) => {\n const [isInstalled, setIsInstalled] = useState(false);\n\n // Held in a ref, and refreshed in its own effect rather than during render, so\n // that a caller passing an inline callback does not re-subscribe the listener\n // on every render.\n const onInstalledRef = useRef(onInstalled);\n useEffect(() => {\n onInstalledRef.current = onInstalled;\n }, [onInstalled]);\n\n useEffect(() => {\n // `appinstalled` can also land before the app mounts, so the stash is\n // consulted for the same reason the deferred event is.\n setIsInstalled(isStandaloneDisplay() || readPwaInstallStash()?.installedAt != null);\n\n const markInstalled = () => {\n setIsInstalled(true);\n onInstalledRef.current?.();\n };\n\n window.addEventListener('appinstalled', markInstalled);\n return () => window.removeEventListener('appinstalled', markInstalled);\n }, []);\n\n return isInstalled;\n};\n\n/**\n * Holds the deferred `beforeinstallprompt` event.\n *\n * The stash written by `capturePwaInstallEvent` is the PRIMARY source, not a\n * fallback. Chromium fires the event once, during initial page load; a\n * listener attached in an effect runs after hydration and, when the component\n * is code-split, after a download that may not happen until a later route. In\n * a real browser it essentially never catches the event, and the event is not\n * reissued on request — so a hook that relies on its own listener reports\n * \"cannot install\" forever on the one platform that can.\n *\n * The live listeners below therefore cover only the narrow case they can\n * actually serve: a browser re-evaluating installability while the app is\n * already running, and a host that never wired the capture at all.\n */\nconst useDeferredPrompt = () => {\n const deferredRef = useRef<BeforeInstallPromptEvent | null>(null);\n const [promptReady, setPromptReady] = useState(false);\n\n useEffect(() => {\n const hold = (event: BeforeInstallPromptEvent) => {\n deferredRef.current = event;\n setPromptReady(true);\n };\n\n // Whatever fired before this component existed.\n const held = readPwaInstallStash()?.event;\n if (held) hold(held);\n\n // The capture rang the bell after mount: adopt what it is holding.\n const adopt = () => {\n const fresh = readPwaInstallStash()?.event;\n if (fresh) hold(fresh);\n };\n\n const capture = (event: Event) => {\n // Without preventDefault the browser owns the event and the handle dies\n // with it. The capture has normally done this already; repeating it is\n // harmless and keeps the hook correct on a host that skipped wiring.\n event.preventDefault();\n hold(event as BeforeInstallPromptEvent);\n };\n\n window.addEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.addEventListener('beforeinstallprompt', capture);\n return () => {\n window.removeEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.removeEventListener('beforeinstallprompt', capture);\n };\n }, []);\n\n const clearPrompt = useCallback(() => {\n deferredRef.current = null;\n setPromptReady(false);\n }, []);\n\n return { deferredRef, promptReady, clearPrompt };\n};\n\n/** Reads and writes the \"don't ask again for a while\" record. */\nconst useDismissal = (storageKey: string, dismissForDays: number) => {\n const [dismissed, setDismissed] = useState(false);\n\n useEffect(() => {\n const dismissedAt = readDismissedAt(storageKey);\n setDismissed(isDismissalActive(dismissedAt, dismissForDays, Date.now()));\n }, [storageKey, dismissForDays]);\n\n const persistDismissal = useCallback(() => {\n writeDismissedAt(storageKey, Date.now());\n setDismissed(true);\n }, [storageKey]);\n\n return { dismissed, persistDismissal };\n};\n\n/** Resolves the install route once the deferred-prompt state is known. */\nconst useInstallPlatform = (promptReady: boolean): InstallPlatform => {\n const [platform, setPlatform] = useState<InstallPlatform>('unsupported');\n\n useEffect(() => {\n setPlatform(resolveInstallPlatform(promptReady));\n }, [promptReady]);\n\n return platform;\n};\n\nexport const usePwaInstall = (options: UsePwaInstallOptions = {}): PwaInstallState => {\n const {\n storageKey = DEFAULT_STORAGE_KEY,\n dismissForDays = DEFAULT_DISMISS_DAYS,\n onInstalled,\n } = options;\n\n const isInstalled = useInstalledState(onInstalled);\n const { deferredRef, promptReady, clearPrompt } = useDeferredPrompt();\n const { dismissed, persistDismissal } = useDismissal(storageKey, dismissForDays);\n const platform = useInstallPlatform(promptReady);\n\n const promptInstall = useCallback(async (): Promise<InstallOutcome> => {\n const deferred = deferredRef.current;\n if (!deferred) return 'unavailable';\n\n await deferred.prompt();\n const { outcome } = await deferred.userChoice;\n\n // Single-use handle: spent whichever way the user answered. Chromium emits a\n // fresh event if the app is still installable, so holding this one would\n // only mean calling prompt() twice on a dead event.\n clearPrompt();\n\n // Declining the browser's own dialog is a real \"no\" — record it, or the\n // affordance reappears on the next render and badgers the user.\n if (outcome === 'dismissed') persistDismissal();\n\n return outcome;\n }, [deferredRef, clearPrompt, persistDismissal]);\n\n // iOS has no event to wait for, so its affordance is gated on the platform\n // probe alone; everywhere else a live captured prompt is the prerequisite.\n const canInstall = !isInstalled && !dismissed && (platform === 'ios' || promptReady);\n\n return {\n canInstall,\n platform,\n isInstalled,\n promptInstall,\n dismiss: persistDismissal,\n };\n};\n","import type { BeforeInstallPromptEvent, PwaInstallStash } from './InstallPrompt.types';\n\n/**\n * Holds `beforeinstallprompt` from before the app exists.\n *\n * `usePwaInstall` cannot attach its own listener early enough, and no React\n * hook can. Chromium fires the event once, during initial page load, the\n * moment it has a manifest and a registered worker — which in a real app is\n * before hydration, and often before the chunk containing the component has\n * even been downloaded. A listener registered in an effect is not late by\n * milliseconds; it can be late by entire navigations. Nothing calls\n * `preventDefault()` in time, the browser keeps the event, and the page can\n * never ask again.\n *\n * So the capture has to live outside React, and the host has to install it as\n * the very first thing the document does. Two supported ways, in order of\n * reliability:\n *\n * 1. An inline `<script>` in `<head>`, which beats a deferred module bundle:\n *\n * <script>\n * window.addEventListener('beforeinstallprompt', function (e) {\n * e.preventDefault();\n * var s = (window.__pwaInstall = window.__pwaInstall || {});\n * s.event = e; s.firedAt = Date.now();\n * window.dispatchEvent(new Event('pwa-install-available'));\n * });\n * </script>\n *\n * 2. `capturePwaInstallEvent()` as the first statement of the entry module,\n * before `createRoot`. Simpler to wire and correct in most apps, but a\n * module is still deferred, so a very early event can outrun it.\n *\n * Both write the same shape, so the hook does not care which was used.\n */\n\n/** Property on `window` holding the captured event. Shared with the inline snippet. */\nexport const PWA_INSTALL_STASH_KEY = '__pwaInstall';\n\n/** Fired after a capture, so an app already running can adopt the event. */\nexport const PWA_INSTALL_AVAILABLE_EVENT = 'pwa-install-available';\n\ntype StashHost = Window & { [PWA_INSTALL_STASH_KEY]?: PwaInstallStash };\n\n/** The stash, or `null` when no capture ever ran (SSR, or a host that skipped wiring). */\nexport const readPwaInstallStash = (): PwaInstallStash | null => {\n if (typeof window === 'undefined') return null;\n return (window as StashHost)[PWA_INSTALL_STASH_KEY] ?? null;\n};\n\nconst ensureStash = (): PwaInstallStash => {\n const host = window as StashHost;\n host[PWA_INSTALL_STASH_KEY] ??= { event: null, firedAt: null, installedAt: null };\n return host[PWA_INSTALL_STASH_KEY];\n};\n\n/**\n * The teardown for the capture currently running, so `resetPwaInstallStash`\n * can unwind completely. Held here rather than on the stash because it must\n * survive the stash being deleted, and because it must never be serialised\n * into the diagnostic payload.\n */\nlet activeTeardown: (() => void) | null = null;\n\n/**\n * Starts capturing. Returns a teardown, and is safe to call twice — a second\n * call is a no-op rather than a duplicate listener, so a host that wires both\n * the inline snippet and this function does not double-handle the event.\n */\nexport const capturePwaInstallEvent = (): (() => void) => {\n if (typeof window === 'undefined') return () => undefined;\n\n const stash = ensureStash();\n if (stash.capturing === true) return () => undefined;\n stash.capturing = true;\n\n const onBeforeInstall = (event: Event): void => {\n // The line the whole component depends on. Without it the browser owns the\n // event, shows at most its own mini-infobar, and discards the handle.\n event.preventDefault();\n stash.event = event as BeforeInstallPromptEvent;\n stash.firedAt = Date.now();\n window.dispatchEvent(new Event(PWA_INSTALL_AVAILABLE_EVENT));\n };\n\n const onInstalled = (): void => {\n stash.installedAt = Date.now();\n stash.event = null;\n };\n\n window.addEventListener('beforeinstallprompt', onBeforeInstall);\n window.addEventListener('appinstalled', onInstalled);\n\n const teardown = () => {\n window.removeEventListener('beforeinstallprompt', onBeforeInstall);\n window.removeEventListener('appinstalled', onInstalled);\n stash.capturing = false;\n if (activeTeardown === teardown) activeTeardown = null;\n };\n\n activeTeardown = teardown;\n return teardown;\n};\n\n/**\n * Returns the page to its pre-capture state: listeners removed, stash gone.\n *\n * Exists for tests and stories, which need a clean page per case. It unwinds\n * the listeners as well as the data, because a leaked capture from a previous\n * case would go on suppressing events that the next case expects to observe.\n */\nexport const resetPwaInstallStash = (): void => {\n if (typeof window === 'undefined') return;\n activeTeardown?.();\n activeTeardown = null;\n delete (window as StashHost)[PWA_INSTALL_STASH_KEY];\n};\n","import type { InstallPlatform } from './InstallPrompt.types';\n\n/**\n * Pure environment probes and dismissal storage for `usePwaInstall`.\n *\n * Everything here is a plain function taking its clock and its storage from the\n * caller where that matters, so the hook's behaviour is testable without\n * faking timers or a browser.\n */\n\nexport const DEFAULT_STORAGE_KEY = 'pwa-install-dismissed';\nexport const DEFAULT_DISMISS_DAYS = 30;\n\nconst MS_PER_DAY = 86_400_000;\nconst STANDALONE_QUERY = '(display-mode: standalone)';\n\n/** iOS Safari predates `display-mode` and exposes this instead. */\ninterface IosNavigator extends Navigator {\n standalone?: boolean;\n}\n\n/** Guard for SSR and for jsdom runs where `matchMedia` is not implemented. */\nconst hasWindow = (): boolean => typeof window !== 'undefined';\n\n/**\n * True when the page is already running as an installed app.\n *\n * Both checks are needed: `display-mode: standalone` is the standard signal,\n * but iOS never implemented it for home-screen launches and reports\n * `navigator.standalone` instead. Miss the iOS branch and the installed app\n * nags its own users to install it again.\n */\nexport const isStandaloneDisplay = (): boolean => {\n if (!hasWindow()) return false;\n if ((window.navigator as IosNavigator).standalone === true) return true;\n return window.matchMedia?.(STANDALONE_QUERY).matches === true;\n};\n\n/**\n * True for Safari on iOS/iPadOS — the one browser that can install but cannot\n * be asked programmatically.\n *\n * Two traps handled here. iPadOS 13+ reports a Macintosh user agent, so the\n * touch-point count is what separates an iPad from a desktop; and Chrome,\n * Firefox and Edge on iOS are Safari underneath but offer no Add to Home\n * Screen at all, so telling their users to tap Share is a dead end.\n *\n * Kept parameterised so the agent table can be tested without reassigning\n * `navigator`, which leaks across test files when it is not restored.\n */\nexport const isIosSafariAgent = (userAgent: string, maxTouchPoints: number): boolean => {\n const isIosDevice =\n /iPad|iPhone|iPod/.test(userAgent) ||\n (userAgent.includes('Macintosh') && maxTouchPoints > 1);\n\n if (!isIosDevice) return false;\n\n return !/CriOS|FxiOS|EdgiOS|OPiOS/.test(userAgent);\n};\n\n/** `isIosSafariAgent` against the live browser. Internal — callers use `resolveInstallPlatform`. */\nconst isIosSafari = (): boolean => {\n if (!hasWindow()) return false;\n\n const { userAgent, maxTouchPoints } = window.navigator;\n return isIosSafariAgent(userAgent, maxTouchPoints);\n};\n\n/**\n * Reads the stored dismissal timestamp.\n *\n * Storage access is wrapped because it THROWS rather than returning null in\n * Safari private mode and wherever cookies are blocked — an uncaught throw\n * here would take down the storefront on the very browsers this component\n * exists to serve.\n */\nexport const readDismissedAt = (storageKey: string): number | null => {\n if (!hasWindow()) return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n } catch {\n return null;\n }\n};\n\n/** Persists a dismissal. Silently a no-op when storage is unavailable. */\nexport const writeDismissedAt = (storageKey: string, at: number): void => {\n if (!hasWindow()) return;\n\n try {\n window.localStorage.setItem(storageKey, String(at));\n } catch {\n // Storage blocked — the dismissal lasts for this page view only.\n }\n};\n\n/**\n * Whether a stored dismissal still suppresses the prompt.\n *\n * `dismissForDays: 0` means forever. A timestamp in the future (a device whose\n * clock was wound back) is treated as active rather than expired, so a bad\n * clock cannot turn into a prompt on every page view.\n */\nexport const isDismissalActive = (\n dismissedAt: number | null,\n dismissForDays: number,\n now: number,\n): boolean => {\n if (dismissedAt === null) return false;\n if (dismissForDays <= 0) return true;\n\n return now - dismissedAt < dismissForDays * MS_PER_DAY;\n};\n\n/** Resolves which install route this browser offers. */\nexport const resolveInstallPlatform = (hasDeferredPrompt: boolean): InstallPlatform => {\n if (hasDeferredPrompt) return 'prompt';\n return isIosSafari() ? 'ios' : 'unsupported';\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/utility/InstallPrompt/InstallPrompt.tsx","../../src/components/utility/InstallPrompt/InstallPrompt.styles.ts","../../src/components/utility/InstallPrompt/usePwaInstall.ts","../../src/components/utility/InstallPrompt/InstallPrompt.earlyCapture.ts","../../src/components/utility/InstallPrompt/InstallPrompt.helpers.ts"],"sourcesContent":["import Close from '@mui/icons-material/Close';\nimport InstallMobile from '@mui/icons-material/InstallMobile';\nimport IosShare from '@mui/icons-material/IosShare';\nimport { Box, Button, IconButton, Typography } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React from 'react';\n\nimport { installPromptStyles } from './InstallPrompt.styles';\nimport type { InstallPlatform, InstallPromptProps } from './InstallPrompt.types';\nimport { usePwaInstall } from './usePwaInstall';\nimport type { InstallPromptCopy } from '../../../copy';\n\nconst StyledInstallPrompt = styled(Box)(({ theme }) => installPromptStyles(theme));\n\nconst testIdFor = (base: string | undefined, suffix: string) =>\n base ? `${base}-${suffix}` : `install-prompt-${suffix}`;\n\n/**\n * The iOS branch renders instructions rather than a button, because Safari\n * exposes no API to open its own Add to Home Screen sheet. A button here would\n * be a control that does nothing when pressed.\n */\nconst IosInstructions: React.FC<{\n copy: InstallPromptCopy;\n instructions?: React.ReactNode;\n dataTestId?: string;\n}> = ({ copy, instructions, dataTestId }) => (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'ios-instructions')}\n sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}\n >\n {instructions ?? (\n <>\n {copy.iosTapBefore} <IosShare fontSize=\"small\" aria-label={copy.shareLabel} />{' '}\n {copy.iosTapAfter}\n </>\n )}\n </Typography>\n);\n\nconst InstallPromptBody: React.FC<{\n copy: InstallPromptCopy;\n icon?: React.ReactNode;\n title: string;\n description?: string;\n iosInstructions?: React.ReactNode;\n platform: InstallPlatform;\n dataTestId?: string;\n}> = ({ copy, icon, title, description, iosInstructions, platform, dataTestId }) => (\n <>\n <Box className=\"install-prompt-icon\" aria-hidden=\"true\">\n {icon ?? <InstallMobile />}\n </Box>\n\n <Box className=\"install-prompt-content\">\n <Typography\n className=\"install-prompt-title\"\n variant=\"subtitle2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'title')}\n >\n {title}\n </Typography>\n\n {description && (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'description')}\n >\n {description}\n </Typography>\n )}\n\n {platform === 'ios' && (\n <IosInstructions copy={copy} instructions={iosInstructions} dataTestId={dataTestId} />\n )}\n </Box>\n </>\n);\n\nconst InstallPromptActions: React.FC<{\n platform: InstallPlatform;\n installLabel: string;\n dismissLabel: string;\n onInstallClick: () => void;\n onDismissClick: () => void;\n dataTestId?: string;\n}> = ({ platform, installLabel, dismissLabel, onInstallClick, onDismissClick, dataTestId }) => (\n <Box className=\"install-prompt-actions\">\n {platform === 'prompt' && (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={onInstallClick}\n data-testid={testIdFor(dataTestId, 'install')}\n >\n {installLabel}\n </Button>\n )}\n\n <IconButton\n size=\"small\"\n onClick={onDismissClick}\n aria-label={dismissLabel}\n data-testid={testIdFor(dataTestId, 'dismiss')}\n >\n <Close fontSize=\"small\" />\n </IconButton>\n </Box>\n);\n\n/**\n * A dismissible invitation to install the app, rendered only when the browser\n * is actually able to install it and the user has not recently said no.\n *\n * Returns `null` in every other case — already installed, dismissed, or a\n * browser with no install route at all — so it is safe to mount unconditionally\n * in a layout rather than gating it at the call site.\n *\n * Every string is REQUIRED host config (`copy`): this package stays\n * locale-neutral and the consuming app supplies its own wording. There is no\n * English default to fall through to, so a missing word is a typecheck failure\n * rather than an English sentence in a pt-BR store.\n */\nexport const InstallPrompt = React.forwardRef<HTMLDivElement, InstallPromptProps>(\n (\n {\n copy,\n description,\n iosInstructions,\n icon,\n onInstall,\n onDismiss,\n storageKey,\n dismissForDays,\n className,\n 'data-testid': dataTestId = 'install-prompt',\n ...props\n },\n ref,\n ) => {\n const { canInstall, platform, promptInstall, dismiss } = usePwaInstall({\n storageKey,\n dismissForDays,\n });\n\n const handleInstall = React.useCallback(() => {\n void promptInstall().then((outcome) => onInstall?.(outcome));\n }, [promptInstall, onInstall]);\n\n const handleDismiss = React.useCallback(() => {\n dismiss();\n onDismiss?.();\n }, [dismiss, onDismiss]);\n\n if (!canInstall) {\n return null;\n }\n\n return (\n <StyledInstallPrompt\n ref={ref}\n className={className}\n role=\"region\"\n aria-label={copy.title}\n data-testid={dataTestId}\n {...props}\n >\n <InstallPromptBody\n icon={icon}\n copy={copy}\n title={copy.title}\n description={description}\n iosInstructions={iosInstructions}\n platform={platform}\n dataTestId={dataTestId}\n />\n\n <InstallPromptActions\n platform={platform}\n installLabel={copy.installLabel}\n dismissLabel={copy.dismissLabel}\n onInstallClick={handleInstall}\n onDismissClick={handleDismiss}\n dataTestId={dataTestId}\n />\n </StyledInstallPrompt>\n );\n },\n);\n\nInstallPrompt.displayName = 'InstallPrompt';\n","import { alpha } from '@mui/material';\nimport type { CSSObject, Theme } from '@mui/material';\n\nexport const installPromptStyles = (theme: Theme): CSSObject => ({\n display: 'flex',\n alignItems: 'flex-start',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1.5, 2),\n borderRadius: theme.spacing(1),\n backgroundColor: alpha(theme.palette.primary.main, 0.08),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,\n\n '.install-prompt-icon': {\n flexShrink: 0,\n marginTop: theme.spacing(0.25),\n display: 'flex',\n color: theme.palette.primary.main,\n },\n\n '.install-prompt-content': {\n flex: 1,\n minWidth: 0,\n },\n\n '.install-prompt-title': {\n fontWeight: 600,\n },\n\n '.install-prompt-description': {\n opacity: 0.9,\n },\n\n '.install-prompt-actions': {\n display: 'flex',\n flexShrink: 0,\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n\n // The actions drop to their own full-width row on a phone, where this\n // component does most of its work.\n [theme.breakpoints.down('sm')]: {\n flexWrap: 'wrap',\n\n '.install-prompt-actions': {\n width: '100%',\n marginTop: theme.spacing(1),\n },\n },\n});\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { PWA_INSTALL_AVAILABLE_EVENT, readPwaInstallStash } from './InstallPrompt.earlyCapture';\nimport {\n DEFAULT_DISMISS_DAYS,\n DEFAULT_STORAGE_KEY,\n isDismissalActive,\n isStandaloneDisplay,\n readDismissedAt,\n resolveInstallPlatform,\n writeDismissedAt,\n} from './InstallPrompt.helpers';\nimport type {\n BeforeInstallPromptEvent,\n InstallOutcome,\n InstallPlatform,\n PwaInstallState,\n UsePwaInstallOptions,\n} from './InstallPrompt.types';\n\n/**\n * Headless PWA install machinery.\n *\n * A manifest and a service worker make a site installABLE; they do not make it\n * install. Chromium announces its willingness by firing `beforeinstallprompt`\n * and then does nothing further on its own beyond a small address-bar icon most\n * users never notice. Unless a page calls `preventDefault()` on that event and\n * keeps it, the handle is gone and the app can never ask.\n *\n * That is the whole reason this hook exists. It captures the event, tracks the\n * three states that should suppress an install invitation (already installed,\n * recently dismissed, browser cannot install), and hands back a\n * gesture-callable `promptInstall`.\n *\n * Every environment probe runs in an effect rather than in a state initialiser:\n * touching `window` during render breaks SSR and desynchronises hydration, so\n * the first paint deliberately reports `canInstall: false` and corrects itself\n * on mount.\n */\n\n/** Tracks whether the page is running as an installed app. */\nconst useInstalledState = (onInstalled?: () => void) => {\n const [isInstalled, setIsInstalled] = useState(false);\n\n // Held in a ref, and refreshed in its own effect rather than during render, so\n // that a caller passing an inline callback does not re-subscribe the listener\n // on every render.\n const onInstalledRef = useRef(onInstalled);\n useEffect(() => {\n onInstalledRef.current = onInstalled;\n }, [onInstalled]);\n\n useEffect(() => {\n // `appinstalled` can also land before the app mounts, so the stash is\n // consulted for the same reason the deferred event is.\n setIsInstalled(isStandaloneDisplay() || readPwaInstallStash()?.installedAt != null);\n\n const markInstalled = () => {\n setIsInstalled(true);\n onInstalledRef.current?.();\n };\n\n window.addEventListener('appinstalled', markInstalled);\n return () => window.removeEventListener('appinstalled', markInstalled);\n }, []);\n\n return isInstalled;\n};\n\n/**\n * Holds the deferred `beforeinstallprompt` event.\n *\n * The stash written by `capturePwaInstallEvent` is the PRIMARY source, not a\n * fallback. Chromium fires the event once, during initial page load; a\n * listener attached in an effect runs after hydration and, when the component\n * is code-split, after a download that may not happen until a later route. In\n * a real browser it essentially never catches the event, and the event is not\n * reissued on request — so a hook that relies on its own listener reports\n * \"cannot install\" forever on the one platform that can.\n *\n * The live listeners below therefore cover only the narrow case they can\n * actually serve: a browser re-evaluating installability while the app is\n * already running, and a host that never wired the capture at all.\n */\nconst useDeferredPrompt = () => {\n const deferredRef = useRef<BeforeInstallPromptEvent | null>(null);\n const [promptReady, setPromptReady] = useState(false);\n\n useEffect(() => {\n const hold = (event: BeforeInstallPromptEvent) => {\n deferredRef.current = event;\n setPromptReady(true);\n };\n\n // Whatever fired before this component existed.\n const held = readPwaInstallStash()?.event;\n if (held) hold(held);\n\n // The capture rang the bell after mount: adopt what it is holding.\n const adopt = () => {\n const fresh = readPwaInstallStash()?.event;\n if (fresh) hold(fresh);\n };\n\n const capture = (event: Event) => {\n // Without preventDefault the browser owns the event and the handle dies\n // with it. The capture has normally done this already; repeating it is\n // harmless and keeps the hook correct on a host that skipped wiring.\n event.preventDefault();\n hold(event as BeforeInstallPromptEvent);\n };\n\n window.addEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.addEventListener('beforeinstallprompt', capture);\n return () => {\n window.removeEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.removeEventListener('beforeinstallprompt', capture);\n };\n }, []);\n\n const clearPrompt = useCallback(() => {\n deferredRef.current = null;\n setPromptReady(false);\n }, []);\n\n return { deferredRef, promptReady, clearPrompt };\n};\n\n/** Reads and writes the \"don't ask again for a while\" record. */\nconst useDismissal = (storageKey: string, dismissForDays: number) => {\n const [dismissed, setDismissed] = useState(false);\n\n useEffect(() => {\n const dismissedAt = readDismissedAt(storageKey);\n setDismissed(isDismissalActive(dismissedAt, dismissForDays, Date.now()));\n }, [storageKey, dismissForDays]);\n\n const persistDismissal = useCallback(() => {\n writeDismissedAt(storageKey, Date.now());\n setDismissed(true);\n }, [storageKey]);\n\n return { dismissed, persistDismissal };\n};\n\n/** Resolves the install route once the deferred-prompt state is known. */\nconst useInstallPlatform = (promptReady: boolean): InstallPlatform => {\n const [platform, setPlatform] = useState<InstallPlatform>('unsupported');\n\n useEffect(() => {\n setPlatform(resolveInstallPlatform(promptReady));\n }, [promptReady]);\n\n return platform;\n};\n\nexport const usePwaInstall = (options: UsePwaInstallOptions = {}): PwaInstallState => {\n const {\n storageKey = DEFAULT_STORAGE_KEY,\n dismissForDays = DEFAULT_DISMISS_DAYS,\n onInstalled,\n } = options;\n\n const isInstalled = useInstalledState(onInstalled);\n const { deferredRef, promptReady, clearPrompt } = useDeferredPrompt();\n const { dismissed, persistDismissal } = useDismissal(storageKey, dismissForDays);\n const platform = useInstallPlatform(promptReady);\n\n const promptInstall = useCallback(async (): Promise<InstallOutcome> => {\n const deferred = deferredRef.current;\n if (!deferred) return 'unavailable';\n\n await deferred.prompt();\n const { outcome } = await deferred.userChoice;\n\n // Single-use handle: spent whichever way the user answered. Chromium emits a\n // fresh event if the app is still installable, so holding this one would\n // only mean calling prompt() twice on a dead event.\n clearPrompt();\n\n // Declining the browser's own dialog is a real \"no\" — record it, or the\n // affordance reappears on the next render and badgers the user.\n if (outcome === 'dismissed') persistDismissal();\n\n return outcome;\n }, [deferredRef, clearPrompt, persistDismissal]);\n\n // iOS has no event to wait for, so its affordance is gated on the platform\n // probe alone; everywhere else a live captured prompt is the prerequisite.\n const canInstall = !isInstalled && !dismissed && (platform === 'ios' || promptReady);\n\n return {\n canInstall,\n platform,\n isInstalled,\n promptInstall,\n dismiss: persistDismissal,\n };\n};\n","import type { BeforeInstallPromptEvent, PwaInstallStash } from './InstallPrompt.types';\n\n/**\n * Holds `beforeinstallprompt` from before the app exists.\n *\n * `usePwaInstall` cannot attach its own listener early enough, and no React\n * hook can. Chromium fires the event once, during initial page load, the\n * moment it has a manifest and a registered worker — which in a real app is\n * before hydration, and often before the chunk containing the component has\n * even been downloaded. A listener registered in an effect is not late by\n * milliseconds; it can be late by entire navigations. Nothing calls\n * `preventDefault()` in time, the browser keeps the event, and the page can\n * never ask again.\n *\n * So the capture has to live outside React, and the host has to install it as\n * the very first thing the document does. Two supported ways, in order of\n * reliability:\n *\n * 1. An inline `<script>` in `<head>`, which beats a deferred module bundle:\n *\n * <script>\n * window.addEventListener('beforeinstallprompt', function (e) {\n * e.preventDefault();\n * var s = (window.__pwaInstall = window.__pwaInstall || {});\n * s.event = e; s.firedAt = Date.now();\n * window.dispatchEvent(new Event('pwa-install-available'));\n * });\n * </script>\n *\n * 2. `capturePwaInstallEvent()` as the first statement of the entry module,\n * before `createRoot`. Simpler to wire and correct in most apps, but a\n * module is still deferred, so a very early event can outrun it.\n *\n * Both write the same shape, so the hook does not care which was used.\n */\n\n/** Property on `window` holding the captured event. Shared with the inline snippet. */\nexport const PWA_INSTALL_STASH_KEY = '__pwaInstall';\n\n/** Fired after a capture, so an app already running can adopt the event. */\nexport const PWA_INSTALL_AVAILABLE_EVENT = 'pwa-install-available';\n\ntype StashHost = Window & { [PWA_INSTALL_STASH_KEY]?: PwaInstallStash };\n\n/** The stash, or `null` when no capture ever ran (SSR, or a host that skipped wiring). */\nexport const readPwaInstallStash = (): PwaInstallStash | null => {\n if (typeof window === 'undefined') return null;\n return (window as StashHost)[PWA_INSTALL_STASH_KEY] ?? null;\n};\n\nconst ensureStash = (): PwaInstallStash => {\n const host = window as StashHost;\n host[PWA_INSTALL_STASH_KEY] ??= { event: null, firedAt: null, installedAt: null };\n return host[PWA_INSTALL_STASH_KEY];\n};\n\n/**\n * The teardown for the capture currently running, so `resetPwaInstallStash`\n * can unwind completely. Held here rather than on the stash because it must\n * survive the stash being deleted, and because it must never be serialised\n * into the diagnostic payload.\n */\nlet activeTeardown: (() => void) | null = null;\n\n/**\n * Starts capturing. Returns a teardown, and is safe to call twice — a second\n * call is a no-op rather than a duplicate listener, so a host that wires both\n * the inline snippet and this function does not double-handle the event.\n */\nexport const capturePwaInstallEvent = (): (() => void) => {\n if (typeof window === 'undefined') return () => undefined;\n\n const stash = ensureStash();\n if (stash.capturing === true) return () => undefined;\n stash.capturing = true;\n\n const onBeforeInstall = (event: Event): void => {\n // The line the whole component depends on. Without it the browser owns the\n // event, shows at most its own mini-infobar, and discards the handle.\n event.preventDefault();\n stash.event = event as BeforeInstallPromptEvent;\n stash.firedAt = Date.now();\n window.dispatchEvent(new Event(PWA_INSTALL_AVAILABLE_EVENT));\n };\n\n const onInstalled = (): void => {\n stash.installedAt = Date.now();\n stash.event = null;\n };\n\n window.addEventListener('beforeinstallprompt', onBeforeInstall);\n window.addEventListener('appinstalled', onInstalled);\n\n const teardown = () => {\n window.removeEventListener('beforeinstallprompt', onBeforeInstall);\n window.removeEventListener('appinstalled', onInstalled);\n stash.capturing = false;\n if (activeTeardown === teardown) activeTeardown = null;\n };\n\n activeTeardown = teardown;\n return teardown;\n};\n\n/**\n * Returns the page to its pre-capture state: listeners removed, stash gone.\n *\n * Exists for tests and stories, which need a clean page per case. It unwinds\n * the listeners as well as the data, because a leaked capture from a previous\n * case would go on suppressing events that the next case expects to observe.\n */\nexport const resetPwaInstallStash = (): void => {\n if (typeof window === 'undefined') return;\n activeTeardown?.();\n activeTeardown = null;\n delete (window as StashHost)[PWA_INSTALL_STASH_KEY];\n};\n","import type { InstallPlatform } from './InstallPrompt.types';\n\n/**\n * Pure environment probes and dismissal storage for `usePwaInstall`.\n *\n * Everything here is a plain function taking its clock and its storage from the\n * caller where that matters, so the hook's behaviour is testable without\n * faking timers or a browser.\n */\n\nexport const DEFAULT_STORAGE_KEY = 'pwa-install-dismissed';\nexport const DEFAULT_DISMISS_DAYS = 30;\n\nconst MS_PER_DAY = 86_400_000;\nconst STANDALONE_QUERY = '(display-mode: standalone)';\n\n/** iOS Safari predates `display-mode` and exposes this instead. */\ninterface IosNavigator extends Navigator {\n standalone?: boolean;\n}\n\n/** Guard for SSR and for jsdom runs where `matchMedia` is not implemented. */\nconst hasWindow = (): boolean => typeof window !== 'undefined';\n\n/**\n * True when the page is already running as an installed app.\n *\n * Both checks are needed: `display-mode: standalone` is the standard signal,\n * but iOS never implemented it for home-screen launches and reports\n * `navigator.standalone` instead. Miss the iOS branch and the installed app\n * nags its own users to install it again.\n */\nexport const isStandaloneDisplay = (): boolean => {\n if (!hasWindow()) return false;\n if ((window.navigator as IosNavigator).standalone === true) return true;\n return window.matchMedia?.(STANDALONE_QUERY).matches === true;\n};\n\n/**\n * True for Safari on iOS/iPadOS — the one browser that can install but cannot\n * be asked programmatically.\n *\n * Two traps handled here. iPadOS 13+ reports a Macintosh user agent, so the\n * touch-point count is what separates an iPad from a desktop; and Chrome,\n * Firefox and Edge on iOS are Safari underneath but offer no Add to Home\n * Screen at all, so telling their users to tap Share is a dead end.\n *\n * Kept parameterised so the agent table can be tested without reassigning\n * `navigator`, which leaks across test files when it is not restored.\n */\nexport const isIosSafariAgent = (userAgent: string, maxTouchPoints: number): boolean => {\n const isIosDevice =\n /iPad|iPhone|iPod/.test(userAgent) ||\n (userAgent.includes('Macintosh') && maxTouchPoints > 1);\n\n if (!isIosDevice) return false;\n\n return !/CriOS|FxiOS|EdgiOS|OPiOS/.test(userAgent);\n};\n\n/** `isIosSafariAgent` against the live browser. Internal — callers use `resolveInstallPlatform`. */\nconst isIosSafari = (): boolean => {\n if (!hasWindow()) return false;\n\n const { userAgent, maxTouchPoints } = window.navigator;\n return isIosSafariAgent(userAgent, maxTouchPoints);\n};\n\n/**\n * Reads the stored dismissal timestamp.\n *\n * Storage access is wrapped because it THROWS rather than returning null in\n * Safari private mode and wherever cookies are blocked — an uncaught throw\n * here would take down the storefront on the very browsers this component\n * exists to serve.\n */\nexport const readDismissedAt = (storageKey: string): number | null => {\n if (!hasWindow()) return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n } catch {\n return null;\n }\n};\n\n/** Persists a dismissal. Silently a no-op when storage is unavailable. */\nexport const writeDismissedAt = (storageKey: string, at: number): void => {\n if (!hasWindow()) return;\n\n try {\n window.localStorage.setItem(storageKey, String(at));\n } catch {\n // Storage blocked — the dismissal lasts for this page view only.\n }\n};\n\n/**\n * Whether a stored dismissal still suppresses the prompt.\n *\n * `dismissForDays: 0` means forever. A timestamp in the future (a device whose\n * clock was wound back) is treated as active rather than expired, so a bad\n * clock cannot turn into a prompt on every page view.\n */\nexport const isDismissalActive = (\n dismissedAt: number | null,\n dismissForDays: number,\n now: number,\n): boolean => {\n if (dismissedAt === null) return false;\n if (dismissForDays <= 0) return true;\n\n return now - dismissedAt < dismissForDays * MS_PER_DAY;\n};\n\n/** Resolves which install route this browser offers. */\nexport const resolveInstallPlatform = (hasDeferredPrompt: boolean): InstallPlatform => {\n if (hasDeferredPrompt) return 'prompt';\n return isIosSafari() ? 'ios' : 'unsupported';\n};\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"packageManager": "pnpm@10.34.5",
|
|
6
6
|
"type": "module",
|
|
@@ -544,6 +544,14 @@
|
|
|
544
544
|
"./pt-BR": {
|
|
545
545
|
"types": "./dist/types/pt-BR.d.ts",
|
|
546
546
|
"default": "./dist/pt-BR.js"
|
|
547
|
+
},
|
|
548
|
+
"./en-US": {
|
|
549
|
+
"types": "./dist/types/en-US.d.ts",
|
|
550
|
+
"default": "./dist/en-US.js"
|
|
551
|
+
},
|
|
552
|
+
"./locales": {
|
|
553
|
+
"types": "./dist/types/locales.d.ts",
|
|
554
|
+
"default": "./dist/locales.js"
|
|
547
555
|
}
|
|
548
556
|
},
|
|
549
557
|
"typesVersions": {
|
|
@@ -620,6 +628,7 @@
|
|
|
620
628
|
},
|
|
621
629
|
"devDependencies": {
|
|
622
630
|
"@12-apps/eslint-config": "^1.21.1",
|
|
631
|
+
"@12-apps/i18n": "^1.0.0",
|
|
623
632
|
"@12-apps/typescript-config": "^1.20.1",
|
|
624
633
|
"@playwright/test": "^1.55.0",
|
|
625
634
|
"@storybook/addon-docs": "^9.1.10",
|
|
@@ -87,14 +87,14 @@ export function DataGridError(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function DataGridEmpty(
|
|
90
|
-
props: StateProps & { emptyState?: React.ReactNode },
|
|
90
|
+
props: StateProps & { emptyState?: React.ReactNode; emptyText: string },
|
|
91
91
|
): React.JSX.Element {
|
|
92
|
-
const { emptyState, ...rest } = props;
|
|
92
|
+
const { emptyState, emptyText, ...rest } = props;
|
|
93
93
|
return (
|
|
94
94
|
<Placeholder {...rest} flag="data-empty">
|
|
95
95
|
{emptyState ?? (
|
|
96
96
|
<Typography variant="body1" color="text.secondary">
|
|
97
|
-
|
|
97
|
+
{emptyText}
|
|
98
98
|
</Typography>
|
|
99
99
|
)}
|
|
100
100
|
</Placeholder>
|
|
@@ -68,7 +68,7 @@ export const DataGrid = <T extends Record<string, unknown> = Record<string, unkn
|
|
|
68
68
|
if (props.loading) return <DataGridLoading {...placeholder} />;
|
|
69
69
|
if (props.error) return <DataGridError {...placeholder} error={props.error} />;
|
|
70
70
|
if (model.processedRows.length === 0) {
|
|
71
|
-
return <DataGridEmpty {...placeholder} emptyState={props.emptyState} />;
|
|
71
|
+
return <DataGridEmpty {...placeholder} emptyState={props.emptyState} emptyText={props.emptyText} />;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const leadingColumns = (props.selection?.mode ?? 'none') !== 'none' ? 1 : 0;
|
|
@@ -119,6 +119,11 @@ export interface GridEditing<T = Record<string, unknown>> {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export interface DataGridProps<T = Record<string, unknown>> extends React.HTMLAttributes<HTMLElement> {
|
|
122
|
+
/**
|
|
123
|
+
* What an empty result reads as. REQUIRED — the fallback was the English
|
|
124
|
+
* "No data available", on the package every host renders.
|
|
125
|
+
*/
|
|
126
|
+
emptyText: string;
|
|
122
127
|
/** Dataset (client mode uses this array; server mode renders current page) */
|
|
123
128
|
rows: T[];
|
|
124
129
|
/** Stable row id accessor */
|
|
@@ -84,6 +84,8 @@ export function PillControl<T extends Record<string, unknown>>({
|
|
|
84
84
|
}
|
|
85
85
|
return (
|
|
86
86
|
<MultiSelectDropdown
|
|
87
|
+
extraOptionsHeading={copy.filters.optionsHeading}
|
|
88
|
+
clearText={copy.filters.clear}
|
|
87
89
|
clearLabel={copy.filters.clearRange(pill.label)}
|
|
88
90
|
label={pill.label}
|
|
89
91
|
options={pill.options}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CategorySelectCopy, ConfirmActionCopy } from "../../../copy";
|
|
1
|
+
import type { CategorySelectCopy, ConfirmActionCopy, TableFilterCopy } from "../../../copy";
|
|
2
2
|
import type { SavedViewsLabels } from "../../layout/ContentToolbar/ContentToolbar.types";
|
|
3
3
|
/**
|
|
4
4
|
* Every word the DataViews family renders, as REQUIRED host config (FUT-760).
|
|
@@ -177,6 +177,16 @@ export interface DataViewsFiltersCopy {
|
|
|
177
177
|
* dates alone — and getting it wrong silently drops or adds a day's rows.
|
|
178
178
|
*/
|
|
179
179
|
rangeInclusiveNote: string;
|
|
180
|
+
/** The FROM half of a range, in the box itself. */
|
|
181
|
+
rangeStart: string;
|
|
182
|
+
/**
|
|
183
|
+
* The day field's mask, which IS its placeholder — so the expected ORDER is
|
|
184
|
+
* on screen before the first keystroke rather than discovered by getting it
|
|
185
|
+
* wrong. `dd/mm/aaaa` is one locale's order and one locale's letters.
|
|
186
|
+
*/
|
|
187
|
+
dayMask: string;
|
|
188
|
+
/** Drop every applied filter, spelled out in the panel where there is room. */
|
|
189
|
+
clearAllFilters: string;
|
|
180
190
|
/** The day-range presets' labels, keyed by preset id. */
|
|
181
191
|
rangePresets: Readonly<Record<string, string>>;
|
|
182
192
|
scopesLabel: string;
|
|
@@ -197,6 +207,11 @@ export interface DataViewsFiltersCopy {
|
|
|
197
207
|
allOption: string;
|
|
198
208
|
optionSearchPlaceholder: string;
|
|
199
209
|
optionsEmpty: string;
|
|
210
|
+
/**
|
|
211
|
+
* The heading over a dropdown's EXTRA toggles — the section below the option
|
|
212
|
+
* list, not the list itself. It only appears when a pill supplies extras.
|
|
213
|
+
*/
|
|
214
|
+
optionsHeading: string;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
export interface DataViewsSelectionCopy {
|
|
@@ -233,6 +248,10 @@ export interface DataViewsNavCopy {
|
|
|
233
248
|
updateFailed: string;
|
|
234
249
|
/** The dismiss on that failure's alert — a glyph with no visible label. */
|
|
235
250
|
dismissError: string;
|
|
251
|
+
/** The filter panel's back arrow — a glyph with no visible label. */
|
|
252
|
+
goBack: string;
|
|
253
|
+
/** The label in front of the page-size select. */
|
|
254
|
+
pageSize: string;
|
|
236
255
|
saveFailed: string;
|
|
237
256
|
/** Reopen the save dialog over an existing view. */
|
|
238
257
|
editView: string;
|
|
@@ -267,6 +286,13 @@ export interface DataViewsCopy {
|
|
|
267
286
|
savedViewsMenu: SavedViewsLabels;
|
|
268
287
|
confirmAction: ConfirmActionCopy;
|
|
269
288
|
categorySelect: CategorySelectCopy;
|
|
289
|
+
/**
|
|
290
|
+
* The filter shell's own words. DataViews mounts three of its parts — the
|
|
291
|
+
* panel, the keyword box and the numeric range field — so all five are on
|
|
292
|
+
* screen; the range's two ends are `Mínimo`/`Máximo` rather than the day
|
|
293
|
+
* range's `De`/`Até`, which is why this cannot be derived from `filters`.
|
|
294
|
+
*/
|
|
295
|
+
tableFilter: TableFilterCopy;
|
|
270
296
|
board: DataViewsBoardCopy;
|
|
271
297
|
deleteView: DeleteViewCopy;
|
|
272
298
|
manageViews: ManageViewsCopy;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { TextField } from '@mui/material';
|
|
31
31
|
import React, { useEffect, useState } from 'react';
|
|
32
32
|
|
|
33
|
+
|
|
33
34
|
/** `AAAA-MM-DD` (the wire) → `dd/mm/aaaa` (what the merchant reads). */
|
|
34
35
|
export function isoToBr(iso: string): string {
|
|
35
36
|
const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
@@ -112,6 +113,7 @@ export function DayBoundInput({
|
|
|
112
113
|
testId,
|
|
113
114
|
error = false,
|
|
114
115
|
describedBy,
|
|
116
|
+
mask,
|
|
115
117
|
}: {
|
|
116
118
|
label: string;
|
|
117
119
|
/** The applied bound, `AAAA-MM-DD`, or undefined when this end is open. */
|
|
@@ -127,6 +129,17 @@ export function DayBoundInput({
|
|
|
127
129
|
error?: boolean;
|
|
128
130
|
/** Id of the element carrying that complaint, so it is read WITH the field. */
|
|
129
131
|
describedBy?: string;
|
|
132
|
+
/**
|
|
133
|
+
* The day mask, which IS the placeholder — so the expected ORDER is on
|
|
134
|
+
* screen before the first keystroke rather than discovered by getting it
|
|
135
|
+
* wrong. `dd/mm/aaaa` is one locale's order and one locale's letters.
|
|
136
|
+
*
|
|
137
|
+
* A PROP rather than a context read: this field has two callers, and only
|
|
138
|
+
* one of them is inside DataViews. `DateRangePicker` mounts it standalone
|
|
139
|
+
* with its own message table, so a context read here throws for every host
|
|
140
|
+
* of that picker — which is exactly what it did.
|
|
141
|
+
*/
|
|
142
|
+
mask: string;
|
|
130
143
|
}): React.JSX.Element {
|
|
131
144
|
const applied = value ?? '';
|
|
132
145
|
const [text, setText] = useState(() => isoToBr(applied));
|
|
@@ -146,9 +159,7 @@ export function DayBoundInput({
|
|
|
146
159
|
type="text"
|
|
147
160
|
label={label}
|
|
148
161
|
error={error}
|
|
149
|
-
|
|
150
|
-
// the first keystroke rather than discovered by getting it wrong.
|
|
151
|
-
placeholder="dd/mm/aaaa"
|
|
162
|
+
placeholder={mask}
|
|
152
163
|
value={text}
|
|
153
164
|
onChange={(event) => {
|
|
154
165
|
const next = maskBrDate(event.target.value);
|
|
@@ -159,6 +159,7 @@ export function FilterDialog<T extends Record<string, unknown>>({
|
|
|
159
159
|
open={open}
|
|
160
160
|
onClose={onClose}
|
|
161
161
|
navigationTitle={copy.filters.panelTitle}
|
|
162
|
+
backLabel={copy.nav.goBack}
|
|
162
163
|
modalId={`${props.testIdPrefix}-filters`}
|
|
163
164
|
maxWidth="xs"
|
|
164
165
|
dataTestId={`${props.testIdPrefix}-filter-dialog`}
|
|
@@ -174,7 +175,7 @@ export function FilterDialog<T extends Record<string, unknown>>({
|
|
|
174
175
|
data-testid={`${props.testIdPrefix}-clear-filters`}
|
|
175
176
|
sx={{ fontSize: "0.75rem", color: "text.secondary" }}
|
|
176
177
|
>
|
|
177
|
-
|
|
178
|
+
{copy.filters.clearAllFilters}
|
|
178
179
|
</Link>
|
|
179
180
|
</Box>
|
|
180
181
|
<FilterControls {...props} />
|
|
@@ -19,6 +19,7 @@ import { SelectAllStrip } from "./data-views-select-all-strip";
|
|
|
19
19
|
import { ListCardGroup, type ListGroupConfig } from "./list-card-rails";
|
|
20
20
|
import type { DataViewCardSelection } from "./data-views-types";
|
|
21
21
|
import type { DataViewsController } from "./use-data-views-state";
|
|
22
|
+
import { useDataViewsCopy } from "./data-views-copy-context";
|
|
22
23
|
|
|
23
24
|
/* ── Body (grid) ─────────────────────────────────────────────────────────── */
|
|
24
25
|
|
|
@@ -52,6 +53,7 @@ function GridBody<T extends Record<string, unknown>>({
|
|
|
52
53
|
dataTestId,
|
|
53
54
|
emptyState,
|
|
54
55
|
}: GridBodyProps<T>): React.JSX.Element {
|
|
56
|
+
const copy = useDataViewsCopy();
|
|
55
57
|
return (
|
|
56
58
|
<Box
|
|
57
59
|
sx={{
|
|
@@ -77,6 +79,7 @@ function GridBody<T extends Record<string, unknown>>({
|
|
|
77
79
|
sorting={{ mode: sortMode, sortBy, onChangeSortBy }}
|
|
78
80
|
data-testid={dataTestId}
|
|
79
81
|
emptyState={emptyState}
|
|
82
|
+
emptyText={copy.grid.emptyFilteredTitle}
|
|
80
83
|
/>
|
|
81
84
|
</Box>
|
|
82
85
|
);
|
|
@@ -20,6 +20,7 @@ import { toOverflowFields, useFilterOverflow, type OverflowSplit } from "./data-
|
|
|
20
20
|
import { ShellToolbar } from "./data-views-shell-toolbar";
|
|
21
21
|
import type { DisplayPanelView } from "./data-views-display-panel";
|
|
22
22
|
import type { DataViewExport } from "./data-views-export";
|
|
23
|
+
import { useDataViewsCopy } from "./data-views-copy-context";
|
|
23
24
|
import { DataViewsEmpty } from "./data-views-empty";
|
|
24
25
|
import { DataViewsPagination } from "./data-views-pagination";
|
|
25
26
|
import type { BoardConfig } from "./DataViewsBoard";
|
|
@@ -286,6 +287,7 @@ function ShellStack<T extends Record<string, unknown>>({
|
|
|
286
287
|
props: GridShellProps<T>;
|
|
287
288
|
filters: ReturnType<typeof useGridShellFilters<T>>;
|
|
288
289
|
}): React.JSX.Element {
|
|
290
|
+
const copy = useDataViewsCopy();
|
|
289
291
|
const { c, testIdPrefix, dataTestId, scopes = [], emptyState, inlineFilters = false } = props;
|
|
290
292
|
const { showInline, useModal, inlineVisible, filterProps, split, onControlOpenChange } = filters;
|
|
291
293
|
// The grid renders the FILTERED empty state itself — it is the only party
|
|
@@ -299,7 +301,10 @@ function ShellStack<T extends Record<string, unknown>>({
|
|
|
299
301
|
/>
|
|
300
302
|
);
|
|
301
303
|
return (
|
|
302
|
-
<TableFilter
|
|
304
|
+
<TableFilter
|
|
305
|
+
copy={copy.tableFilter} open={c.filterOpen} onOpenChange={c.setFilterOpen}
|
|
306
|
+
hasActiveFilters={c.activeFilterCount > 0}
|
|
307
|
+
>
|
|
303
308
|
<Stack spacing={0} data-testid={dataTestId ? `${dataTestId}-container` : undefined}>
|
|
304
309
|
<GridHeaderRow title={props.title} headerActions={props.headerActions} testIdPrefix={testIdPrefix} />
|
|
305
310
|
<ShellToolbar
|
|
@@ -311,9 +316,7 @@ function ShellStack<T extends Record<string, unknown>>({
|
|
|
311
316
|
filterControls={
|
|
312
317
|
inlineVisible ? (
|
|
313
318
|
<InlineFilterControls
|
|
314
|
-
{...filterProps}
|
|
315
|
-
split={split}
|
|
316
|
-
activeFilterCount={c.activeFilterCount}
|
|
319
|
+
{...filterProps} split={split} activeFilterCount={c.activeFilterCount}
|
|
317
320
|
onControlOpenChange={onControlOpenChange}
|
|
318
321
|
/>
|
|
319
322
|
) : undefined
|
|
@@ -328,12 +331,8 @@ function ShellStack<T extends Record<string, unknown>>({
|
|
|
328
331
|
showInline={showInline}
|
|
329
332
|
/>
|
|
330
333
|
<ScopeTabsSlot
|
|
331
|
-
scopes={scopes}
|
|
332
|
-
|
|
333
|
-
board={props.board}
|
|
334
|
-
value={c.scope}
|
|
335
|
-
onChange={c.setScope}
|
|
336
|
-
counts={c.scopeCounts}
|
|
334
|
+
scopes={scopes} scopeFieldId={props.scopeFieldId} board={props.board}
|
|
335
|
+
value={c.scope} onChange={c.setScope} counts={c.scopeCounts}
|
|
337
336
|
testIdPrefix={testIdPrefix}
|
|
338
337
|
/>
|
|
339
338
|
<TableFilter.Layout>
|
|
@@ -60,7 +60,9 @@ function OverflowPill<T extends Record<string, unknown>>({
|
|
|
60
60
|
if (options.length > INLINE_OPTION_LIMIT) {
|
|
61
61
|
return (
|
|
62
62
|
<MultiSelectDropdown
|
|
63
|
-
|
|
63
|
+
extraOptionsHeading={copy.filters.optionsHeading}
|
|
64
|
+
clearText={copy.filters.clear}
|
|
65
|
+
clearLabel={copy.filters.clearRange(field.label)}
|
|
64
66
|
label={field.pill?.label ?? field.label}
|
|
65
67
|
options={options}
|
|
66
68
|
selected={new Set(values)}
|
|
@@ -4,6 +4,7 @@ import { Box } from "../../../mui/Box";
|
|
|
4
4
|
import { Pagination } from "../../navigation/Pagination";
|
|
5
5
|
|
|
6
6
|
import type { DataViewsController } from "./use-data-views-state";
|
|
7
|
+
import { useDataViewsCopy } from "./data-views-copy-context";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The server-mode pagination control rendered beneath the grid — shown only
|
|
@@ -17,10 +18,12 @@ export function DataViewsPagination<T extends Record<string, unknown>>({
|
|
|
17
18
|
c: DataViewsController<T>;
|
|
18
19
|
testIdPrefix: string;
|
|
19
20
|
}): React.JSX.Element | null {
|
|
21
|
+
const copy = useDataViewsCopy();
|
|
20
22
|
if (!c.serverMode || c.serverPageCount <= 1) return null;
|
|
21
23
|
return (
|
|
22
24
|
<Box sx={{ mt: 2, display: "flex", justifyContent: "center" }}>
|
|
23
25
|
<Pagination
|
|
26
|
+
pageSizeLabel={copy.nav.pageSize}
|
|
24
27
|
page={c.serverPage}
|
|
25
28
|
count={c.serverPageCount}
|
|
26
29
|
onChange={(_event, page) => c.changePage(page)}
|
|
@@ -52,9 +52,11 @@ function BoundField<T extends Record<string, unknown>>({
|
|
|
52
52
|
onChange: (bound: number | string | undefined) => void;
|
|
53
53
|
testId: string;
|
|
54
54
|
}): React.JSX.Element {
|
|
55
|
+
const copy = useDataViewsCopy();
|
|
55
56
|
if (field.kind === 'day') {
|
|
56
57
|
return (
|
|
57
58
|
<DayBoundInput
|
|
59
|
+
mask={copy.filters.dayMask}
|
|
58
60
|
label={label}
|
|
59
61
|
value={value == null ? undefined : String(value)}
|
|
60
62
|
onChange={onChange}
|
|
@@ -102,7 +104,7 @@ export function RangeBounds<T extends Record<string, unknown>>({
|
|
|
102
104
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
103
105
|
<BoundField
|
|
104
106
|
field={field}
|
|
105
|
-
label=
|
|
107
|
+
label={copy.filters.rangeStart}
|
|
106
108
|
value={value.min}
|
|
107
109
|
onChange={(min) => onChange({ ...value, min })}
|
|
108
110
|
testId={`${testId}-min`}
|
|
@@ -153,6 +153,8 @@ export function GridToolbar<T extends Record<string, unknown>>(props: GridToolba
|
|
|
153
153
|
>
|
|
154
154
|
<ContentToolbar
|
|
155
155
|
selectAllLabel={copy.selection.selectAll}
|
|
156
|
+
selectAllText={copy.selection.selectAllOnPage}
|
|
157
|
+
clearAllText={copy.filters.clearAll}
|
|
156
158
|
hasSelection={selectedRows.length > 0}
|
|
157
159
|
selectedCount={selectedRows.length}
|
|
158
160
|
selectAll={props.selectAll}
|
|
@@ -19,7 +19,14 @@ const testId = (base: string | undefined, suffix: string, fallback: string) =>
|
|
|
19
19
|
export interface HoverCardContentProps
|
|
20
20
|
extends Pick<
|
|
21
21
|
HoverCardProps,
|
|
22
|
-
|
|
22
|
+
| 'variant'
|
|
23
|
+
| 'title'
|
|
24
|
+
| 'description'
|
|
25
|
+
| 'avatar'
|
|
26
|
+
| 'loading'
|
|
27
|
+
| 'loadingComponent'
|
|
28
|
+
| 'loadingText'
|
|
29
|
+
| 'children'
|
|
23
30
|
> {
|
|
24
31
|
dataTestId?: string;
|
|
25
32
|
}
|
|
@@ -113,6 +120,7 @@ const DetailedContent: React.FC<HoverCardContentProps> = ({
|
|
|
113
120
|
export const HoverCardContent: React.FC<HoverCardContentProps> = (props) => {
|
|
114
121
|
const { variant, title, description, avatar, loading, loadingComponent, children, dataTestId } =
|
|
115
122
|
props;
|
|
123
|
+
const { loadingText } = props;
|
|
116
124
|
|
|
117
125
|
if (loading) {
|
|
118
126
|
return (
|
|
@@ -120,7 +128,7 @@ export const HoverCardContent: React.FC<HoverCardContentProps> = (props) => {
|
|
|
120
128
|
{loadingComponent || (
|
|
121
129
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
122
130
|
<CircularProgress size={20} />
|
|
123
|
-
<Typography variant="body2">
|
|
131
|
+
<Typography variant="body2">{loadingText}</Typography>
|
|
124
132
|
</Box>
|
|
125
133
|
)}
|
|
126
134
|
</LoadingContainer>
|
|
@@ -62,7 +62,7 @@ const offsetMargin = (placement: HoverCardPlacement, offset: number) => {
|
|
|
62
62
|
export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props, ref) => {
|
|
63
63
|
const {
|
|
64
64
|
variant, glow, pulse, title, description, avatar, trigger, placement, showArrow,
|
|
65
|
-
animation, enterDelay, exitDelay, maxWidth, loading, loadingComponent, touchEnabled,
|
|
65
|
+
animation, enterDelay, exitDelay, maxWidth, loading, loadingComponent, loadingText, touchEnabled,
|
|
66
66
|
offset, disabled, onOpen, onClose, children, dataTestId,
|
|
67
67
|
...rest
|
|
68
68
|
} = withDefaults(props, DEFAULTS) as ResolvedProps;
|
|
@@ -122,8 +122,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
|
|
|
122
122
|
>
|
|
123
123
|
<CardContent sx={{ p: padding, '&:last-child': { pb: padding } }}>
|
|
124
124
|
<HoverCardContent
|
|
125
|
-
variant={variant}
|
|
126
|
-
title={title}
|
|
125
|
+
loadingText={loadingText} variant={variant} title={title}
|
|
127
126
|
description={description}
|
|
128
127
|
avatar={avatar}
|
|
129
128
|
loading={loading}
|
|
@@ -9,6 +9,8 @@ export type HoverCardPlacement =
|
|
|
9
9
|
export type HoverCardAnimation = 'fade' | 'scale' | 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right';
|
|
10
10
|
|
|
11
11
|
export interface HoverCardProps extends Omit<MuiPopoverProps, 'variant' | 'open' | 'anchorEl'> {
|
|
12
|
+
/** What the card says while its content is in flight. REQUIRED. */
|
|
13
|
+
loadingText: string;
|
|
12
14
|
/**
|
|
13
15
|
* The variant of the hover card
|
|
14
16
|
*/
|
|
@@ -299,10 +299,13 @@ const TableLoadingState: React.FC<{
|
|
|
299
299
|
const TableEmptyState: React.FC<{
|
|
300
300
|
shell: TableShellProps;
|
|
301
301
|
emptyStateComponent?: React.ReactNode;
|
|
302
|
+
emptyText: string;
|
|
302
303
|
colSpan: number;
|
|
303
|
-
}> = ({ shell, emptyStateComponent, colSpan }) => (
|
|
304
|
+
}> = ({ shell, emptyStateComponent, emptyText, colSpan }) => (
|
|
304
305
|
<TableShell {...shell}>
|
|
305
|
-
<EmptyRow colSpan={colSpan}>
|
|
306
|
+
<EmptyRow colSpan={colSpan}>
|
|
307
|
+
{emptyStateComponent || <NoDataPlaceholder emptyText={emptyText} />}
|
|
308
|
+
</EmptyRow>
|
|
306
309
|
</TableShell>
|
|
307
310
|
);
|
|
308
311
|
|
|
@@ -316,6 +319,7 @@ export const Table = React.forwardRef<globalThis.HTMLTableElement, TableProps>(
|
|
|
316
319
|
loading,
|
|
317
320
|
loadingComponent,
|
|
318
321
|
emptyStateComponent,
|
|
322
|
+
emptyText,
|
|
319
323
|
children,
|
|
320
324
|
...props
|
|
321
325
|
} = resolved;
|
|
@@ -345,6 +349,7 @@ export const Table = React.forwardRef<globalThis.HTMLTableElement, TableProps>(
|
|
|
345
349
|
<TableEmptyState
|
|
346
350
|
shell={shell}
|
|
347
351
|
emptyStateComponent={emptyStateComponent}
|
|
352
|
+
emptyText={emptyText}
|
|
348
353
|
colSpan={finalColumns?.length || 1}
|
|
349
354
|
/>
|
|
350
355
|
);
|
|
@@ -62,6 +62,11 @@ export interface SelectionConfig {
|
|
|
62
62
|
export type TableStripeColor = ColorValue;
|
|
63
63
|
|
|
64
64
|
export interface TableProps extends Omit<MuiTableProps, 'variant'> {
|
|
65
|
+
/**
|
|
66
|
+
* What an empty result reads as. REQUIRED — the fallback was the English
|
|
67
|
+
* "No data available", on the package every host renders.
|
|
68
|
+
*/
|
|
69
|
+
emptyText: string;
|
|
65
70
|
/**
|
|
66
71
|
* The variant of the table
|
|
67
72
|
*/
|
|
@@ -2,9 +2,9 @@ import { Box, TableBody, TableCell, TableRow } from '@mui/material';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
export const NoDataPlaceholder: React.FC = () => (
|
|
5
|
+
export const NoDataPlaceholder: React.FC<{ emptyText: string }> = ({ emptyText }) => (
|
|
6
6
|
<Box py={4} color="text.secondary">
|
|
7
|
-
|
|
7
|
+
{emptyText}
|
|
8
8
|
</Box>
|
|
9
9
|
);
|
|
10
10
|
|