@12-apps/ui 6.93.2 → 6.95.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/NATIVE.md +1 -1
- package/components-catalog.md +7 -0
- package/dist/_internal/components/data-display/Avatar/Avatar.js +2 -2
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +1 -1
- package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +1 -1
- package/dist/_internal/components/data-display/Carousel/Carousel.js +3 -3
- package/dist/_internal/components/data-display/Chart/SpecChart.js +3 -3
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +27 -27
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +36 -36
- package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +1 -1
- package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +3 -3
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +2 -2
- package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +2 -2
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +2 -2
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +10 -10
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +25 -25
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +7 -7
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +7 -7
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +6 -6
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +2 -2
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
- package/dist/_internal/components/data-display/DayRuler/DayRuler.js +3 -3
- package/dist/_internal/components/data-display/HoverCard/HoverCard.js +1 -1
- package/dist/_internal/components/data-display/Lightbox/Lightbox.js +5 -5
- package/dist/_internal/components/data-display/Lightbox/LightboxControls.js +2 -2
- package/dist/_internal/components/data-display/Lightbox/LightboxOverlay.js +3 -3
- package/dist/_internal/components/data-display/Lightbox/LightboxThumbnails.js +2 -2
- package/dist/_internal/components/data-display/RegionMap/RegionMap.js +2 -2
- package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.js +1 -1
- package/dist/_internal/components/data-display/Sheet/Sheet.body.js +2 -2
- package/dist/_internal/components/data-display/Sheet/Sheet.hooks.js +2 -2
- package/dist/_internal/components/data-display/Sheet/Sheet.js +5 -5
- package/dist/_internal/components/data-display/Sheet/Sheet.parts.js +1 -1
- package/dist/_internal/components/data-display/Table/Table.js +3 -3
- package/dist/_internal/components/data-display/index.js +39 -39
- package/dist/_internal/components/feedback/StackedModal/StackedModal.hooks.js +2 -2
- package/dist/_internal/components/feedback/StackedModal/StackedModal.js +4 -4
- package/dist/_internal/components/feedback/StackedModal/modal-stack.js +3 -1
- package/dist/_internal/components/feedback/TutorialOverlay/TutorialOverlay.js +2 -2
- package/dist/_internal/components/feedback/UsableModal/UsableModal.js +11 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.js.map +1 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.modal.js +87 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.modal.js.map +1 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.sheet.js +91 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.sheet.js.map +1 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.types.js +1 -0
- package/dist/_internal/components/feedback/UsableModal/UsableModal.types.js.map +1 -0
- package/dist/_internal/components/feedback/UsableModal/has-footer.js +8 -0
- package/dist/_internal/components/feedback/UsableModal/has-footer.js.map +1 -0
- package/dist/_internal/components/feedback/UsableModal/recover-chunk.js +10 -0
- package/dist/_internal/components/feedback/UsableModal/recover-chunk.js.map +1 -0
- package/dist/_internal/components/feedback/index.js +9 -9
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.hooks.js +3 -3
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.js +5 -5
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocompleteField.js +2 -2
- package/dist/_internal/components/form/CategorySelect/CategorySelect.js +2 -2
- package/dist/_internal/components/form/CepField/CepField.js +3 -3
- package/dist/_internal/components/form/CodeEditor/CodeEditor.js +2 -2
- package/dist/_internal/components/form/Command/Command.js +2 -2
- package/dist/_internal/components/form/Input/Input.factory.js +1 -1
- package/dist/_internal/components/form/Input/Input.js +2 -2
- package/dist/_internal/components/form/Menubar/Menubar.js +2 -2
- package/dist/_internal/components/form/NumberField/NumberField.js +3 -3
- package/dist/_internal/components/form/UploadButton/UploadButton.js +2 -2
- package/dist/_internal/components/form/index.js +48 -48
- package/dist/_internal/components/form/total-form/Fields/CategoryField.js +3 -3
- package/dist/_internal/components/form/total-form/Fields/CepField.js +4 -4
- package/dist/_internal/components/form/total-form/Fields/TextField.js +3 -3
- package/dist/_internal/components/form/total-form/Fields/index.js +15 -15
- package/dist/_internal/components/index.js +130 -130
- package/dist/_internal/components/layout/SettingCard/SettingGroup.js +3 -3
- package/dist/_internal/components/layout/SettingCard/SettingToggle.js +3 -3
- package/dist/_internal/components/layout/SettingsLayout/SettingsLayout.js +3 -3
- package/dist/_internal/components/layout/SettingsLayout/SettingsPanel.js +2 -2
- package/dist/_internal/components/layout/index.js +17 -17
- package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +6 -6
- package/dist/_internal/components/navigation/NavigationMenu/NavigationMegaSection.js +2 -2
- package/dist/_internal/components/navigation/NavigationMenu/NavigationMenu.js +4 -4
- package/dist/_internal/components/navigation/NavigationMenu/NavigationStandardBody.js +2 -2
- package/dist/_internal/components/navigation/SectionNav/SectionNav.js +3 -3
- package/dist/_internal/components/navigation/index.js +19 -19
- package/dist/_internal/components/utility/index.js +4 -4
- package/dist/_internal/user-avatar/user-avatar.js +3 -3
- package/dist/charts.js +11 -11
- package/dist/{chunk-NQDOKJLI.js → chunk-2365CQJU.js} +2 -2
- package/dist/{chunk-4E2RQOSQ.js → chunk-24G3BWKD.js} +2 -2
- package/dist/{chunk-XE33EZVS.js → chunk-2SBQ47RI.js} +5 -5
- package/dist/{chunk-HWZETMO4.js → chunk-37SNQQ7P.js} +3 -3
- package/dist/{chunk-I4DG3PP4.js → chunk-43EFQ3DU.js} +2 -2
- package/dist/{chunk-2L6TANRY.js → chunk-4RF4BALN.js} +3 -3
- package/dist/{chunk-X6D5JEUV.js → chunk-4RSFYS2V.js} +6 -6
- package/dist/{chunk-LYVTJLCF.js → chunk-56JF5PZK.js} +4 -4
- package/dist/{chunk-7WE432RQ.js → chunk-5HXVXW22.js} +5 -5
- package/dist/{chunk-N3GLX64O.js → chunk-5I75WRXT.js} +4 -4
- package/dist/{chunk-BP3ZKIPE.js → chunk-64UNVW6D.js} +4 -4
- package/dist/{chunk-NQ6P7O7C.js → chunk-6AKDAQSS.js} +2 -2
- package/dist/{chunk-PGJIWCQR.js → chunk-6EZQLNPK.js} +4 -4
- package/dist/{chunk-EE6DJPB7.js → chunk-6HXNFNOB.js} +4 -4
- package/dist/{chunk-4QTPERAY.js → chunk-6RW4WNGS.js} +4 -4
- package/dist/{chunk-GBB23QLL.js → chunk-6ZAKPYWX.js} +4 -4
- package/dist/{chunk-CWXX5JR3.js → chunk-75GAQL2Q.js} +5 -5
- package/dist/{chunk-JZRSHDNY.js → chunk-77FQFWA3.js} +6 -3
- package/dist/chunk-77FQFWA3.js.map +1 -0
- package/dist/{chunk-LY5I3ZSV.js → chunk-A2YLFKQU.js} +4 -4
- package/dist/{chunk-6DGNMASB.js → chunk-ALREWFRX.js} +4 -4
- package/dist/{chunk-FXECKKPW.js → chunk-ASBFGGJB.js} +6 -1
- package/dist/chunk-ASBFGGJB.js.map +1 -0
- package/dist/{chunk-F2UP5ZF4.js → chunk-BMC5KGH2.js} +4 -4
- package/dist/{chunk-BR3WBTTQ.js → chunk-BRFL7OWI.js} +7 -7
- package/dist/chunk-BTMBQIED.js +7 -0
- package/dist/chunk-BTMBQIED.js.map +1 -0
- package/dist/{chunk-UIIHUWOV.js → chunk-C7IFBYIJ.js} +7 -7
- package/dist/{chunk-FKS2PNJF.js → chunk-CSD6SWBZ.js} +6 -6
- package/dist/{chunk-6LGKJVOA.js → chunk-E2UOEI7N.js} +2 -2
- package/dist/{chunk-K2VDRODL.js → chunk-E4NFH77Z.js} +4 -4
- package/dist/{chunk-VM4UYCD4.js → chunk-FTYOCFTV.js} +3 -3
- package/dist/{chunk-JVQ56E3U.js → chunk-FWTVIRZZ.js} +6 -6
- package/dist/{chunk-3IX2TP4I.js → chunk-GB7NTXL2.js} +2 -2
- package/dist/chunk-IQDPMLDM.js +39 -0
- package/dist/chunk-IQDPMLDM.js.map +1 -0
- package/dist/{chunk-TSMF7K37.js → chunk-J4UA744J.js} +9 -2
- package/dist/chunk-J4UA744J.js.map +1 -0
- package/dist/chunk-J55SDNCE.js +53 -0
- package/dist/chunk-J55SDNCE.js.map +1 -0
- package/dist/{chunk-4J62V5OE.js → chunk-JIEVKHOH.js} +6 -6
- package/dist/{chunk-MKNW4FRR.js → chunk-JOQGKXR5.js} +5 -5
- package/dist/{chunk-E5OBAS3U.js → chunk-JQGDKWFH.js} +4 -2
- package/dist/chunk-JQGDKWFH.js.map +1 -0
- package/dist/{chunk-ASEHJIIV.js → chunk-KHDKG7XB.js} +3 -3
- package/dist/{chunk-TSID4257.js → chunk-KMMFN24G.js} +4 -4
- package/dist/{chunk-YGWLMTPB.js → chunk-KXVWH3JM.js} +2 -2
- package/dist/{chunk-LKWXU6NH.js → chunk-LV6AD6XC.js} +2 -2
- package/dist/{chunk-WQO5WONV.js → chunk-MWXZXCVX.js} +4 -4
- package/dist/{chunk-PNZH6CDD.js → chunk-NI3MC742.js} +4 -4
- package/dist/{chunk-BZYLTGOE.js → chunk-OKDKKBLI.js} +4 -4
- package/dist/{chunk-AJ6KFHEE.js → chunk-PJDAZG65.js} +2 -2
- package/dist/{chunk-NASLJO2R.js → chunk-QSZYQZZP.js} +3 -2
- package/dist/chunk-QSZYQZZP.js.map +1 -0
- package/dist/{chunk-ALMGYCHY.js → chunk-RLQVJXGW.js} +2 -2
- package/dist/{chunk-FPNVOB52.js → chunk-RNIJL54W.js} +4 -4
- package/dist/{chunk-2M3W6ACW.js → chunk-S46LGAQ2.js} +4 -4
- package/dist/{chunk-6N2U67KF.js → chunk-T3KVZTO3.js} +4 -4
- package/dist/{chunk-GY53SDVL.js → chunk-U2SAW6DH.js} +4 -4
- package/dist/{chunk-GXGHEOWC.js → chunk-USHVG4UQ.js} +2 -2
- package/dist/{chunk-2GDPZ53K.js → chunk-UTWEHMAP.js} +2 -2
- package/dist/{chunk-P772HCHI.js → chunk-W676GY42.js} +4 -4
- package/dist/{chunk-4JNC5AEF.js → chunk-WGKBZ7DN.js} +2 -2
- package/dist/{chunk-T2G4LDU5.js → chunk-WLLUARTC.js} +2 -2
- package/dist/{chunk-SI5SKX52.js → chunk-WW3F7DCX.js} +2 -2
- package/dist/{chunk-UOKXYV3U.js → chunk-XALDXR3F.js} +2 -2
- package/dist/{chunk-ZM6FJCWS.js → chunk-XNAI7QGU.js} +4 -4
- package/dist/{chunk-LZ5PGB7U.js → chunk-XRC6SF4N.js} +21 -13
- package/dist/chunk-XRC6SF4N.js.map +1 -0
- package/dist/{chunk-JXK53XJW.js → chunk-YOLLISEY.js} +9 -9
- package/dist/{chunk-FKUVFPQZ.js → chunk-Z6QFIRD7.js} +4 -4
- package/dist/{chunk-RHVJ3L4U.js → chunk-ZJY7OK6H.js} +2 -2
- package/dist/{chunk-LABIUXDM.js → chunk-ZYXXAWDF.js} +2 -2
- package/dist/data-display/Avatar.js +2 -2
- package/dist/data-display/CardKit.js +1 -1
- package/dist/data-display/Carousel.js +3 -3
- package/dist/data-display/Chart.js +11 -11
- package/dist/data-display/DataViews.js +44 -44
- package/dist/data-display/DayRuler.js +3 -3
- package/dist/data-display/HoverCard.js +1 -1
- package/dist/data-display/Lightbox.js +5 -5
- package/dist/data-display/RegionMap.js +2 -2
- package/dist/data-display/ScheduleGrid.js +5 -5
- package/dist/data-display/Sheet.js +5 -5
- package/dist/data-display/Table.js +3 -3
- package/dist/feedback/ConfirmAction.js +3 -3
- package/dist/feedback/StackedModal.js +4 -4
- package/dist/feedback/TutorialOverlay.js +2 -2
- package/dist/feedback/UsableModal.js +9 -0
- package/dist/feedback/UsableModal.js.map +1 -0
- package/dist/form/AddressAutocomplete.js +5 -5
- package/dist/form/CategorySelect.js +2 -2
- package/dist/form/CepField.js +3 -3
- package/dist/form/CodeEditor.js +2 -2
- package/dist/form/Command.js +2 -2
- package/dist/form/Input/outlined.js +2 -2
- package/dist/form/Input.js +2 -2
- package/dist/form/Menubar.js +2 -2
- package/dist/form/NumberField.js +3 -3
- package/dist/form/UploadButton.js +2 -2
- package/dist/form/total-form.js +13 -13
- package/dist/layout/ContentToolbar.js +3 -3
- package/dist/layout/SettingCard.js +7 -7
- package/dist/layout/SettingsLayout.js +3 -3
- package/dist/locales.js +4 -4
- package/dist/navigation/AppHeader.js +8 -8
- package/dist/navigation/NavigationMenu.js +4 -4
- package/dist/navigation/SectionNav.js +3 -3
- package/dist/pt-BR.js +5 -5
- package/dist/types/components/data-display/Sheet/Sheet.body.d.ts +2 -0
- package/dist/types/components/data-display/Sheet/Sheet.body.d.ts.map +1 -1
- package/dist/types/components/data-display/Sheet/Sheet.d.ts.map +1 -1
- package/dist/types/components/data-display/Sheet/Sheet.parts.d.ts.map +1 -1
- package/dist/types/components/data-display/Sheet/Sheet.types.d.ts +14 -0
- package/dist/types/components/data-display/Sheet/Sheet.types.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.hooks.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/modal-stack.d.ts +6 -0
- package/dist/types/components/feedback/StackedModal/modal-stack.d.ts.map +1 -1
- package/dist/types/components/feedback/UsableModal/UsableModal.d.ts +20 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.modal.d.ts +11 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.modal.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.sheet.d.ts +17 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.sheet.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.types.d.ts +35 -0
- package/dist/types/components/feedback/UsableModal/UsableModal.types.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/has-footer.d.ts +4 -0
- package/dist/types/components/feedback/UsableModal/has-footer.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/index.d.ts +3 -0
- package/dist/types/components/feedback/UsableModal/index.d.ts.map +1 -0
- package/dist/types/components/feedback/UsableModal/recover-chunk.d.ts +24 -0
- package/dist/types/components/feedback/UsableModal/recover-chunk.d.ts.map +1 -0
- package/dist/types/components/form/Input/Input.factory.d.ts.map +1 -1
- package/dist/types/components/form/Input/Input.types.d.ts +3 -2
- package/dist/types/components/form/Input/Input.types.d.ts.map +1 -1
- package/dist/user-avatar.js +3 -3
- package/package.json +5 -1
- package/src/components/data-display/Sheet/Sheet.body.tsx +4 -0
- package/src/components/data-display/Sheet/Sheet.md +5 -0
- package/src/components/data-display/Sheet/Sheet.parts.tsx +2 -1
- package/src/components/data-display/Sheet/Sheet.tsx +29 -9
- package/src/components/data-display/Sheet/Sheet.types.ts +14 -0
- package/src/components/feedback/StackedModal/StackedModal.hooks.ts +16 -0
- package/src/components/feedback/StackedModal/modal-stack.tsx +12 -2
- package/src/components/feedback/UsableModal/UsableModal.md +95 -0
- package/src/components/feedback/UsableModal/UsableModal.modal.tsx +76 -0
- package/src/components/feedback/UsableModal/UsableModal.sheet.tsx +65 -0
- package/src/components/feedback/UsableModal/UsableModal.tsx +92 -0
- package/src/components/feedback/UsableModal/UsableModal.types.ts +38 -0
- package/src/components/feedback/UsableModal/has-footer.ts +4 -0
- package/src/components/feedback/UsableModal/index.ts +2 -0
- package/src/components/feedback/UsableModal/recover-chunk.ts +61 -0
- package/src/components/form/Input/Input.factory.tsx +12 -0
- package/src/components/form/Input/Input.types.ts +3 -2
- package/dist/chunk-E5OBAS3U.js.map +0 -1
- package/dist/chunk-FXECKKPW.js.map +0 -1
- package/dist/chunk-JZRSHDNY.js.map +0 -1
- package/dist/chunk-LZ5PGB7U.js.map +0 -1
- package/dist/chunk-NASLJO2R.js.map +0 -1
- package/dist/chunk-TSMF7K37.js.map +0 -1
- /package/dist/{chunk-NQDOKJLI.js.map → chunk-2365CQJU.js.map} +0 -0
- /package/dist/{chunk-4E2RQOSQ.js.map → chunk-24G3BWKD.js.map} +0 -0
- /package/dist/{chunk-XE33EZVS.js.map → chunk-2SBQ47RI.js.map} +0 -0
- /package/dist/{chunk-HWZETMO4.js.map → chunk-37SNQQ7P.js.map} +0 -0
- /package/dist/{chunk-I4DG3PP4.js.map → chunk-43EFQ3DU.js.map} +0 -0
- /package/dist/{chunk-2L6TANRY.js.map → chunk-4RF4BALN.js.map} +0 -0
- /package/dist/{chunk-X6D5JEUV.js.map → chunk-4RSFYS2V.js.map} +0 -0
- /package/dist/{chunk-LYVTJLCF.js.map → chunk-56JF5PZK.js.map} +0 -0
- /package/dist/{chunk-7WE432RQ.js.map → chunk-5HXVXW22.js.map} +0 -0
- /package/dist/{chunk-N3GLX64O.js.map → chunk-5I75WRXT.js.map} +0 -0
- /package/dist/{chunk-BP3ZKIPE.js.map → chunk-64UNVW6D.js.map} +0 -0
- /package/dist/{chunk-NQ6P7O7C.js.map → chunk-6AKDAQSS.js.map} +0 -0
- /package/dist/{chunk-PGJIWCQR.js.map → chunk-6EZQLNPK.js.map} +0 -0
- /package/dist/{chunk-EE6DJPB7.js.map → chunk-6HXNFNOB.js.map} +0 -0
- /package/dist/{chunk-4QTPERAY.js.map → chunk-6RW4WNGS.js.map} +0 -0
- /package/dist/{chunk-GBB23QLL.js.map → chunk-6ZAKPYWX.js.map} +0 -0
- /package/dist/{chunk-CWXX5JR3.js.map → chunk-75GAQL2Q.js.map} +0 -0
- /package/dist/{chunk-LY5I3ZSV.js.map → chunk-A2YLFKQU.js.map} +0 -0
- /package/dist/{chunk-6DGNMASB.js.map → chunk-ALREWFRX.js.map} +0 -0
- /package/dist/{chunk-F2UP5ZF4.js.map → chunk-BMC5KGH2.js.map} +0 -0
- /package/dist/{chunk-BR3WBTTQ.js.map → chunk-BRFL7OWI.js.map} +0 -0
- /package/dist/{chunk-UIIHUWOV.js.map → chunk-C7IFBYIJ.js.map} +0 -0
- /package/dist/{chunk-FKS2PNJF.js.map → chunk-CSD6SWBZ.js.map} +0 -0
- /package/dist/{chunk-6LGKJVOA.js.map → chunk-E2UOEI7N.js.map} +0 -0
- /package/dist/{chunk-K2VDRODL.js.map → chunk-E4NFH77Z.js.map} +0 -0
- /package/dist/{chunk-VM4UYCD4.js.map → chunk-FTYOCFTV.js.map} +0 -0
- /package/dist/{chunk-JVQ56E3U.js.map → chunk-FWTVIRZZ.js.map} +0 -0
- /package/dist/{chunk-3IX2TP4I.js.map → chunk-GB7NTXL2.js.map} +0 -0
- /package/dist/{chunk-4J62V5OE.js.map → chunk-JIEVKHOH.js.map} +0 -0
- /package/dist/{chunk-MKNW4FRR.js.map → chunk-JOQGKXR5.js.map} +0 -0
- /package/dist/{chunk-ASEHJIIV.js.map → chunk-KHDKG7XB.js.map} +0 -0
- /package/dist/{chunk-TSID4257.js.map → chunk-KMMFN24G.js.map} +0 -0
- /package/dist/{chunk-YGWLMTPB.js.map → chunk-KXVWH3JM.js.map} +0 -0
- /package/dist/{chunk-LKWXU6NH.js.map → chunk-LV6AD6XC.js.map} +0 -0
- /package/dist/{chunk-WQO5WONV.js.map → chunk-MWXZXCVX.js.map} +0 -0
- /package/dist/{chunk-PNZH6CDD.js.map → chunk-NI3MC742.js.map} +0 -0
- /package/dist/{chunk-BZYLTGOE.js.map → chunk-OKDKKBLI.js.map} +0 -0
- /package/dist/{chunk-AJ6KFHEE.js.map → chunk-PJDAZG65.js.map} +0 -0
- /package/dist/{chunk-ALMGYCHY.js.map → chunk-RLQVJXGW.js.map} +0 -0
- /package/dist/{chunk-FPNVOB52.js.map → chunk-RNIJL54W.js.map} +0 -0
- /package/dist/{chunk-2M3W6ACW.js.map → chunk-S46LGAQ2.js.map} +0 -0
- /package/dist/{chunk-6N2U67KF.js.map → chunk-T3KVZTO3.js.map} +0 -0
- /package/dist/{chunk-GY53SDVL.js.map → chunk-U2SAW6DH.js.map} +0 -0
- /package/dist/{chunk-GXGHEOWC.js.map → chunk-USHVG4UQ.js.map} +0 -0
- /package/dist/{chunk-2GDPZ53K.js.map → chunk-UTWEHMAP.js.map} +0 -0
- /package/dist/{chunk-P772HCHI.js.map → chunk-W676GY42.js.map} +0 -0
- /package/dist/{chunk-4JNC5AEF.js.map → chunk-WGKBZ7DN.js.map} +0 -0
- /package/dist/{chunk-T2G4LDU5.js.map → chunk-WLLUARTC.js.map} +0 -0
- /package/dist/{chunk-SI5SKX52.js.map → chunk-WW3F7DCX.js.map} +0 -0
- /package/dist/{chunk-UOKXYV3U.js.map → chunk-XALDXR3F.js.map} +0 -0
- /package/dist/{chunk-ZM6FJCWS.js.map → chunk-XNAI7QGU.js.map} +0 -0
- /package/dist/{chunk-JXK53XJW.js.map → chunk-YOLLISEY.js.map} +0 -0
- /package/dist/{chunk-FKUVFPQZ.js.map → chunk-Z6QFIRD7.js.map} +0 -0
- /package/dist/{chunk-RHVJ3L4U.js.map → chunk-ZJY7OK6H.js.map} +0 -0
- /package/dist/{chunk-LABIUXDM.js.map → chunk-ZYXXAWDF.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.95.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"packageManager": "pnpm@10.34.5",
|
|
6
6
|
"type": "module",
|
|
@@ -348,6 +348,10 @@
|
|
|
348
348
|
"types": "./dist/types/components/feedback/TutorialOverlay/index.d.ts",
|
|
349
349
|
"default": "./dist/feedback/TutorialOverlay.js"
|
|
350
350
|
},
|
|
351
|
+
"./feedback/UsableModal": {
|
|
352
|
+
"types": "./dist/types/components/feedback/UsableModal/index.d.ts",
|
|
353
|
+
"default": "./dist/feedback/UsableModal.js"
|
|
354
|
+
},
|
|
351
355
|
"./form/AddressAutocomplete": {
|
|
352
356
|
"types": "./dist/types/components/form/AddressAutocomplete/index.d.ts",
|
|
353
357
|
"default": "./dist/form/AddressAutocomplete.js"
|
|
@@ -24,6 +24,8 @@ export interface SheetBodyProps extends Pick<
|
|
|
24
24
|
| 'onBlur'
|
|
25
25
|
> {
|
|
26
26
|
testId: string;
|
|
27
|
+
/** The title's element id, which a dialog sheet's `aria-labelledby` names. */
|
|
28
|
+
titleId?: string;
|
|
27
29
|
isDragging: boolean;
|
|
28
30
|
isVerticalSheet: boolean;
|
|
29
31
|
isDraggableVariant: boolean;
|
|
@@ -79,6 +81,7 @@ export const SheetBody: React.FC<SheetBodyProps> = (props) => {
|
|
|
79
81
|
onFocus,
|
|
80
82
|
onBlur,
|
|
81
83
|
testId,
|
|
84
|
+
titleId,
|
|
82
85
|
isDragging,
|
|
83
86
|
isVerticalSheet,
|
|
84
87
|
isDraggableVariant,
|
|
@@ -115,6 +118,7 @@ export const SheetBody: React.FC<SheetBodyProps> = (props) => {
|
|
|
115
118
|
showHandle={showHandle && isVerticalSheet}
|
|
116
119
|
isDraggable={isDraggableVariant && isVerticalSheet}
|
|
117
120
|
onDragStart={onDragStart}
|
|
121
|
+
titleId={titleId}
|
|
118
122
|
dataTestId={`${testId}-header`}
|
|
119
123
|
>
|
|
120
124
|
{header}
|
|
@@ -43,6 +43,11 @@ A vertical sheet's **width** is one rule rather than one per preset: `min(100%,
|
|
|
43
43
|
- **elevation** (number): Shadow depth level (0-24)
|
|
44
44
|
- **animated** (boolean, default true): `false` opens and closes the panel at once, with no slide, and shows and hides its veil with no fade (`SheetOverlay` takes the same prop)
|
|
45
45
|
|
|
46
|
+
### Stacking and announcing
|
|
47
|
+
|
|
48
|
+
- **zIndex** (number): The drawer's stacking level, its veil one below. A sheet opened over a `StackedModal` stack (1300 and up) passes one above it; the drawer's default (1200) would slide in under the panels.
|
|
49
|
+
- **asDialog** (boolean): Announce the panel as a modal dialog named by its `title` — `role="dialog"`, `aria-modal` and `aria-labelledby`. Off by default: a sheet used as a side panel or a menu is not a dialog.
|
|
50
|
+
|
|
46
51
|
### Interactive Behavior
|
|
47
52
|
|
|
48
53
|
- **swipeable** (boolean): Enable swipe gestures for touch devices (default: true)
|
|
@@ -103,6 +103,7 @@ export const SheetHeader: React.FC<SheetHeaderProps> = (props) => {
|
|
|
103
103
|
className,
|
|
104
104
|
style,
|
|
105
105
|
children,
|
|
106
|
+
titleId,
|
|
106
107
|
dataTestId,
|
|
107
108
|
} = withDefaults(props, HEADER_DEFAULTS);
|
|
108
109
|
|
|
@@ -125,7 +126,7 @@ export const SheetHeader: React.FC<SheetHeaderProps> = (props) => {
|
|
|
125
126
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
|
126
127
|
<Box sx={{ flex: 1 }}>
|
|
127
128
|
{title && (
|
|
128
|
-
<Typography variant="h6" sx={{ fontWeight: 600 }} data-testid={`${dataTestId}-title`}>
|
|
129
|
+
<Typography id={titleId} variant="h6" sx={{ fontWeight: 600 }} data-testid={`${dataTestId}-title`}>
|
|
129
130
|
{title}
|
|
130
131
|
</Typography>
|
|
131
132
|
)}
|
|
@@ -2,7 +2,7 @@ import Drawer from '@mui/material/Drawer/index.js';
|
|
|
2
2
|
import SwipeableDrawer from '@mui/material/SwipeableDrawer/index.js';
|
|
3
3
|
import { useTheme } from '@mui/material/styles/index.js';
|
|
4
4
|
import type { BackdropProps } from '@mui/material/Backdrop/index.js';
|
|
5
|
-
import React from 'react';
|
|
5
|
+
import React, { useId } from 'react';
|
|
6
6
|
|
|
7
7
|
import { withDefaults } from '../../../utils/withDefaults';
|
|
8
8
|
|
|
@@ -102,9 +102,31 @@ const orientationOf = ({ position, variant }: ResolvedSheetProps) => {
|
|
|
102
102
|
};
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/** The paper's dialog attributes: named only when it is a dialog AND has a title to be named by. */
|
|
106
|
+
const dialogAriaOf = ({ asDialog, title }: ResolvedSheetProps, titleId: string) =>
|
|
107
|
+
asDialog ? { role: 'dialog', 'aria-modal': true, ...(title ? { 'aria-labelledby': titleId } : {}) } : {};
|
|
108
|
+
|
|
109
|
+
/** The drawer at a caller's stacking level, or MUI's own. */
|
|
110
|
+
const stackedAt = (zIndex: number | undefined) => (zIndex === undefined ? {} : { sx: { zIndex } });
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The drawer's modal: kept mounted, its own Escape off (the hook's listener
|
|
114
|
+
* honours `closeOnEscape`), and an invisible backdrop that catches the tap.
|
|
115
|
+
*/
|
|
116
|
+
const modalPropsOf = (testId: string | undefined) => ({
|
|
117
|
+
keepMounted: true,
|
|
118
|
+
disableEscapeKeyDown: true,
|
|
119
|
+
BackdropComponent: ClickCatcher,
|
|
120
|
+
BackdropProps: { 'data-testid': testId ? `${testId}-click-catcher` : undefined } as Partial<BackdropProps>,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** The veil one below the drawer it dims. */
|
|
124
|
+
const veilStyleOf = (zIndex: number | undefined) => (zIndex === undefined ? undefined : { zIndex: zIndex - 1 });
|
|
125
|
+
|
|
105
126
|
export const Sheet: React.FC<SheetProps> = (props) => {
|
|
106
127
|
const resolved = withDefaults(props, DEFAULTS) as ResolvedSheetProps;
|
|
107
|
-
const { position, variant, showOverlay, swipeable, className, dataTestId } = resolved;
|
|
128
|
+
const { position, variant, showOverlay, swipeable, className, dataTestId, zIndex, asDialog } = resolved;
|
|
129
|
+
const titleId = useId();
|
|
108
130
|
|
|
109
131
|
const theme = useTheme();
|
|
110
132
|
const orientation = orientationOf(resolved);
|
|
@@ -125,6 +147,7 @@ export const Sheet: React.FC<SheetProps> = (props) => {
|
|
|
125
147
|
anchor: position,
|
|
126
148
|
open: isOpen,
|
|
127
149
|
className,
|
|
150
|
+
...stackedAt(zIndex),
|
|
128
151
|
...(resolved.animated === false ? { transitionDuration: 0 } : {}),
|
|
129
152
|
// A tap outside obeys `closeOnOverlayClick`. A swipe-to-dismiss (no
|
|
130
153
|
// reason: SwipeableDrawer calls `onClose()` bare) closes. Escape is the
|
|
@@ -134,14 +157,10 @@ export const Sheet: React.FC<SheetProps> = (props) => {
|
|
|
134
157
|
reason === 'backdropClick' ? handleOverlayClick() : handleClose(),
|
|
135
158
|
PaperProps: {
|
|
136
159
|
ref: sheetRef,
|
|
160
|
+
...dialogAriaOf(resolved, titleId),
|
|
137
161
|
sx: panelSx({ ...resolved, ...orientation, theme, currentHeight, isDragging, isAnimating }),
|
|
138
162
|
},
|
|
139
|
-
ModalProps:
|
|
140
|
-
keepMounted: true,
|
|
141
|
-
disableEscapeKeyDown: true,
|
|
142
|
-
BackdropComponent: ClickCatcher,
|
|
143
|
-
BackdropProps: { 'data-testid': testId ? `${testId}-click-catcher` : undefined } as Partial<BackdropProps>,
|
|
144
|
-
},
|
|
163
|
+
ModalProps: modalPropsOf(testId),
|
|
145
164
|
};
|
|
146
165
|
|
|
147
166
|
const body = (
|
|
@@ -149,6 +168,7 @@ export const Sheet: React.FC<SheetProps> = (props) => {
|
|
|
149
168
|
{...resolved}
|
|
150
169
|
{...orientation}
|
|
151
170
|
testId={testId}
|
|
171
|
+
titleId={asDialog ? titleId : undefined}
|
|
152
172
|
isDragging={isDragging}
|
|
153
173
|
onClose={handleClose}
|
|
154
174
|
onDragStart={handleDragStart}
|
|
@@ -162,7 +182,7 @@ export const Sheet: React.FC<SheetProps> = (props) => {
|
|
|
162
182
|
return (
|
|
163
183
|
<>
|
|
164
184
|
{showOverlay && isOpen && (
|
|
165
|
-
<SheetOverlay open
|
|
185
|
+
<SheetOverlay open onClick={handleOverlayClick} blur={variant === 'glass'} animated={resolved.animated} style={veilStyleOf(zIndex)} />
|
|
166
186
|
)}
|
|
167
187
|
|
|
168
188
|
{isSwipeable ? (
|
|
@@ -62,6 +62,18 @@ export interface SheetProps {
|
|
|
62
62
|
elevation?: number;
|
|
63
63
|
/** Slide in and out (default). `false` opens and closes at once. */
|
|
64
64
|
animated?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The drawer's stacking level, its veil one below. For a sheet opened over a
|
|
67
|
+
* `StackedModal` stack (1300 and up), which the drawer's default (1200) would
|
|
68
|
+
* slide under.
|
|
69
|
+
*/
|
|
70
|
+
zIndex?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Announce the panel as a modal dialog named by its `title`: `role="dialog"`,
|
|
73
|
+
* `aria-modal` and `aria-labelledby`. Off by default — a sheet used as a side
|
|
74
|
+
* panel or a menu is not a dialog.
|
|
75
|
+
*/
|
|
76
|
+
asDialog?: boolean;
|
|
65
77
|
dataTestId?: string;
|
|
66
78
|
}
|
|
67
79
|
|
|
@@ -81,6 +93,8 @@ export interface SheetHeaderProps {
|
|
|
81
93
|
className?: string;
|
|
82
94
|
style?: CSSProperties;
|
|
83
95
|
children?: ReactNode;
|
|
96
|
+
/** The title's element id, for a sheet that names itself as a dialog (`asDialog`). */
|
|
97
|
+
titleId?: string;
|
|
84
98
|
dataTestId?: string;
|
|
85
99
|
}
|
|
86
100
|
|
|
@@ -91,6 +91,22 @@ const useStackRegistration = (open: boolean, modalId: string): void => {
|
|
|
91
91
|
const timer = window.setTimeout(() => popModal(modalId), ROLE_TRANSITION_MS);
|
|
92
92
|
return () => window.clearTimeout(timer);
|
|
93
93
|
}, [open, modalId, popModal, isModalInStack]);
|
|
94
|
+
|
|
95
|
+
// A panel unmounted while open, or before its slide-out timer fired, still
|
|
96
|
+
// leaves the stack: otherwise its entry stays `primary` and the panel below
|
|
97
|
+
// is stuck as `secondary` (no Esc, no backdrop, a back arrow to nowhere).
|
|
98
|
+
// `popModal(id)` filters by id, so popping one already gone is a no-op, and
|
|
99
|
+
// it is a stable callback, so this runs on unmount (or an id change) only.
|
|
100
|
+
// The registration flag goes with the entry: StrictMode runs this cleanup and
|
|
101
|
+
// then the effects again on the SAME instance, and a flag left `true` would
|
|
102
|
+
// skip the re-push and leave an open panel out of its stack.
|
|
103
|
+
useEffect(
|
|
104
|
+
() => () => {
|
|
105
|
+
popModal(modalId);
|
|
106
|
+
hasRegisteredRef.current = false;
|
|
107
|
+
},
|
|
108
|
+
[modalId, popModal],
|
|
109
|
+
);
|
|
94
110
|
};
|
|
95
111
|
|
|
96
112
|
interface PanelChromeOptions {
|
|
@@ -17,7 +17,8 @@ const noop = (): void => {
|
|
|
17
17
|
/** do nothing */
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/** What a `StackedModal` reads with no provider above it: an empty stack nothing can push onto. */
|
|
21
|
+
const NO_STACK: ModalStackContextValue = {
|
|
21
22
|
stack: [],
|
|
22
23
|
pushModal: noop,
|
|
23
24
|
popModal: noop,
|
|
@@ -25,7 +26,16 @@ const ModalStackContext = createContext<ModalStackContextValue>({
|
|
|
25
26
|
currentDepth: 0,
|
|
26
27
|
isModalInStack: () => false,
|
|
27
28
|
getModalRole: () => null,
|
|
28
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ModalStackContext = createContext<ModalStackContextValue>(NO_STACK);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether a `ModalStackProvider` is above this point. A component that opens a
|
|
35
|
+
* `StackedModal` on its own (`UsableModal`) brings a provider only when the
|
|
36
|
+
* host has none, so its panel still joins a stack the host already keeps.
|
|
37
|
+
*/
|
|
38
|
+
export const useInModalStack = (): boolean => useContext(ModalStackContext) !== NO_STACK;
|
|
29
39
|
|
|
30
40
|
export const useModalStack = (): ModalStackContextValue => {
|
|
31
41
|
const context = useContext(ModalStackContext);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# UsableModal
|
|
2
|
+
|
|
3
|
+
A modal that is a **`StackedModal` on a computer** and a **bottom `Sheet` on a
|
|
4
|
+
phone**, behind one API. A screen that opens "the same task" on both — a form, a
|
|
5
|
+
wizard, a pedido being written — uses this instead of switching between the two
|
|
6
|
+
by hand.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { UsableModal } from '@12-apps/ui/feedback/UsableModal';
|
|
10
|
+
|
|
11
|
+
<UsableModal
|
|
12
|
+
open={open}
|
|
13
|
+
onClose={close}
|
|
14
|
+
title="Novo pedido"
|
|
15
|
+
closeLabel="Fechar"
|
|
16
|
+
backLabel="Voltar"
|
|
17
|
+
footer={<Actions />}
|
|
18
|
+
dataTestId="new-order"
|
|
19
|
+
>
|
|
20
|
+
<Form />
|
|
21
|
+
</UsableModal>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## The surface
|
|
25
|
+
|
|
26
|
+
| viewport | surface | from |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| below the theme's `sm` | bottom `Sheet`, as tall as its body up to the screen less a 3rem strip (a ceiling, not a height) | `UsableModal.sheet.tsx` |
|
|
29
|
+
| `sm` and up | `StackedModal` (`size`, `panelWidth` pass through) | `UsableModal.modal.tsx` |
|
|
30
|
+
|
|
31
|
+
`layout="modal"` or `layout="sheet"` fixes one surface for a host that already
|
|
32
|
+
knows (a test, a kiosk). With the default `auto` the surface is chosen when the
|
|
33
|
+
modal **opens** and kept until it closes: a phone turned to landscape or a
|
|
34
|
+
window narrowed past `sm` mid-task does not swap the surface and lose what was
|
|
35
|
+
typed. The next opening asks the viewport again.
|
|
36
|
+
|
|
37
|
+
## Loaded on demand
|
|
38
|
+
|
|
39
|
+
Each surface is its own lazily imported chunk (the package builds with
|
|
40
|
+
`splitting: true`). A closed modal renders nothing and loads nothing; the first
|
|
41
|
+
open loads the one surface the viewport asks for. A phone never downloads
|
|
42
|
+
`StackedModal`, a computer never downloads `Sheet`. While the chunk loads the
|
|
43
|
+
screen shows nothing — no spinner before the content.
|
|
44
|
+
|
|
45
|
+
Closing hands the surface `open={false}`, so it runs its own way out (the
|
|
46
|
+
slide-out, the stacked modal leaving its stack), and `SURFACE_EXIT_MS` (400 ms)
|
|
47
|
+
later it unmounts. Every opening starts a fresh body on both surfaces, and a
|
|
48
|
+
closed sheet leaves nothing on the page, not even the drawer's swipe strip
|
|
49
|
+
along the bottom edge.
|
|
50
|
+
|
|
51
|
+
**A chunk a deploy replaced** (a tab opened before it asks for a file that is
|
|
52
|
+
gone) is retried once, then the page reloads once onto the current build; if
|
|
53
|
+
that already happened in this tab in the last five minutes the modal renders
|
|
54
|
+
nothing rather than throwing to the host's error boundary (`recover-chunk.ts`).
|
|
55
|
+
A later deploy earns its own reload.
|
|
56
|
+
|
|
57
|
+
## Props
|
|
58
|
+
|
|
59
|
+
- **open**, **onClose** — `onClose` fires for every way out: the ✕, Esc, the
|
|
60
|
+
backdrop, a swipe down on the sheet.
|
|
61
|
+
- **title** — names the dialog (the stacked modal's bar, the sheet's header).
|
|
62
|
+
- **children** — the body. It scrolls.
|
|
63
|
+
- **footer** — the pinned action bar at the bottom of either surface. On the
|
|
64
|
+
stacked modal it is NOT `StackedModal`'s `actions`, which sit in the header
|
|
65
|
+
from `sm` up: a screen's acts belong under what they act on.
|
|
66
|
+
- **closeLabel** — the ✕'s accessible name, `common.close` by default.
|
|
67
|
+
- **backLabel** — the back arrow's (required, as on `StackedModal`; it shows when
|
|
68
|
+
the stacked modal opens over another panel).
|
|
69
|
+
- **layout**, **size**, **panelWidth**, **dataTestId**.
|
|
70
|
+
|
|
71
|
+
## The stack
|
|
72
|
+
|
|
73
|
+
On a computer the panel joins a `ModalStackProvider` the host already keeps
|
|
74
|
+
(it is pushed over the host's panel and offers the way back) and leaves it when
|
|
75
|
+
it closes. A host with no provider gets a stack of one, so the component works
|
|
76
|
+
on its own.
|
|
77
|
+
|
|
78
|
+
On a phone the sheet takes the level a panel pushed onto the stack would take:
|
|
79
|
+
**above** any open `StackedModal` (a drawer's own level, 1200, is under the
|
|
80
|
+
stack's 1300 and up), and with no stack 1300, MUI's modal level, so the menus a
|
|
81
|
+
Select or a Popover in its body opens still land on top. It is a named modal dialog
|
|
82
|
+
(`role="dialog"`, `aria-modal`, `aria-labelledby` its title), like the stacked
|
|
83
|
+
surface.
|
|
84
|
+
|
|
85
|
+
## Test ids
|
|
86
|
+
|
|
87
|
+
`dataTestId` marks the surface; `<id>-footer` marks the action bar on both.
|
|
88
|
+
The rest are each surface's own, so a host test that must not branch asks by
|
|
89
|
+
role instead (`getByRole('dialog', { name: title })`, the ✕ by `closeLabel`):
|
|
90
|
+
|
|
91
|
+
| | computer (`StackedModal`) | phone (`Sheet`) |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| title | `<id>-title` | `<id>-header-title` |
|
|
94
|
+
| ✕ | `<id>-close-button` | `<id>-header-close-button` |
|
|
95
|
+
| back | `<id>-back-button` | — |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Box from '@mui/material/Box/index.js';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { safeAreaBottom } from '../../../utils/viewport';
|
|
5
|
+
import { StackedModal, StackedModalContent } from '../StackedModal/StackedModal';
|
|
6
|
+
import { ModalStackProvider, useInModalStack } from '../StackedModal/modal-stack';
|
|
7
|
+
import { hasFooter } from './has-footer';
|
|
8
|
+
import type { UsableModalSurfaceProps } from './UsableModal.types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The computer's surface: a `StackedModal`, loaded only when this layout is the
|
|
12
|
+
* one drawn (`./UsableModal`).
|
|
13
|
+
*
|
|
14
|
+
* The footer is pinned at the bottom of the panel's scroll, not handed to
|
|
15
|
+
* `StackedModal`'s `actions`: those sit in the header from `sm` up, and a
|
|
16
|
+
* screen's acts belong under what they act on.
|
|
17
|
+
*/
|
|
18
|
+
export function UsableModalStacked(props: UsableModalSurfaceProps): React.JSX.Element {
|
|
19
|
+
const inStack = useInModalStack();
|
|
20
|
+
const panel = <StackedPanel {...props} />;
|
|
21
|
+
// A host that keeps its own stack gets this panel pushed onto it; one that
|
|
22
|
+
// keeps none gets a stack of one, which is what `StackedModal` needs to draw.
|
|
23
|
+
return inStack ? panel : <ModalStackProvider>{panel}</ModalStackProvider>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function StackedPanel({
|
|
27
|
+
open,
|
|
28
|
+
onClose,
|
|
29
|
+
title,
|
|
30
|
+
children,
|
|
31
|
+
footer,
|
|
32
|
+
closeLabel,
|
|
33
|
+
backLabel,
|
|
34
|
+
size,
|
|
35
|
+
panelWidth,
|
|
36
|
+
dataTestId,
|
|
37
|
+
}: UsableModalSurfaceProps): React.JSX.Element {
|
|
38
|
+
return (
|
|
39
|
+
<StackedModal
|
|
40
|
+
open={open}
|
|
41
|
+
onClose={onClose}
|
|
42
|
+
navigationTitle={title}
|
|
43
|
+
closeLabel={closeLabel}
|
|
44
|
+
backLabel={backLabel}
|
|
45
|
+
size={size}
|
|
46
|
+
panelWidth={panelWidth}
|
|
47
|
+
dataTestId={dataTestId}
|
|
48
|
+
>
|
|
49
|
+
<StackedModalContent sx={{ p: 0, display: 'flex', flexDirection: 'column', minHeight: '100%' }}>
|
|
50
|
+
<Box sx={{ flex: 1, p: 3 }}>{children}</Box>
|
|
51
|
+
{hasFooter(footer) && <PinnedFooter dataTestId={dataTestId}>{footer}</PinnedFooter>}
|
|
52
|
+
</StackedModalContent>
|
|
53
|
+
</StackedModal>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The action bar, held at the panel's bottom edge while the body scrolls under it. */
|
|
58
|
+
function PinnedFooter({ dataTestId, children }: { dataTestId?: string; children: ReactNode }): React.JSX.Element {
|
|
59
|
+
return (
|
|
60
|
+
<Box
|
|
61
|
+
sx={{
|
|
62
|
+
position: 'sticky',
|
|
63
|
+
bottom: 0,
|
|
64
|
+
px: 3,
|
|
65
|
+
py: 2,
|
|
66
|
+
bgcolor: 'background.paper',
|
|
67
|
+
borderTop: (theme) => `1px solid ${theme.palette.divider}`,
|
|
68
|
+
// Full screen under `sm` (`layout="modal"` on a phone): clear of the iOS home indicator.
|
|
69
|
+
...safeAreaBottom(16),
|
|
70
|
+
}}
|
|
71
|
+
data-testid={dataTestId ? `${dataTestId}-footer` : undefined}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Box from '@mui/material/Box/index.js';
|
|
2
|
+
import { useTheme, type Theme } from '@mui/material/styles/index.js';
|
|
3
|
+
|
|
4
|
+
import { Sheet } from '../../data-display/Sheet/Sheet';
|
|
5
|
+
import { MODAL_STACK_BASE_Z_INDEX, MODAL_STACK_Z_INDEX_STEP } from '../../../tokens/layers';
|
|
6
|
+
import { rem } from '../../../tokens/relative';
|
|
7
|
+
import { safeAreaBottom } from '../../../utils/viewport';
|
|
8
|
+
import { useModalStack } from '../StackedModal/modal-stack';
|
|
9
|
+
import { hasFooter } from './has-footer';
|
|
10
|
+
import type { UsableModalSurfaceProps } from './UsableModal.types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How tall the sheet may grow: the whole screen but a strip of the dimmed page
|
|
14
|
+
* above it, so it still reads as a sheet over the screen it came from. The
|
|
15
|
+
* Sheet's own `xl` ceiling is 600px — a phone of 844 lost a quarter of the
|
|
16
|
+
* screen to it, and a modal's body (a form, a cardápio) is what needs it.
|
|
17
|
+
*/
|
|
18
|
+
const sheetCeiling = (theme: Theme) => ({ maxHeight: `calc(100% - ${rem(theme, 48)})` });
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The phone's surface: a bottom `Sheet`, loaded only when this layout is the one
|
|
22
|
+
* drawn (`./UsableModal`). Its height is a ceiling, not a height: a short body
|
|
23
|
+
* gives a short sheet, and a long one grows to `sheetCeiling` and scrolls above
|
|
24
|
+
* the pinned footer.
|
|
25
|
+
*
|
|
26
|
+
* It is a named modal dialog (`asDialog`), like the stacked surface. And it
|
|
27
|
+
* takes the level a panel pushed onto the stack would take (`BASE + depth *
|
|
28
|
+
* STEP`): above any `StackedModal` already open — a drawer's default (1200) is
|
|
29
|
+
* under the stack's 1300 and up, and a sheet opened from a panel would slide in
|
|
30
|
+
* behind it, invisible, holding the focus — and never above the menus its own
|
|
31
|
+
* body opens. With no stack that is 1300, MUI's `zIndex.modal`, the level a
|
|
32
|
+
* Select's or a Popover's menu opens at; portalled later, the menu is on top.
|
|
33
|
+
*/
|
|
34
|
+
export function UsableModalSheet({
|
|
35
|
+
open,
|
|
36
|
+
onClose,
|
|
37
|
+
title,
|
|
38
|
+
children,
|
|
39
|
+
footer,
|
|
40
|
+
closeLabel,
|
|
41
|
+
dataTestId,
|
|
42
|
+
}: UsableModalSurfaceProps): React.JSX.Element {
|
|
43
|
+
const { currentDepth } = useModalStack();
|
|
44
|
+
const theme = useTheme();
|
|
45
|
+
return (
|
|
46
|
+
<Sheet
|
|
47
|
+
open={open}
|
|
48
|
+
onOpenChange={(next) => {
|
|
49
|
+
if (!next) onClose();
|
|
50
|
+
}}
|
|
51
|
+
position="bottom"
|
|
52
|
+
size="xl"
|
|
53
|
+
style={sheetCeiling(theme)}
|
|
54
|
+
title={title}
|
|
55
|
+
closeLabel={closeLabel}
|
|
56
|
+
asDialog
|
|
57
|
+
zIndex={MODAL_STACK_BASE_Z_INDEX + currentDepth * MODAL_STACK_Z_INDEX_STEP}
|
|
58
|
+
// The sheet marks its own footer `${dataTestId}-footer`, as the stacked surface does.
|
|
59
|
+
footer={hasFooter(footer) ? <Box sx={safeAreaBottom(0)}>{footer}</Box> : undefined}
|
|
60
|
+
dataTestId={dataTestId}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</Sheet>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useTheme } from '@mui/material/styles/index.js';
|
|
2
|
+
import useMediaQuery from '@mui/material/useMediaQuery/index.js';
|
|
3
|
+
import { lazy, Suspense, useEffect, useState, type ComponentType } from 'react';
|
|
4
|
+
|
|
5
|
+
import { recoverChunk } from './recover-chunk';
|
|
6
|
+
import type { UsableModalLayout, UsableModalProps, UsableModalSurfaceProps } from './UsableModal.types';
|
|
7
|
+
|
|
8
|
+
type Surface = 'modal' | 'sheet';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Each surface is its own chunk (the package builds with `splitting: true`), so
|
|
12
|
+
* a phone never downloads `StackedModal` and a computer never downloads `Sheet`.
|
|
13
|
+
* A chunk a deploy replaced is retried, then the page reloads once onto the
|
|
14
|
+
* current build (`./recover-chunk`) — never the host's error boundary.
|
|
15
|
+
*/
|
|
16
|
+
const StackedSurface = lazy(
|
|
17
|
+
recoverChunk(async (): Promise<{ default: ComponentType<UsableModalSurfaceProps> }> => ({
|
|
18
|
+
default: (await import('./UsableModal.modal')).UsableModalStacked,
|
|
19
|
+
})),
|
|
20
|
+
);
|
|
21
|
+
const SheetSurface = lazy(
|
|
22
|
+
recoverChunk(async (): Promise<{ default: ComponentType<UsableModalSurfaceProps> }> => ({
|
|
23
|
+
default: (await import('./UsableModal.sheet')).UsableModalSheet,
|
|
24
|
+
})),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/** The surface the viewport asks for now: a sheet below `sm`, a stacked modal from `sm` up. */
|
|
28
|
+
function useViewportSurface(layout: UsableModalLayout): Surface {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
// `noSsr`: the first render already knows the width, so a phone never mounts
|
|
31
|
+
// (and never fetches) the stacked modal for one frame before the sheet.
|
|
32
|
+
const phone = useMediaQuery(theme.breakpoints.down('sm'), { noSsr: true });
|
|
33
|
+
if (layout !== 'auto') return layout;
|
|
34
|
+
return phone ? 'sheet' : 'modal';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* How long a closed surface stays mounted: longer than its own way out — the
|
|
39
|
+
* drawer's slide-down (225 ms) and the stacked modal's leaving its stack
|
|
40
|
+
* (300 ms) — so neither is cut off.
|
|
41
|
+
*/
|
|
42
|
+
export const SURFACE_EXIT_MS = 400;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The surface is chosen when the modal OPENS and kept until it closes: a phone
|
|
46
|
+
* turned to landscape, or a window narrowed past `sm`, mid-task, does not swap
|
|
47
|
+
* the surface and throw away what the person typed. The next opening asks the
|
|
48
|
+
* viewport again.
|
|
49
|
+
*
|
|
50
|
+
* Once closed, and its way out run, the surface is let go of: every opening
|
|
51
|
+
* starts with a fresh body on both surfaces (the sheet would otherwise keep its
|
|
52
|
+
* content mounted, and the last half-written form, while the stacked modal
|
|
53
|
+
* drops its own), and a closed sheet leaves nothing on the page — not even the
|
|
54
|
+
* drawer's edge strip that swallows taps and swipes the sheet open.
|
|
55
|
+
*/
|
|
56
|
+
function useOpenedSurface(open: boolean, layout: UsableModalLayout): Surface | null {
|
|
57
|
+
const viewport = useViewportSurface(layout);
|
|
58
|
+
const [surface, setSurface] = useState<Surface | null>(open ? viewport : null);
|
|
59
|
+
const [wasOpen, setWasOpen] = useState(open);
|
|
60
|
+
if (open !== wasOpen) {
|
|
61
|
+
setWasOpen(open);
|
|
62
|
+
if (open) setSurface(viewport);
|
|
63
|
+
}
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (open || surface === null) return;
|
|
66
|
+
const timer = window.setTimeout(() => setSurface(null), SURFACE_EXIT_MS);
|
|
67
|
+
return () => window.clearTimeout(timer);
|
|
68
|
+
}, [open, surface]);
|
|
69
|
+
return surface;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A modal that is a `StackedModal` on a computer and a bottom `Sheet` on a
|
|
74
|
+
* phone, behind one API: title, body, a pinned footer, and the ways out.
|
|
75
|
+
*
|
|
76
|
+
* Nothing is fetched until the first open: until then it renders nothing.
|
|
77
|
+
* Closing hands the surface `open={false}`, so it runs its own way out — the
|
|
78
|
+
* slide-out, and the stacked modal leaving its stack — and then it unmounts
|
|
79
|
+
* (`SURFACE_EXIT_MS`). While the
|
|
80
|
+
* chunk loads the screen shows no placeholder: a modal that blinks a spinner
|
|
81
|
+
* before its content is worse than one that appears a frame later.
|
|
82
|
+
*/
|
|
83
|
+
export function UsableModal({ layout = 'auto', ...props }: UsableModalProps): React.JSX.Element | null {
|
|
84
|
+
const surface = useOpenedSurface(props.open, layout);
|
|
85
|
+
if (surface === null) return null;
|
|
86
|
+
const Shown = surface === 'sheet' ? SheetSurface : StackedSurface;
|
|
87
|
+
return (
|
|
88
|
+
<Suspense fallback={null}>
|
|
89
|
+
<Shown {...props} />
|
|
90
|
+
</Suspense>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { PanelSize, PanelWidth } from '../StackedModal/StackedModal.types';
|
|
4
|
+
|
|
5
|
+
/** Which surface draws the modal: chosen by the viewport, or fixed by the host. */
|
|
6
|
+
export type UsableModalLayout = 'auto' | 'modal' | 'sheet';
|
|
7
|
+
|
|
8
|
+
export interface UsableModalProps {
|
|
9
|
+
/** Controls the visibility of the modal. */
|
|
10
|
+
open: boolean;
|
|
11
|
+
/** Fired by every way out: the ✕, Esc, the backdrop, a swipe down on the sheet. */
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
/** Names the dialog: the stacked modal's bar, the sheet's header. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** The body. It scrolls; the footer does not. */
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
/** The pinned action bar at the bottom of either surface — the screen's acts. */
|
|
18
|
+
footer?: ReactNode;
|
|
19
|
+
/** The ✕'s accessible name — it carries a glyph only. Defaults to `common.close` (`EN_US_UI_LABELS`). */
|
|
20
|
+
closeLabel?: string;
|
|
21
|
+
/** The stacked modal's back arrow, shown when it opens over another panel. REQUIRED. */
|
|
22
|
+
backLabel: string;
|
|
23
|
+
/**
|
|
24
|
+
* `auto` (default): a bottom `Sheet` below the theme's `sm` breakpoint, a
|
|
25
|
+
* `StackedModal` from `sm` up. `modal` and `sheet` fix one surface, for a host
|
|
26
|
+
* that already knows (a test, a kiosk).
|
|
27
|
+
*/
|
|
28
|
+
layout?: UsableModalLayout;
|
|
29
|
+
/** The stacked modal's viewport share (`StackedModal`'s `size`). */
|
|
30
|
+
size?: PanelSize;
|
|
31
|
+
/** An exact width for the stacked modal (`StackedModal`'s `panelWidth`). */
|
|
32
|
+
panelWidth?: PanelWidth;
|
|
33
|
+
/** Base test id: the surface carries it, and `-footer` marks the action bar. */
|
|
34
|
+
dataTestId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What each lazily loaded surface receives: the host's props, layout already decided. */
|
|
38
|
+
export type UsableModalSurfaceProps = Omit<UsableModalProps, 'layout'>;
|