@12-apps/ui 6.93.0 → 6.93.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-catalog.md +9 -2
- package/components-guidelines.md +30 -0
- package/dist/_internal/components/data-display/AlertDialog/AlertDialog.js +2 -2
- package/dist/_internal/components/data-display/AlertDialog/AlertDialog.parts.js +9 -3
- package/dist/_internal/components/data-display/AsyncStateContainer/AsyncStateContainer.js +3 -3
- package/dist/_internal/components/data-display/Avatar/Avatar.js +2 -2
- package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +7 -7
- package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +7 -7
- package/dist/_internal/components/data-display/Carousel/Carousel.js +3 -3
- package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
- package/dist/_internal/components/data-display/Chart/SpecChart.js +7 -7
- package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +3 -3
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +31 -31
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +42 -42
- package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +7 -7
- 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 +5 -5
- 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 +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +26 -26
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +7 -7
- package/dist/_internal/components/data-display/DataViews/data-views-range-bounds.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.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 +8 -8
- 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 +3 -3
- package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js +25 -0
- package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js.map +1 -0
- package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.styles.js +23 -0
- package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.styles.js.map +1 -0
- package/dist/_internal/components/data-display/DayRuler/DayRuler.js +14 -12
- package/dist/_internal/components/data-display/DayRuler/DayRuler.keyboard.js +3 -3
- package/dist/_internal/components/data-display/DayRuler/DayRuler.metrics.js +1 -1
- package/dist/_internal/components/data-display/DayRuler/DayRuler.parts.js +12 -15
- package/dist/_internal/components/data-display/DayRuler/DayRuler.styles.js +3 -11
- package/dist/_internal/components/data-display/DayRuler/day-ruler-model.js +5 -1
- package/dist/_internal/components/data-display/DayRuler/useRulerDrag.js +3 -3
- package/dist/_internal/components/data-display/DayRuler/useTextFits.js +4 -3
- package/dist/_internal/components/data-display/ErrorState/ErrorState.js +2 -2
- package/dist/_internal/components/data-display/ErrorState/ErrorState.metrics.js +3 -1
- package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.js +6 -6
- package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.parts.js +5 -5
- package/dist/_internal/components/data-display/ScheduleGrid/ScheduleGrid.styles.js +2 -2
- package/dist/_internal/components/data-display/ScheduleGrid/ScheduleNameCell.js +3 -3
- package/dist/_internal/components/data-display/ScheduleGrid/schedule-name.styles.js +2 -2
- package/dist/_internal/components/data-display/ScheduleGrid/schedule-tones.js +3 -1
- package/dist/_internal/components/data-display/index.js +41 -39
- package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.js +4 -4
- package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.parts.js +3 -3
- package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +5 -5
- package/dist/_internal/components/feedback/ConfirmAction/with-confirmation.js +4 -4
- package/dist/_internal/components/feedback/index.js +6 -6
- package/dist/_internal/components/index.js +52 -50
- package/dist/_internal/components/navigation/SectionNav/SectionNav.bar.js +6 -6
- package/dist/_internal/components/navigation/SectionNav/SectionNav.js +9 -9
- package/dist/_internal/components/navigation/SectionNav/SectionNav.parts.js +3 -3
- package/dist/_internal/components/navigation/SectionNav/SectionNav.primary.js +4 -4
- package/dist/_internal/components/navigation/SectionNav/SectionNav.rail.js +5 -5
- package/dist/_internal/components/navigation/SectionNav/SectionNav.railCreate.js +4 -4
- package/dist/_internal/components/navigation/SectionNav/SectionNav.sheet.js +4 -4
- package/dist/_internal/components/navigation/index.js +9 -9
- package/dist/_internal/user-avatar/user-avatar.js +3 -3
- package/dist/charts.js +13 -13
- package/dist/{chunk-LDCZPZPD.js → chunk-24HHUCWB.js} +2 -2
- package/dist/{chunk-7ZQ3KQ7J.js → chunk-273OLFJD.js} +16 -6
- package/dist/chunk-273OLFJD.js.map +1 -0
- package/dist/{chunk-FAUNQ3FS.js → chunk-2M3W6ACW.js} +3 -3
- package/dist/{chunk-ALOEOGQV.js → chunk-3TZUZXY5.js} +2 -2
- package/dist/{chunk-4HK4FVMV.js → chunk-3UAHDLKH.js} +7 -1
- package/dist/{chunk-4HK4FVMV.js.map → chunk-3UAHDLKH.js.map} +1 -1
- package/dist/{chunk-F5H3WDG7.js → chunk-4E2RQOSQ.js} +2 -2
- package/dist/{chunk-JIEVKHOH.js → chunk-4J62V5OE.js} +6 -6
- package/dist/{chunk-OJZLFFG4.js → chunk-5AZEDQXX.js} +59 -70
- package/dist/chunk-5AZEDQXX.js.map +1 -0
- package/dist/{chunk-CZVAVX5L.js → chunk-6ATXOJVQ.js} +2 -2
- package/dist/{chunk-E2UOEI7N.js → chunk-6LGKJVOA.js} +2 -2
- package/dist/{chunk-XBWW3QSI.js → chunk-7WE432RQ.js} +9 -9
- package/dist/{chunk-5357QUVE.js → chunk-AMLHEKSV.js} +5 -5
- package/dist/{chunk-HSVSU7I2.js → chunk-ASEHJIIV.js} +3 -3
- package/dist/{chunk-FYUNROLW.js → chunk-BZYLTGOE.js} +2 -2
- package/dist/{chunk-TZ44F6W7.js → chunk-CGS2LQE5.js} +2 -2
- package/dist/{chunk-KPUUHAK6.js → chunk-CWXX5JR3.js} +11 -9
- package/dist/chunk-CWXX5JR3.js.map +1 -0
- package/dist/{chunk-6HXNFNOB.js → chunk-EE6DJPB7.js} +4 -4
- package/dist/{chunk-OM6ZT64U.js → chunk-F2UP5ZF4.js} +3 -3
- package/dist/{chunk-P53ANKZV.js → chunk-F7LBTMCA.js} +4 -4
- package/dist/{chunk-6HPVCDVU.js → chunk-FGZIEZEP.js} +69 -112
- package/dist/chunk-FGZIEZEP.js.map +1 -0
- package/dist/{chunk-IWSTMVCA.js → chunk-FKS2PNJF.js} +8 -8
- package/dist/{chunk-3WYEENUH.js → chunk-FPNVOB52.js} +7 -7
- package/dist/{chunk-E73C73UD.js → chunk-FYSS2PYA.js} +3 -3
- package/dist/{chunk-N557OEZR.js → chunk-G6NMSBSQ.js} +2 -2
- package/dist/{chunk-A5OERY43.js → chunk-GARM5SX6.js} +2 -2
- package/dist/{chunk-WJMSND5Q.js → chunk-GXGHEOWC.js} +2 -2
- package/dist/{chunk-U2SAW6DH.js → chunk-GY53SDVL.js} +4 -4
- package/dist/{chunk-MFX4ASX5.js → chunk-HWZETMO4.js} +3 -3
- package/dist/{chunk-HBH7V3IP.js → chunk-I6IFUFLW.js} +5 -3
- package/dist/chunk-I6IFUFLW.js.map +1 -0
- package/dist/{chunk-MM3QESLI.js → chunk-ILJ3FXIC.js} +2 -2
- package/dist/{chunk-K6VEN5VN.js → chunk-K3PMMW23.js} +4 -4
- package/dist/{chunk-T5RFH2T5.js → chunk-KOGJBAFI.js} +2 -2
- package/dist/{chunk-COK65GX2.js → chunk-KURE5R7Z.js} +12 -2
- package/dist/chunk-KURE5R7Z.js.map +1 -0
- package/dist/{chunk-MAYAJXO6.js → chunk-KXDLLS62.js} +5 -5
- package/dist/{chunk-LE5JYNR7.js → chunk-L5CAHQIN.js} +23 -2
- package/dist/chunk-L5CAHQIN.js.map +1 -0
- package/dist/{chunk-DBI6AMNY.js → chunk-LKWXU6NH.js} +4 -4
- package/dist/chunk-MPVH2GM5.js +83 -0
- package/dist/chunk-MPVH2GM5.js.map +1 -0
- package/dist/{chunk-6DO672AG.js → chunk-N5PVD4J5.js} +7 -7
- package/dist/{chunk-ZYZPSBNQ.js → chunk-OGSTMHT6.js} +22 -7
- package/dist/chunk-OGSTMHT6.js.map +1 -0
- package/dist/{chunk-TLGGVE56.js → chunk-OTVOQHZB.js} +3 -1
- package/dist/{chunk-TLGGVE56.js.map → chunk-OTVOQHZB.js.map} +1 -1
- package/dist/{chunk-7F6D73NL.js → chunk-RLTGD5WJ.js} +2 -2
- package/dist/{chunk-R52N5UCS.js → chunk-SB2SEGR6.js} +2 -2
- package/dist/{chunk-Z2WZDVGG.js → chunk-SI5SKX52.js} +2 -2
- package/dist/{chunk-RYJ6XGFA.js → chunk-SJAOQGQI.js} +2 -2
- package/dist/{chunk-D7R6SRJB.js → chunk-SKQPUAMK.js} +3 -3
- package/dist/{chunk-AMIUZRDQ.js → chunk-SNWCT6JN.js} +3 -3
- package/dist/{chunk-7W5X3KQ2.js → chunk-SW7JCF4Z.js} +2 -2
- package/dist/{chunk-BNBWUXT3.js → chunk-TE6HAWDM.js} +2 -2
- package/dist/{chunk-JG733RMP.js → chunk-TZC6KIK2.js} +5 -5
- package/dist/{chunk-4QGKR65P.js → chunk-U4Z6GZDW.js} +5 -5
- package/dist/{chunk-XFZDO2KB.js → chunk-UIIHUWOV.js} +13 -13
- package/dist/{chunk-L4V7FZJI.js → chunk-UKUGSU3Y.js} +2 -2
- package/dist/{chunk-VJIJCLUX.js → chunk-UOKXYV3U.js} +2 -2
- package/dist/{chunk-K7IDOTSU.js → chunk-VNMD6BA4.js} +2 -2
- package/dist/chunk-WG7CAT5D.js +65 -0
- package/dist/chunk-WG7CAT5D.js.map +1 -0
- package/dist/chunk-WSSD7H5E.js +52 -0
- package/dist/chunk-WSSD7H5E.js.map +1 -0
- package/dist/{chunk-DTZOP4ZR.js → chunk-XCUHGPPZ.js} +2 -2
- package/dist/{chunk-3YFDGFEK.js → chunk-Y7BOP7VZ.js} +2 -2
- package/dist/{chunk-XNAI7QGU.js → chunk-ZM6FJCWS.js} +4 -4
- package/dist/{chunk-FC7UCFFA.js → chunk-ZPWPA6FR.js} +8 -8
- package/dist/data-display/AlertDialog.js +2 -2
- package/dist/data-display/AsyncStateContainer.js +3 -3
- package/dist/data-display/Avatar.js +2 -2
- package/dist/data-display/Badge.js +2 -2
- package/dist/data-display/CardKit.js +10 -10
- package/dist/data-display/Carousel.js +3 -3
- package/dist/data-display/Chart.js +13 -13
- package/dist/data-display/DataViews.js +56 -56
- package/dist/data-display/DayRuler.js +14 -12
- package/dist/data-display/ErrorState.js +2 -2
- package/dist/data-display/ScheduleGrid.js +6 -6
- package/dist/feedback/ConfirmAction.js +6 -6
- package/dist/native/data-display/ErrorState.js.map +1 -1
- package/dist/navigation/SectionNav.js +9 -9
- package/dist/types/components/data-display/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts +11 -0
- package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/DayRuler.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/DayRuler.footer.d.ts +16 -0
- package/dist/types/components/data-display/DayRuler/DayRuler.footer.d.ts.map +1 -0
- package/dist/types/components/data-display/DayRuler/DayRuler.footer.styles.d.ts +18 -0
- package/dist/types/components/data-display/DayRuler/DayRuler.footer.styles.d.ts.map +1 -0
- package/dist/types/components/data-display/DayRuler/DayRuler.metrics.d.ts +15 -5
- package/dist/types/components/data-display/DayRuler/DayRuler.metrics.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/DayRuler.parts.d.ts +13 -14
- package/dist/types/components/data-display/DayRuler/DayRuler.parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/DayRuler.styles.d.ts +20 -23
- package/dist/types/components/data-display/DayRuler/DayRuler.styles.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/DayRuler.types.d.ts +13 -3
- package/dist/types/components/data-display/DayRuler/DayRuler.types.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/day-ruler-model.d.ts +27 -0
- package/dist/types/components/data-display/DayRuler/day-ruler-model.d.ts.map +1 -1
- package/dist/types/components/data-display/DayRuler/useTextFits.d.ts +11 -9
- package/dist/types/components/data-display/DayRuler/useTextFits.d.ts.map +1 -1
- package/dist/types/components/data-display/ErrorState/ErrorState.d.ts.map +1 -1
- package/dist/types/components/data-display/ErrorState/ErrorState.metrics.d.ts +5 -0
- package/dist/types/components/data-display/ErrorState/ErrorState.metrics.d.ts.map +1 -1
- package/dist/types/components/data-display/ScheduleGrid/schedule-tones.d.ts +8 -0
- package/dist/types/components/data-display/ScheduleGrid/schedule-tones.d.ts.map +1 -1
- package/dist/types-native/components/data-display/ErrorState/ErrorState.metrics.d.ts +5 -0
- package/dist/types-native/components/data-display/ErrorState/ErrorState.metrics.d.ts.map +1 -1
- package/dist/user-avatar.js +3 -3
- package/package.json +1 -1
- package/src/components/data-display/AlertDialog/AlertDialog.md +25 -1
- package/src/components/data-display/AlertDialog/AlertDialog.parts.tsx +28 -0
- package/src/components/data-display/AlertDialog/AlertDialog.tsx +34 -5
- package/src/components/data-display/DayRuler/DayRuler.footer.styles.ts +54 -0
- package/src/components/data-display/DayRuler/DayRuler.footer.tsx +123 -0
- package/src/components/data-display/DayRuler/DayRuler.md +37 -13
- package/src/components/data-display/DayRuler/DayRuler.metrics.ts +15 -5
- package/src/components/data-display/DayRuler/DayRuler.parts.tsx +86 -155
- package/src/components/data-display/DayRuler/DayRuler.styles.ts +89 -83
- package/src/components/data-display/DayRuler/DayRuler.tsx +3 -2
- package/src/components/data-display/DayRuler/DayRuler.types.ts +13 -3
- package/src/components/data-display/DayRuler/day-ruler-model.ts +45 -0
- package/src/components/data-display/DayRuler/useTextFits.ts +68 -25
- package/src/components/data-display/ErrorState/ErrorState.metrics.ts +5 -0
- package/src/components/data-display/ErrorState/ErrorState.tsx +2 -0
- package/src/components/data-display/ScheduleGrid/schedule-tones.ts +13 -0
- package/src/components/feedback/ConfirmAction/ConfirmAction.md +10 -0
- package/dist/chunk-6HPVCDVU.js.map +0 -1
- package/dist/chunk-7ZQ3KQ7J.js.map +0 -1
- package/dist/chunk-COK65GX2.js.map +0 -1
- package/dist/chunk-HBH7V3IP.js.map +0 -1
- package/dist/chunk-KPUUHAK6.js.map +0 -1
- package/dist/chunk-LE5JYNR7.js.map +0 -1
- package/dist/chunk-OJZLFFG4.js.map +0 -1
- package/dist/chunk-YMYJO2CZ.js +0 -32
- package/dist/chunk-YMYJO2CZ.js.map +0 -1
- package/dist/chunk-ZYZPSBNQ.js.map +0 -1
- /package/dist/{chunk-LDCZPZPD.js.map → chunk-24HHUCWB.js.map} +0 -0
- /package/dist/{chunk-FAUNQ3FS.js.map → chunk-2M3W6ACW.js.map} +0 -0
- /package/dist/{chunk-ALOEOGQV.js.map → chunk-3TZUZXY5.js.map} +0 -0
- /package/dist/{chunk-F5H3WDG7.js.map → chunk-4E2RQOSQ.js.map} +0 -0
- /package/dist/{chunk-JIEVKHOH.js.map → chunk-4J62V5OE.js.map} +0 -0
- /package/dist/{chunk-CZVAVX5L.js.map → chunk-6ATXOJVQ.js.map} +0 -0
- /package/dist/{chunk-E2UOEI7N.js.map → chunk-6LGKJVOA.js.map} +0 -0
- /package/dist/{chunk-XBWW3QSI.js.map → chunk-7WE432RQ.js.map} +0 -0
- /package/dist/{chunk-5357QUVE.js.map → chunk-AMLHEKSV.js.map} +0 -0
- /package/dist/{chunk-HSVSU7I2.js.map → chunk-ASEHJIIV.js.map} +0 -0
- /package/dist/{chunk-FYUNROLW.js.map → chunk-BZYLTGOE.js.map} +0 -0
- /package/dist/{chunk-TZ44F6W7.js.map → chunk-CGS2LQE5.js.map} +0 -0
- /package/dist/{chunk-6HXNFNOB.js.map → chunk-EE6DJPB7.js.map} +0 -0
- /package/dist/{chunk-OM6ZT64U.js.map → chunk-F2UP5ZF4.js.map} +0 -0
- /package/dist/{chunk-P53ANKZV.js.map → chunk-F7LBTMCA.js.map} +0 -0
- /package/dist/{chunk-IWSTMVCA.js.map → chunk-FKS2PNJF.js.map} +0 -0
- /package/dist/{chunk-3WYEENUH.js.map → chunk-FPNVOB52.js.map} +0 -0
- /package/dist/{chunk-E73C73UD.js.map → chunk-FYSS2PYA.js.map} +0 -0
- /package/dist/{chunk-N557OEZR.js.map → chunk-G6NMSBSQ.js.map} +0 -0
- /package/dist/{chunk-A5OERY43.js.map → chunk-GARM5SX6.js.map} +0 -0
- /package/dist/{chunk-WJMSND5Q.js.map → chunk-GXGHEOWC.js.map} +0 -0
- /package/dist/{chunk-U2SAW6DH.js.map → chunk-GY53SDVL.js.map} +0 -0
- /package/dist/{chunk-MFX4ASX5.js.map → chunk-HWZETMO4.js.map} +0 -0
- /package/dist/{chunk-MM3QESLI.js.map → chunk-ILJ3FXIC.js.map} +0 -0
- /package/dist/{chunk-K6VEN5VN.js.map → chunk-K3PMMW23.js.map} +0 -0
- /package/dist/{chunk-T5RFH2T5.js.map → chunk-KOGJBAFI.js.map} +0 -0
- /package/dist/{chunk-MAYAJXO6.js.map → chunk-KXDLLS62.js.map} +0 -0
- /package/dist/{chunk-DBI6AMNY.js.map → chunk-LKWXU6NH.js.map} +0 -0
- /package/dist/{chunk-6DO672AG.js.map → chunk-N5PVD4J5.js.map} +0 -0
- /package/dist/{chunk-7F6D73NL.js.map → chunk-RLTGD5WJ.js.map} +0 -0
- /package/dist/{chunk-R52N5UCS.js.map → chunk-SB2SEGR6.js.map} +0 -0
- /package/dist/{chunk-Z2WZDVGG.js.map → chunk-SI5SKX52.js.map} +0 -0
- /package/dist/{chunk-RYJ6XGFA.js.map → chunk-SJAOQGQI.js.map} +0 -0
- /package/dist/{chunk-D7R6SRJB.js.map → chunk-SKQPUAMK.js.map} +0 -0
- /package/dist/{chunk-AMIUZRDQ.js.map → chunk-SNWCT6JN.js.map} +0 -0
- /package/dist/{chunk-7W5X3KQ2.js.map → chunk-SW7JCF4Z.js.map} +0 -0
- /package/dist/{chunk-BNBWUXT3.js.map → chunk-TE6HAWDM.js.map} +0 -0
- /package/dist/{chunk-JG733RMP.js.map → chunk-TZC6KIK2.js.map} +0 -0
- /package/dist/{chunk-4QGKR65P.js.map → chunk-U4Z6GZDW.js.map} +0 -0
- /package/dist/{chunk-XFZDO2KB.js.map → chunk-UIIHUWOV.js.map} +0 -0
- /package/dist/{chunk-L4V7FZJI.js.map → chunk-UKUGSU3Y.js.map} +0 -0
- /package/dist/{chunk-VJIJCLUX.js.map → chunk-UOKXYV3U.js.map} +0 -0
- /package/dist/{chunk-K7IDOTSU.js.map → chunk-VNMD6BA4.js.map} +0 -0
- /package/dist/{chunk-DTZOP4ZR.js.map → chunk-XCUHGPPZ.js.map} +0 -0
- /package/dist/{chunk-3YFDGFEK.js.map → chunk-Y7BOP7VZ.js.map} +0 -0
- /package/dist/{chunk-XNAI7QGU.js.map → chunk-ZM6FJCWS.js.map} +0 -0
- /package/dist/{chunk-FC7UCFFA.js.map → chunk-ZPWPA6FR.js.map} +0 -0
|
@@ -2,39 +2,39 @@ import { alpha, type CSSObject, type SxProps, type Theme } from '@mui/material/s
|
|
|
2
2
|
|
|
3
3
|
import { FIELD_BORDER_WIDTH } from '../../../tokens/field-height.core';
|
|
4
4
|
import { rem, sxRem } from '../../../tokens/relative';
|
|
5
|
-
import { actualBarColor, blockToneStyles, breakHatch,
|
|
5
|
+
import { actualBarColor, blockToneStyles, breakHatch, dashedLabelGround } from '../ScheduleGrid/schedule-tones';
|
|
6
6
|
import type { ScheduleBlockTone } from '../ScheduleGrid/ScheduleGrid.types';
|
|
7
7
|
import { DAY_RULER_METRICS as M } from './DayRuler.metrics';
|
|
8
8
|
|
|
9
|
-
/** The container
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'--dr-actions': hasActions ? rem(theme, M.sideColumn) : '0%',
|
|
21
|
-
});
|
|
22
|
-
}
|
|
9
|
+
/** The container the ruler's layout answers to — named, so no other container between the root and a row can stand in for it. */
|
|
10
|
+
const CONTAINER = 'day-ruler';
|
|
11
|
+
|
|
12
|
+
/** The ruler's root: the named container every layout query reads. */
|
|
13
|
+
export const rootSx: SxProps<Theme> = {
|
|
14
|
+
containerType: 'inline-size',
|
|
15
|
+
containerName: CONTAINER,
|
|
16
|
+
minWidth: 0,
|
|
17
|
+
width: '100%',
|
|
18
|
+
userSelect: 'none',
|
|
19
|
+
};
|
|
23
20
|
|
|
24
21
|
/** `@container` below which a row stacks (the coverage and axis rows go full width). Bounds are "from px up". */
|
|
25
22
|
function compact(theme: Theme): string {
|
|
26
|
-
return `@container (width < ${rem(theme, M.compactBelow)})`;
|
|
23
|
+
return `@container ${CONTAINER} (width < ${rem(theme, M.compactBelow)})`;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
/** `@container` below which the pane is phone-sized: no coverage row, no axis. Bounds are "from px up". */
|
|
30
27
|
function phone(theme: Theme): string {
|
|
31
|
-
return `@container (width < ${rem(theme, M.phoneBelow)})`;
|
|
28
|
+
return `@container ${CONTAINER} (width < ${rem(theme, M.phoneBelow)})`;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Name · track: the coverage row and the axis row share these columns with
|
|
33
|
+
* every row on a computer, so the hours line up down the whole ruler.
|
|
34
|
+
*/
|
|
35
35
|
export const lineSx: SxProps<Theme> = (theme: Theme) => ({
|
|
36
36
|
display: 'grid',
|
|
37
|
-
gridTemplateColumns: `${rem(theme, M.sideColumn)} minmax(0, 1fr)
|
|
37
|
+
gridTemplateColumns: `${rem(theme, M.sideColumn)} minmax(0, 1fr)`,
|
|
38
38
|
columnGap: rem(theme, M.gap),
|
|
39
39
|
alignItems: 'center',
|
|
40
40
|
// Stacked: across the full width, like the tracks; the label above the counts.
|
|
@@ -109,19 +109,23 @@ export const groupTitleSx: SxProps<Theme> = {
|
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* One person's row. A computer: name
|
|
113
|
-
*
|
|
112
|
+
* One person's row. A computer: name (its actions under it) · track, the
|
|
113
|
+
* track the same width on every row whether or not the row has actions. A
|
|
114
|
+
* narrow ruler (a phone): name and actions on top, the track under them, full
|
|
115
|
+
* width.
|
|
114
116
|
*/
|
|
115
117
|
export const rowSx: SxProps<Theme> = (theme: Theme) => ({
|
|
116
118
|
display: 'grid',
|
|
117
|
-
gridTemplateColumns: `${rem(theme, M.sideColumn)} minmax(0, 1fr)
|
|
118
|
-
|
|
119
|
+
gridTemplateColumns: `${rem(theme, M.sideColumn)} minmax(0, 1fr)`,
|
|
120
|
+
gridTemplateRows: 'auto auto',
|
|
121
|
+
gridTemplateAreas: '"who track" "actions track"',
|
|
119
122
|
columnGap: rem(theme, M.gap),
|
|
120
123
|
alignItems: 'center',
|
|
121
124
|
py: rem(theme, M.rowPaddingY),
|
|
122
125
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
123
126
|
[compact(theme)]: {
|
|
124
127
|
gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
128
|
+
gridTemplateRows: 'none',
|
|
125
129
|
gridTemplateAreas: '"who actions" "track track"',
|
|
126
130
|
rowGap: theme.spacing(1),
|
|
127
131
|
py: theme.spacing(1.5),
|
|
@@ -130,15 +134,17 @@ export const rowSx: SxProps<Theme> = (theme: Theme) => ({
|
|
|
130
134
|
|
|
131
135
|
export const whoSx: SxProps<Theme> = { gridArea: 'who', minWidth: 0 };
|
|
132
136
|
|
|
133
|
-
export const actionsSx: SxProps<Theme> = {
|
|
137
|
+
export const actionsSx: SxProps<Theme> = (theme: Theme) => ({
|
|
134
138
|
gridArea: 'actions',
|
|
135
139
|
display: 'flex',
|
|
136
140
|
gap: 1,
|
|
137
141
|
alignItems: 'center',
|
|
138
|
-
justifyContent: 'flex-
|
|
142
|
+
justifyContent: 'flex-start',
|
|
139
143
|
flexWrap: 'wrap',
|
|
140
144
|
minWidth: 0,
|
|
141
|
-
|
|
145
|
+
pt: rem(theme, M.actionsTop),
|
|
146
|
+
[compact(theme)]: { justifyContent: 'flex-end', pt: 0 },
|
|
147
|
+
});
|
|
142
148
|
|
|
143
149
|
/** A break inside a block: a hatched band across its height, under its text. `span` is in % of the block. */
|
|
144
150
|
export function breakSx(span: Span): SxProps<Theme> {
|
|
@@ -191,7 +197,36 @@ export interface Span {
|
|
|
191
197
|
share: number;
|
|
192
198
|
}
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
/** How far a bar's hit band may reach, in % of the track: the midpoints to its neighbours, or the track's ends. */
|
|
201
|
+
export interface HitBounds {
|
|
202
|
+
lo: number;
|
|
203
|
+
hi: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The hit band round a bar on a narrow ruler (FUT-3831): `hitArea` tall and
|
|
208
|
+
* at least `hitArea` wide, centred on the bar, then cut to `bounds` so it never
|
|
209
|
+
* reaches past the midpoint to a neighbour. Positioned in % of the bar's
|
|
210
|
+
* PADDING box (the pseudo-element's containing block, the bar less its
|
|
211
|
+
* border), so a point at `k` bar-widths from the bar's outer left edge is
|
|
212
|
+
* `k × (100% + 2 border) − border`; the px floor is the layout's to resolve.
|
|
213
|
+
*/
|
|
214
|
+
function hitBand(theme: Theme, span: Span, bounds: HitBounds): CSSObject {
|
|
215
|
+
// Every tone's border is the 1px hairline (`blockToneStyles`), the field border's width.
|
|
216
|
+
const border = `${FIELD_BORDER_WIDTH}px`;
|
|
217
|
+
const half = `max(calc(50% + ${border}), ${rem(theme, M.block.hitArea / 2)})`;
|
|
218
|
+
if (!(span.share > 0)) return { left: `calc(50% - ${half})`, width: `calc(2 * ${half})` };
|
|
219
|
+
const rel = (pct: number): string => `calc(${(pct - span.at) / span.share} * (100% + 2 * ${border}) - ${border})`;
|
|
220
|
+
const left = `max(${rel(bounds.lo)}, calc(50% - ${half}))`;
|
|
221
|
+
const right = `min(${rel(bounds.hi)}, calc(50% + ${half}))`;
|
|
222
|
+
return { left, width: `calc(${right} - ${left})` };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function blockSx(
|
|
226
|
+
tone: ScheduleBlockTone,
|
|
227
|
+
span: Span,
|
|
228
|
+
opts: { selected: boolean; movable: boolean; hit: HitBounds },
|
|
229
|
+
): SxProps<Theme> {
|
|
195
230
|
return (theme: Theme): CSSObject => ({
|
|
196
231
|
...blockToneStyles(theme, tone, true),
|
|
197
232
|
position: 'absolute',
|
|
@@ -214,25 +249,35 @@ export function blockSx(tone: ScheduleBlockTone, span: Span, opts: { selected: b
|
|
|
214
249
|
cursor: opts.movable ? 'grab' : 'pointer',
|
|
215
250
|
boxShadow: opts.selected ? `0 0 0 ${rem(theme, M.ring)} ${theme.palette.primary.main}` : 'none',
|
|
216
251
|
'&:focus-visible': { outline: `${rem(theme, M.ring)} solid ${theme.palette.primary.main}`, outlineOffset: rem(theme, M.ringOffset) },
|
|
217
|
-
|
|
252
|
+
// The label sits above the "agora" line (drawn under the blocks): on a fill
|
|
253
|
+
// that is not opaque it takes an opaque ground, so the line never cuts it.
|
|
254
|
+
'& [data-dr-text]': {
|
|
255
|
+
position: 'relative',
|
|
256
|
+
overflow: 'hidden',
|
|
257
|
+
textOverflow: 'ellipsis',
|
|
258
|
+
minWidth: 0,
|
|
259
|
+
backgroundColor: dashedLabelGround(theme, tone),
|
|
260
|
+
},
|
|
218
261
|
'& [data-dr-text="short"]': { display: 'none' },
|
|
262
|
+
// By pixels, at every width: the full text, else the start, else nothing — never a cut text.
|
|
263
|
+
'&[data-fit="short"] [data-dr-text="full"]': { display: 'none' },
|
|
264
|
+
'&[data-fit="short"] [data-dr-text="short"]': { display: 'inline' },
|
|
265
|
+
'&[data-fit="none"] [data-dr-text]': { display: 'none' },
|
|
219
266
|
[compact(theme)]: {
|
|
220
267
|
top: rem(theme, M.block.compactTop),
|
|
221
268
|
height: rem(theme, M.block.compactHeight),
|
|
222
|
-
// A 40px hit area
|
|
223
|
-
//
|
|
269
|
+
// A 40px × 40px-or-wider hit area round the 20px bar, the look
|
|
270
|
+
// unchanged: the text never overflows (it fits or it is not shown), so
|
|
271
|
+
// the block need not clip.
|
|
224
272
|
overflow: 'visible',
|
|
225
273
|
'&::after': {
|
|
226
274
|
content: '""',
|
|
227
275
|
position: 'absolute',
|
|
228
|
-
left: 0,
|
|
229
|
-
right: 0,
|
|
230
276
|
top: '50%',
|
|
231
277
|
height: rem(theme, M.block.hitArea),
|
|
232
278
|
transform: 'translateY(-50%)',
|
|
279
|
+
...hitBand(theme, span, opts.hit),
|
|
233
280
|
},
|
|
234
|
-
'&[data-narrow] [data-dr-text="full"]': { display: 'none' },
|
|
235
|
-
'&[data-narrow] [data-dr-text="short"]': { display: 'inline' },
|
|
236
281
|
},
|
|
237
282
|
});
|
|
238
283
|
}
|
|
@@ -245,6 +290,10 @@ export function handleSx(side: 'l' | 'r'): SxProps<Theme> {
|
|
|
245
290
|
[side === 'l' ? 'left' : 'right']: 0,
|
|
246
291
|
width: rem(theme, M.handle),
|
|
247
292
|
cursor: 'ew-resize',
|
|
293
|
+
// Above the block's hit band (its `::after`, painted last): on a narrow
|
|
294
|
+
// ruler the band covers the whole bar, and a press on an end must still
|
|
295
|
+
// resize rather than move (FUT-3831).
|
|
296
|
+
zIndex: 1,
|
|
248
297
|
});
|
|
249
298
|
}
|
|
250
299
|
|
|
@@ -275,6 +324,12 @@ export function actualLabelSx(left: number): SxProps<Theme> {
|
|
|
275
324
|
});
|
|
276
325
|
}
|
|
277
326
|
|
|
327
|
+
/**
|
|
328
|
+
* "Agora": drawn UNDER the blocks (before them in the track), so it never
|
|
329
|
+
* crosses a block's label — "07:30–1|3:00" at 1280 (FUT-3831). It still shows
|
|
330
|
+
* above and below each bar, where it overhangs the track, and through a
|
|
331
|
+
* block's fill where that fill is not opaque — never through its text.
|
|
332
|
+
*/
|
|
278
333
|
export function nowSx(left: number): SxProps<Theme> {
|
|
279
334
|
return (theme: Theme) => ({
|
|
280
335
|
position: 'absolute',
|
|
@@ -301,52 +356,3 @@ export const skeletonNameSx: SxProps<Theme> = (theme: Theme) => ({
|
|
|
301
356
|
borderRadius: rem(theme, M.skeletonNameRadius),
|
|
302
357
|
backgroundColor: theme.palette.action.hover,
|
|
303
358
|
});
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* A marked footer's words (FUT-3882): the warning ink deepened until it reads
|
|
307
|
-
* at 4.5:1 on the page — `warning.main` is 3.1:1 on white. The outline keeps
|
|
308
|
-
* `warning.main`: a non-text mark needs 3:1.
|
|
309
|
-
*/
|
|
310
|
-
export function footerInk(theme: Theme, marked: boolean, plain: string): string {
|
|
311
|
-
return marked ? signalInk(theme, 'warning', theme.palette.background.default) : plain;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** A group's count line (FUT-3882): plain counts on the hours' columns, a marked hour outlined in the warning ink. */
|
|
315
|
-
export function footerCellSx(marked: boolean): SxProps<Theme> {
|
|
316
|
-
return (theme: Theme) => ({
|
|
317
|
-
display: 'flex',
|
|
318
|
-
alignItems: 'center',
|
|
319
|
-
justifyContent: 'center',
|
|
320
|
-
fontSize: rem(theme, M.coverage.font),
|
|
321
|
-
borderRadius: rem(theme, M.coverage.radius),
|
|
322
|
-
color: footerInk(theme, marked, theme.palette.text.primary),
|
|
323
|
-
fontWeight: marked ? 500 : 400,
|
|
324
|
-
// The hairline is always there, transparent when unmarked, so a mark never shifts a count.
|
|
325
|
-
border: `1px solid ${marked ? theme.palette.warning.main : 'transparent'}`,
|
|
326
|
-
minWidth: 0,
|
|
327
|
-
overflow: 'hidden',
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* A footer line's sentence; when the host marks it ("Faltam 2, 20h–22h"), in
|
|
333
|
-
* the readable warning ink with a warning bar before it — a shape, so colour
|
|
334
|
-
* is never the only signal.
|
|
335
|
-
*/
|
|
336
|
-
export function footerNoteSx(marked: boolean): SxProps<Theme> {
|
|
337
|
-
return (theme: Theme) => ({
|
|
338
|
-
fontSize: rem(theme, M.meta),
|
|
339
|
-
color: footerInk(theme, marked, theme.palette.text.secondary),
|
|
340
|
-
fontWeight: marked ? 500 : 400,
|
|
341
|
-
...(marked ? { borderLeft: `${rem(theme, 3)} solid ${theme.palette.warning.main}`, paddingLeft: rem(theme, 6) } : {}),
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/** A group's trend line: as tall as the coverage row, drawn in the primary colour. */
|
|
346
|
-
export const trendSx: SxProps<Theme> = (theme: Theme) => ({
|
|
347
|
-
display: 'block',
|
|
348
|
-
width: '100%',
|
|
349
|
-
height: rem(theme, M.coverage.height + M.coverage.height / 2),
|
|
350
|
-
color: theme.palette.primary.main,
|
|
351
|
-
overflow: 'visible',
|
|
352
|
-
});
|
|
@@ -9,7 +9,8 @@ import { useAnnouncer } from '../ScheduleGrid/useAnnouncer';
|
|
|
9
9
|
import { handleBlockKey, keyCreateSpan, type RulerAbilities, type RulerScale } from './DayRuler.keyboard';
|
|
10
10
|
import { EN_US_DAY_RULER_LABELS } from './DayRuler.en-US';
|
|
11
11
|
import { DAY_RULER_METRICS as M } from './DayRuler.metrics';
|
|
12
|
-
import {
|
|
12
|
+
import { GroupFooter } from './DayRuler.footer';
|
|
13
|
+
import { Axis, Coverage, RulerRow, type RowContext } from './DayRuler.parts';
|
|
13
14
|
import { groupTitleSx, rootSx, rowSx, skeletonNameSx, skeletonTrackSx } from './DayRuler.styles';
|
|
14
15
|
import type { DayRulerProps, DayRulerRow } from './DayRuler.types';
|
|
15
16
|
import { useRulerDrag } from './useRulerDrag';
|
|
@@ -119,7 +120,7 @@ export function DayRuler(props: DayRulerProps): React.JSX.Element {
|
|
|
119
120
|
|
|
120
121
|
return (
|
|
121
122
|
<Box
|
|
122
|
-
sx={rootSx
|
|
123
|
+
sx={rootSx}
|
|
123
124
|
data-testid={testId}
|
|
124
125
|
role="region"
|
|
125
126
|
aria-label={props['aria-label']}
|
|
@@ -14,9 +14,14 @@ export interface DayRulerBlock {
|
|
|
14
14
|
id: string;
|
|
15
15
|
start: RulerHour;
|
|
16
16
|
end: RulerHour;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* What the block says ("↻ 20:39–00:00", "Ausente"). One line, drawn whole or
|
|
19
|
+
* not at all: a bar too short for it says `shortLabel`, and one too short
|
|
20
|
+
* even for that says nothing — never a cut text (FUT-3831). Either way it
|
|
21
|
+
* stays the block's tooltip (unless `title`) and its accessible name.
|
|
22
|
+
*/
|
|
18
23
|
label: string;
|
|
19
|
-
/** What a
|
|
24
|
+
/** What a block too short for its `label` says instead, at any width — its start ("20:39"). */
|
|
20
25
|
shortLabel?: string;
|
|
21
26
|
tone: ScheduleBlockTone;
|
|
22
27
|
/** Its middle can be dragged along the row (and Alt+←/→ moves it). */
|
|
@@ -53,7 +58,12 @@ export interface DayRulerRow {
|
|
|
53
58
|
nameAdornment?: React.ReactNode;
|
|
54
59
|
/** Under the meta: the host's chips (praça, "Em pausa desde …"). One line; a chip that does not fit shrinks, its label cut. */
|
|
55
60
|
chips?: React.ReactNode;
|
|
56
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* The row's own buttons ("Encerrar turno", "Iniciar agora"): under the name
|
|
63
|
+
* on a computer, beside it when the row stacks. They take no column of their
|
|
64
|
+
* own, so every track keeps the same width and the hours stay aligned
|
|
65
|
+
* (FUT-3831).
|
|
66
|
+
*/
|
|
57
67
|
actions?: React.ReactNode;
|
|
58
68
|
blocks: readonly DayRulerBlock[];
|
|
59
69
|
actual?: DayRulerActual;
|
|
@@ -81,3 +81,48 @@ export function ticksOf(from: RulerHour, to: RulerHour, step: number): RulerHour
|
|
|
81
81
|
for (let h = from; h <= to + 1e-9; h += step) out.push(h);
|
|
82
82
|
return out;
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/** A bar along the track: where it starts and how long it is, both in % of the track. */
|
|
86
|
+
export interface TrackSpan {
|
|
87
|
+
at: number;
|
|
88
|
+
share: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How far each bar's hit band may reach, in % of the track (FUT-3831): from
|
|
93
|
+
* `lo` to `hi`, never past the track's ends and never past the MIDPOINT of
|
|
94
|
+
* the gap to the bar beside it, so a band widened round a short bar cannot
|
|
95
|
+
* take a tap meant for its neighbour. Two bars that touch or overlap give each
|
|
96
|
+
* other nothing: the band stays on the bar's own edge on that side.
|
|
97
|
+
*
|
|
98
|
+
* The band itself is drawn in CSS — at least the hit area wide, centred on the
|
|
99
|
+
* bar, then cut to `lo…hi` — because the bar's width in px is the layout's.
|
|
100
|
+
*/
|
|
101
|
+
export function hitBoundsOf(spans: readonly TrackSpan[]): { lo: number; hi: number }[] {
|
|
102
|
+
const order = spans.map((_, index) => index).sort((a, b) => spans[a]!.at - spans[b]!.at);
|
|
103
|
+
const out = spans.map(() => ({ lo: 0, hi: 100 }));
|
|
104
|
+
for (let k = 1; k < order.length; k += 1) {
|
|
105
|
+
const left = spans[order[k - 1]!]!;
|
|
106
|
+
const right = spans[order[k]!]!;
|
|
107
|
+
const leftEnd = left.at + left.share;
|
|
108
|
+
const mid = leftEnd <= right.at ? (leftEnd + right.at) / 2 : null;
|
|
109
|
+
out[order[k - 1]!]!.hi = Math.min(out[order[k - 1]!]!.hi, mid ?? leftEnd);
|
|
110
|
+
out[order[k]!]!.lo = Math.max(out[order[k]!]!.lo, mid ?? right.at);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** What a block can say in its bar: its full text, its start, or nothing. */
|
|
116
|
+
export type BlockTextFit = 'full' | 'short' | 'none';
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Which of a block's texts fits the room it leaves inside the bar, by pixels
|
|
120
|
+
* (FUT-3831): the full one, else the short one, else NOTHING — a cut "12:"
|
|
121
|
+
* says less than an empty bar whose title and accessible name carry the text.
|
|
122
|
+
*/
|
|
123
|
+
export function textFitOf(fullWidth: number, shortWidth: number, room: number): BlockTextFit {
|
|
124
|
+
const slack = 0.5;
|
|
125
|
+
if (fullWidth <= room + slack) return 'full';
|
|
126
|
+
if (shortWidth <= room + slack) return 'short';
|
|
127
|
+
return 'none';
|
|
128
|
+
}
|
|
@@ -1,41 +1,84 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
2
|
import { useLayoutEffect, useState } from 'react';
|
|
3
3
|
|
|
4
|
+
import { textFitOf, type BlockTextFit } from './day-ruler-model';
|
|
5
|
+
|
|
6
|
+
/** A text span's own width (its laid-out text, whatever the span's own box). */
|
|
7
|
+
function textWidth(span: HTMLElement | null): number {
|
|
8
|
+
if (!span) return Number.POSITIVE_INFINITY;
|
|
9
|
+
const range = span.ownerDocument.createRange();
|
|
10
|
+
range.selectNodeContents(span);
|
|
11
|
+
return range.getBoundingClientRect().width;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
15
|
+
* The block's fit, measured in ONE layout: every write first (both texts
|
|
16
|
+
* shown for the measurement alone), then every read, then the writes undone —
|
|
17
|
+
* so a ruler of many blocks forces one layout per block, not one per text.
|
|
18
|
+
* `null` when there is nothing to measure yet.
|
|
19
|
+
*/
|
|
20
|
+
function measureFit(el: HTMLElement): BlockTextFit | null {
|
|
21
|
+
const full = el.querySelector<HTMLElement>('[data-dr-text="full"]');
|
|
22
|
+
if (!full) return null;
|
|
23
|
+
const short = el.querySelector<HTMLElement>('[data-dr-text="short"]');
|
|
24
|
+
const spans = [full, short].filter((span): span is HTMLElement => span !== null);
|
|
25
|
+
const shown = spans.map((span) => span.style.display);
|
|
26
|
+
for (const span of spans) span.style.display = 'inline';
|
|
27
|
+
const fullWidth = textWidth(full);
|
|
28
|
+
const shortWidth = textWidth(short);
|
|
29
|
+
const style = getComputedStyle(el);
|
|
30
|
+
const outer = el.clientWidth;
|
|
31
|
+
const room = outer - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);
|
|
32
|
+
spans.forEach((span, index) => {
|
|
33
|
+
span.style.display = shown[index] ?? '';
|
|
34
|
+
});
|
|
35
|
+
if (room > 0) return textFitOf(fullWidth, shortWidth, room);
|
|
36
|
+
// A bar with no room at all (narrower than its own padding) says nothing.
|
|
37
|
+
return outer > 0 ? 'none' : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Which of a block's texts fits it, by pixels (FUT-3825, FUT-3831): the full
|
|
42
|
+
* text's and the short text's own widths against the room the block leaves
|
|
43
|
+
* them. A wide block keeps "18:00–23:00"; one too short for it says its start
|
|
44
|
+
* ("18:00"); one too short even for that says nothing — never a cut "18:".
|
|
9
45
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
46
|
+
* Measured at once (synchronously, so nothing is painted), then again on the
|
|
47
|
+
* next frame whenever the block is resized, and once more when the page's
|
|
48
|
+
* fonts have loaded: a webfont arriving late changes every text's width, and
|
|
49
|
+
* a fit read in the fallback font would leave a label cut. Where nothing can
|
|
50
|
+
* be measured (no `ResizeObserver`) it answers `fallback`.
|
|
14
51
|
*/
|
|
15
|
-
export function
|
|
16
|
-
const [
|
|
52
|
+
export function useTextFit(block: React.RefObject<HTMLElement | null>, text: string, short: string, fallback: BlockTextFit): BlockTextFit {
|
|
53
|
+
const [fit, setFit] = useState<BlockTextFit>(fallback);
|
|
17
54
|
|
|
18
55
|
useLayoutEffect(() => {
|
|
19
56
|
const el = block.current;
|
|
20
57
|
if (!el || typeof ResizeObserver === 'undefined') return undefined;
|
|
58
|
+
let live = true;
|
|
59
|
+
let frame = 0;
|
|
21
60
|
const measure = (): void => {
|
|
22
|
-
|
|
23
|
-
if (!
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (room > 0) setFits(width <= room + 0.5);
|
|
61
|
+
frame = 0;
|
|
62
|
+
if (!live) return;
|
|
63
|
+
const next = measureFit(el);
|
|
64
|
+
if (next) setFit(next);
|
|
65
|
+
};
|
|
66
|
+
// Resizes and the fonts come in bursts: one measurement per frame.
|
|
67
|
+
const schedule = (): void => {
|
|
68
|
+
if (frame || !live) return;
|
|
69
|
+
frame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame(measure) : 0;
|
|
70
|
+
if (!frame) measure();
|
|
33
71
|
};
|
|
34
72
|
measure();
|
|
35
|
-
const observer = new ResizeObserver(
|
|
73
|
+
const observer = new ResizeObserver(schedule);
|
|
36
74
|
observer.observe(el);
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
el.ownerDocument.fonts?.ready.then(schedule, () => undefined);
|
|
76
|
+
return () => {
|
|
77
|
+
live = false;
|
|
78
|
+
observer.disconnect();
|
|
79
|
+
if (frame && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(frame);
|
|
80
|
+
};
|
|
81
|
+
}, [block, text, short]);
|
|
39
82
|
|
|
40
|
-
return
|
|
83
|
+
return fit;
|
|
41
84
|
}
|
|
@@ -27,6 +27,11 @@ export const ERROR_MESSAGE_LINE_HEIGHT = 1.6;
|
|
|
27
27
|
/** The retry button: 8px below the text, no narrower than 120px. */
|
|
28
28
|
export const RETRY_MARGIN_TOP_UNITS = 1;
|
|
29
29
|
export const RETRY_MIN_WIDTH = 120;
|
|
30
|
+
/**
|
|
31
|
+
* The web retry is never shorter than the 40px tap floor. MUI's medium outlined
|
|
32
|
+
* button is 37px tall on its own (FUT-1310), so the floor is stated, not inherited.
|
|
33
|
+
*/
|
|
34
|
+
export const RETRY_MIN_HEIGHT = 40;
|
|
30
35
|
/**
|
|
31
36
|
* The web retry IS MUI's medium outlined `Button`: 5px 15px inside its 1px
|
|
32
37
|
* border, 14px type, the theme's radius, a 20px start icon. The native side
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
ERROR_STATE_PADDING_UNITS,
|
|
21
21
|
ERROR_TEXT_GAP_UNITS,
|
|
22
22
|
RETRY_MARGIN_TOP_UNITS,
|
|
23
|
+
RETRY_MIN_HEIGHT,
|
|
23
24
|
RETRY_MIN_WIDTH,
|
|
24
25
|
} from './ErrorState.metrics';
|
|
25
26
|
import type { ErrorStateProps, ErrorStateSeverity } from './ErrorState.types';
|
|
@@ -153,6 +154,7 @@ export const ErrorState: React.FC<ErrorStateProps> = React.memo((props) => {
|
|
|
153
154
|
sx={{
|
|
154
155
|
mt: theme.spacing(RETRY_MARGIN_TOP_UNITS),
|
|
155
156
|
minWidth: rem(theme, RETRY_MIN_WIDTH),
|
|
157
|
+
minHeight: rem(theme, RETRY_MIN_HEIGHT),
|
|
156
158
|
}}
|
|
157
159
|
>
|
|
158
160
|
{retryLabel}
|
|
@@ -108,6 +108,19 @@ export function blockToneStyles(theme: Theme, tone: ScheduleBlockTone, dashedPla
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* An OPAQUE ground for a ruler block's label where the block's own fill is
|
|
113
|
+
* not opaque (the dashed plan's faint wash, the away / uncovered outline): the
|
|
114
|
+
* fill flattened over the cell's ground, so the label reads the same and
|
|
115
|
+
* whatever the track draws under the block — the "agora" line — never shows
|
|
116
|
+
* through the text (FUT-3831). An opaque fill needs none: `undefined`.
|
|
117
|
+
*/
|
|
118
|
+
export function dashedLabelGround(theme: Theme, tone: ScheduleBlockTone): string | undefined {
|
|
119
|
+
if (tone === 'away' || tone === 'uncovered') return cellGround(theme);
|
|
120
|
+
if (tone === 'plan') return flatten(cellGround(theme), theme.palette.action.hover);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
/**
|
|
112
125
|
* A break inside a block (a meal break): the page's own ground with diagonal
|
|
113
126
|
* stripes of the disabled action colour, opaque, so the block reads as CUT there in any
|
|
@@ -29,6 +29,16 @@ Whichever of the three entry points you use, you get the same contract:
|
|
|
29
29
|
- **The popup names the thing.** `entityName` is echoed into the body, so it
|
|
30
30
|
reads "Bebidas", not "este item".
|
|
31
31
|
|
|
32
|
+
## On a phone
|
|
33
|
+
|
|
34
|
+
The popup is an `AlertDialog`, so it takes that dialog's narrow layout
|
|
35
|
+
(FUT-3831): given less than 480px — a container query, never a media query —
|
|
36
|
+
the icon moves above the title so the question has the paper's whole width, and
|
|
37
|
+
the buttons stack each across the row, **the verb on top and Cancel at the
|
|
38
|
+
bottom**, the library's stacked order (`components-guidelines.md`, "Stacked
|
|
39
|
+
buttons"). The keyboard contract is unchanged: a destructive popup still opens
|
|
40
|
+
with focus on Cancel, and Cancel is still the first button to tab to.
|
|
41
|
+
|
|
32
42
|
## Three entry points
|
|
33
43
|
|
|
34
44
|
### `withConfirmation(Component, defaults?)` — the HOC
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DayRuler/DayRuler.parts.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport type * as React from 'react';\nimport { useRef } from 'react';\n\nimport { ScheduleNameCell } from '../ScheduleGrid/ScheduleNameCell';\nimport type { RulerAbilities, RulerScale } from './DayRuler.keyboard';\nimport { DAY_RULER_METRICS as M } from './DayRuler.metrics';\nimport { useTextFits } from './useTextFits';\nimport { formatHour, formatRange, percentOf, ticksOf } from './day-ruler-model';\nimport { SR_ONLY_SX } from '../ScheduleGrid/ScheduleGrid.styles';\nimport {\n actionsSx,\n actualLabelSx,\n actualSx,\n axisSx,\n blockSx,\n breakSx,\n coverageCellSx,\n coverageLabelSx,\n coverageSx,\n footerCellSx,\n footerNoteSx,\n handleSx,\n lineSx,\n nowSx,\n rowSx,\n trackSx,\n trendSx,\n whoSx,\n type Span,\n} from './DayRuler.styles';\nimport type { DayRulerBlock, DayRulerFooterCell, DayRulerFooterRow, DayRulerRow, RulerHour } from './DayRuler.types';\nimport type { RulerDrag, RulerDragApi } from './useRulerDrag';\n\nexport const spanOf = (start: RulerHour, end: RulerHour, scale: RulerScale): Span => {\n const at = percentOf(start, scale.from, scale.to);\n return { at, share: Math.max(0, percentOf(end, scale.from, scale.to) - at) };\n};\n\n/** What every row of one ruler shares. */\nexport interface RowContext {\n scale: RulerScale;\n now?: RulerHour;\n can: RulerAbilities;\n drag: RulerDrag | null;\n dragApi: RulerDragApi;\n onBlockClick?: (blockId: string) => void;\n onKeyCreate: (row: DayRulerRow) => void;\n onBlockKey: (event: React.KeyboardEvent, block: DayRulerBlock) => void;\n createLabel: (row: string) => string;\n testId: string;\n}\n\n/** The keys a block answers to, for `aria-keyshortcuts`. */\nfunction shortcutsOf(movable: boolean, resizable: boolean): string | undefined {\n const keys = [\n movable ? 'Alt+ArrowLeft Alt+ArrowRight' : '',\n resizable ? 'Shift+ArrowLeft Shift+ArrowRight Shift+Alt+ArrowLeft Shift+Alt+ArrowRight' : '',\n ];\n return keys.filter(Boolean).join(' ') || undefined;\n}\n\nfunction Handle({ side, onPointerDown, testId }: { side: 'l' | 'r'; onPointerDown: (event: React.PointerEvent<HTMLElement>) => void; testId: string }) {\n return <Box component=\"span\" aria-hidden sx={handleSx(side)} data-testid={testId} onPointerDown={onPointerDown} />;\n}\n\ninterface BlockProps {\n row: DayRulerRow;\n block: DayRulerBlock;\n /** Where it is drawn: its own hours, or where a drag has it now. */\n span: Span;\n /** The hours it is drawn at (its own, or a drag's). */\n at: { start: RulerHour; end: RulerHour };\n text: string;\n ctx: RowContext;\n testId: string;\n}\n\n/**\n * The block's break, where it falls inside the block as drawn: it moves with a\n * dragged block (same offset from its start) and is cut to the block's ends.\n */\nfunction breakShare(block: DayRulerBlock, start: RulerHour, end: RulerHour): Span | null {\n const span = block.breakSpan;\n const length = end - start;\n if (!span || !(length > 0)) return null;\n const shift = Math.abs(length - (block.end - block.start)) < 1e-6 ? start - block.start : 0;\n const a = Math.max(start, span.start + shift);\n const b = Math.min(end, span.end + shift);\n if (!(b > a)) return null;\n return { at: ((a - start) / length) * 100, share: ((b - a) / length) * 100 };\n}\n\n/** The block's break as a hatched band, when it falls inside the block as drawn. */\nfunction BreakBand({ block, at, testId }: { block: DayRulerBlock; at: BlockProps['at']; testId: string }): React.JSX.Element | null {\n const gap = breakShare(block, at.start, at.end);\n if (!gap) return null;\n return <Box component=\"span\" aria-hidden sx={breakSx(gap)} data-dr-break=\"\" data-testid={testId} />;\n}\n\nfunction RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): React.JSX.Element {\n const { can, dragApi, scale } = ctx;\n const movable = can.move && Boolean(block.movable);\n const resizable = can.resize && Boolean(block.resizable);\n const ref = useRef<HTMLButtonElement | null>(null);\n // Short by pixels; where nothing can be measured, by the prototype's 30% rule.\n const fits = useTextFits(ref, text, (block.end - block.start) / (scale.to - scale.from) >= M.narrowShare);\n const press = (mode: 'move' | 'start' | 'end') => (event: React.PointerEvent<HTMLElement>) =>\n dragApi.startBlock(event, row.id, block, mode);\n return (\n <Box\n component=\"button\"\n type=\"button\"\n sx={blockSx(block.tone, span, { selected: Boolean(block.selected), movable })}\n title={block.title ?? block.label}\n aria-keyshortcuts={shortcutsOf(movable, resizable)}\n aria-current={block.selected ? 'true' : undefined}\n ref={ref}\n data-narrow={fits ? undefined : ''}\n data-tone={block.tone}\n data-testid={testId}\n onPointerDown={movable ? press('move') : undefined}\n onClick={() => {\n if (!dragApi.takeClick()) ctx.onBlockClick?.(block.id);\n }}\n onKeyDown={(event: React.KeyboardEvent) => ctx.onBlockKey(event, block)}\n >\n <BreakBand block={block} at={at} testId={`${ctx.testId}-break-${block.id}`} />\n {resizable ? <Handle side=\"l\" onPointerDown={press('start')} testId={`${ctx.testId}-handle-l-${block.id}`} /> : null}\n <span data-dr-text=\"full\" title={text}>\n {text}\n </span>\n <span data-dr-text=\"short\" title={text}>\n {block.shortLabel ?? text}\n </span>\n {resizable ? <Handle side=\"r\" onPointerDown={press('end')} testId={`${ctx.testId}-handle-r-${block.id}`} /> : null}\n </Box>\n );\n}\n\n/** A row's blocks, each where a drag has it now (with its live hours) or where it is. */\nfunction Blocks({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {\n const mine = ctx.drag && ctx.drag.rowId === row.id ? ctx.drag : null;\n return (\n <>\n {row.blocks.map((block) => {\n const live = mine?.blockId === block.id ? mine : null;\n const start = live ? live.start : block.start;\n const end = live ? live.end : block.end;\n return (\n <RulerBlock\n key={block.id}\n row={row}\n block={block}\n span={spanOf(start, end, ctx.scale)}\n at={{ start, end }}\n text={live?.moved ? formatRange(start, end) : block.label}\n ctx={ctx}\n testId={`${ctx.testId}-block-${block.id}`}\n />\n );\n })}\n {mine && mine.mode === 'create' ? <CreatePreview row={row} drag={mine} ctx={ctx} /> : null}\n </>\n );\n}\n\n/** The block a create drag is drawing, with its live hours. */\nfunction CreatePreview({ row, drag, ctx }: { row: DayRulerRow; drag: RulerDrag; ctx: RowContext }): React.JSX.Element {\n const range = formatRange(drag.start, drag.end);\n return (\n <RulerBlock\n row={row}\n block={{ id: '\\u0000new', start: drag.start, end: drag.end, label: range, tone: 'plan' }}\n span={spanOf(drag.start, Math.max(drag.end, drag.start + ctx.scale.snap), ctx.scale)}\n at={{ start: drag.start, end: drag.end }}\n text={range}\n ctx={ctx}\n testId={`${ctx.testId}-preview`}\n />\n );\n}\n\n/** The real shift under the blocks, and its label at its start. */\nfunction Actual({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element | null {\n const { actual } = row;\n if (!actual) return null;\n return (\n <>\n <Box sx={actualSx(spanOf(actual.start, actual.end, ctx.scale))} data-testid={`${ctx.testId}-actual-${row.id}`} />\n {row.actualLabel ? (\n <Box component=\"span\" sx={actualLabelSx(percentOf(actual.start, ctx.scale.from, ctx.scale.to))}>\n {row.actualLabel}\n </Box>\n ) : null}\n </>\n );\n}\n\n/** A row's track: the actual bar, its blocks (or the one being dragged, live) and the now line. */\nfunction Track({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {\n const { scale, now, can, dragApi } = ctx;\n const showNow = now !== undefined && now >= scale.from && now <= scale.to;\n return (\n <Box\n sx={trackSx({ hours: scale.to - scale.from, tickHours: scale.tickHours, interactive: can.create })}\n data-dr-track=\"\"\n data-testid={`${ctx.testId}-track-${row.id}`}\n role={can.create ? 'group' : undefined}\n tabIndex={can.create ? 0 : undefined}\n aria-label={can.create ? ctx.createLabel(row.label) : undefined}\n onPointerDown={can.create ? (event: React.PointerEvent<HTMLElement>) => dragApi.startCreate(event, row.id) : undefined}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (!can.create || event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return;\n event.preventDefault();\n ctx.onKeyCreate(row);\n }}\n >\n <Actual row={row} ctx={ctx} />\n <Blocks row={row} ctx={ctx} />\n {showNow ? <Box sx={nowSx(percentOf(now, scale.from, scale.to))} data-testid={`${ctx.testId}-now`} /> : null}\n </Box>\n );\n}\n\n/** One person: name, meta and chips · the track · the row's actions. */\nexport function RulerRow({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {\n return (\n <Box sx={rowSx} data-testid={`${ctx.testId}-row-${row.id}`} role=\"listitem\">\n <ScheduleNameCell row={row} sx={whoSx} sizes={{ name: M.name, meta: M.meta, chipsTop: 0.75 }} />\n <Track row={row} ctx={ctx} />\n <Box sx={actionsSx}>{row.actions}</Box>\n </Box>\n );\n}\n\n/** \"Na escala, por hora\": a count per hour above the axis. */\nexport function Coverage(props: { coverage: readonly number[]; label?: string; scale: RulerScale; testId: string }): React.JSX.Element {\n const { coverage, label, scale, testId } = props;\n const hours = scale.to - scale.from;\n return (\n <Box sx={lineSx} data-testid={`${testId}-coverage`}>\n <Box component=\"span\" sx={coverageLabelSx}>\n {label}\n </Box>\n <Box sx={coverageSx(hours)}>\n {Array.from({ length: hours }, (_, index) => {\n const count = coverage[index] ?? 0;\n return (\n <Box key={index} component=\"span\" sx={coverageCellSx(count)} title={`${formatHour(scale.from + index)} · ${count}`}>\n {count > 0 ? count : ''}\n </Box>\n );\n })}\n </Box>\n <span />\n </Box>\n );\n}\n\n/** The points of a trend over the hours' centres, in a 100 × 40 box: the highest value near the top, `null` breaks nothing (it is skipped). */\nexport function trendPoints(values: readonly (number | null)[], hours: number): string {\n const present = values.slice(0, hours).filter((value): value is number => value !== null);\n const max = Math.max(0, ...present);\n return values\n .slice(0, hours)\n .flatMap((value, index) => {\n if (value === null) return [];\n const x = ((index + 0.5) * 100) / hours;\n const y = max > 0 ? 38 - (value / max) * 34 : 38;\n return [`${x.toFixed(2)},${y.toFixed(2)}`];\n })\n .join(' ');\n}\n\n/** One hour's default words: \"20:00 · 4\"; just the hour when it says nothing. */\nconst hourWords = (hour: RulerHour, text: string): string => (text ? `${formatHour(hour)} · ${text}` : formatHour(hour));\n\n/** What a reader hears for a marked hour: the host's `title`, else the hour, its count and that it is short. */\nfunction markedWords(cell: DayRulerFooterCell, hour: RulerHour, marked: string): string {\n return cell.title ?? `${hourWords(hour, cell.text)} · ${marked}`;\n}\n\ninterface FooterContext {\n hours: number;\n from: RulerHour;\n /** `labels.footerMarked`. */\n marked: string;\n}\n\nfunction FooterCells({ cells, ctx }: { cells: readonly DayRulerFooterCell[]; ctx: FooterContext }): React.JSX.Element {\n return (\n <Box sx={coverageSx(ctx.hours)}>\n {Array.from({ length: ctx.hours }, (_, index) => {\n const cell = cells[index] ?? { text: '' };\n const hour = ctx.from + index;\n return (\n <Box\n key={index}\n component=\"span\"\n sx={footerCellSx(Boolean(cell.marked))}\n title={cell.marked ? markedWords(cell, hour, ctx.marked) : (cell.title ?? hourWords(hour, cell.text))}\n data-marked={cell.marked ? '' : undefined}\n >\n {/* A marked hour is said in words too, not only by its outline and a tooltip touch never shows. */}\n {cell.marked ? (\n <>\n <span aria-hidden>{cell.text}</span>\n <Box component=\"span\" sx={SR_ONLY_SX}>\n {markedWords(cell, hour, ctx.marked)}\n </Box>\n </>\n ) : (\n cell.text\n )}\n </Box>\n );\n })}\n </Box>\n );\n}\n\n/** A trend's figures in words, for the reader the drawn line does not reach: \"11:00 4 · 12:00 6\". */\nfunction trendWords(values: readonly (number | null)[], ctx: FooterContext): string {\n return values\n .slice(0, ctx.hours)\n .flatMap((value, index) => (value === null ? [] : [`${formatHour(ctx.from + index)} ${Math.round(value * 10) / 10}`]))\n .join(' · ');\n}\n\nfunction FooterFigures({ row, ctx }: { row: DayRulerFooterRow; ctx: FooterContext }): React.JSX.Element {\n if (row.trend) {\n return (\n <Box component=\"span\" sx={{ display: 'block', minWidth: 0 }}>\n <Box component=\"svg\" viewBox=\"0 0 100 40\" preserveAspectRatio=\"none\" sx={trendSx} aria-hidden>\n <polyline fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} vectorEffect=\"non-scaling-stroke\" points={trendPoints(row.trend, ctx.hours)} />\n </Box>\n <Box component=\"span\" sx={SR_ONLY_SX}>\n {trendWords(row.trend, ctx)}\n </Box>\n </Box>\n );\n }\n if (row.note !== undefined) {\n return (\n <Box component=\"span\" sx={footerNoteSx(Boolean(row.marked))} data-marked={row.marked ? '' : undefined}>\n {row.note}\n </Box>\n );\n }\n return <FooterCells cells={row.cells ?? []} ctx={ctx} />;\n}\n\n/**\n * A group's lines under its rows (FUT-3882): counts per hour, a trend, or a\n * sentence, on the tracks' columns. `marked` is `labels.footerMarked`.\n */\nexport function GroupFooter(props: { rows: readonly DayRulerFooterRow[]; scale: RulerScale; testId: string; marked: string }): React.JSX.Element {\n const { rows, scale, testId } = props;\n const ctx: FooterContext = { hours: scale.to - scale.from, from: scale.from, marked: props.marked };\n return (\n <>\n {rows.map((row) => (\n <Box key={row.id} sx={lineSx} data-testid={`${testId}-footer-${row.id}`}>\n <Box component=\"span\" sx={coverageLabelSx}>\n {row.label}\n </Box>\n <FooterFigures row={row} ctx={ctx} />\n <span />\n </Box>\n ))}\n </>\n );\n}\n\n/** The hour labels under the coverage row. */\nexport function Axis({ scale, testId }: { scale: RulerScale; testId: string }): React.JSX.Element {\n return (\n <Box sx={lineSx} data-testid={`${testId}-axis`} aria-hidden>\n <span />\n <Box sx={axisSx}>\n {ticksOf(scale.from, scale.to, scale.tickHours).map((h) => (\n <span key={h} style={{ left: `${percentOf(h, scale.from, scale.to)}%` }}>\n {formatHour(h)}\n </span>\n ))}\n </Box>\n <span />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DayRuler/DayRuler.metrics.ts"],"sourcesContent":["/**\n * THE DAY RULER'S NUMBERS — the approved prototype's (FUT-3823, frames d02 at\n * 1280 and the phone rows), in px. The web reads them through `rem`/`sxRem`.\n */\nexport const DAY_RULER_METRICS = {\n /** The name and the actions columns on a computer. */\n sideColumn: 220,\n gap: 16,\n /**\n * Below this container width a row stacks: name and actions on top, the\n * track under them. Two 220px side columns, their gaps and a track of at\n * least 320px (where the hour labels every 4 hours do not collide): 792,\n * rounded up.\n */\n compactBelow: 800,\n /**\n * Below this container width the pane is phone-sized: no coverage row and\n * no hour axis. Between it and `compactBelow` the rows stack and the two\n * lines are drawn across the full width, like the tracks.\n */\n phoneBelow: 600,\n rowPaddingY: 8,\n track: { height: 40, compactHeight: 30, radius: 8, compactRadius: 6 },\n block: { top: 4, height: 24, compactTop: 3, compactHeight: 20, radius: 6, paddingX: 6, font: 12, hitArea: 40 },\n /** The end handles' hit area. */\n handle: 10,\n actual: { top: 31, height: 6, compactTop: 24, compactHeight: 4, radius: 3 },\n actualLabel: { top: 8, offset: 6, font: 12 },\n now: { width: 2, overhang: 2 },\n coverage: { height: 26, gap: 2, radius: 4, font: 12 },\n axis: { height: 18, font: 12 },\n name: 15,\n meta: 13,\n groupTitle: 12,\n ring: 2,\n /** How far a block's focus ring stands off it. */\n ringOffset: 1,\n /** The loading row's name bar. */\n skeletonNameRadius: 4,\n /** Where a block's text cannot be measured, one shorter than this share of the ruler says only its start on a narrow ruler. */\n narrowShare: 0.3,\n /** The shortest block a resize leaves, in hours. */\n minHours: 0.5,\n skeletonRows: 4,\n /** The hatch of a break inside a block: stripe width. */\n breakStripe: { ink: 2, gap: 3 },\n} as const;\n"],"mappings":";AAIO,IAAM,oBAAoB;AAAA;AAAA,EAE/B,YAAY;AAAA,EACZ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOL,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,OAAO,EAAE,QAAQ,IAAI,eAAe,IAAI,QAAQ,GAAG,eAAe,EAAE;AAAA,EACpE,OAAO,EAAE,KAAK,GAAG,QAAQ,IAAI,YAAY,GAAG,eAAe,IAAI,QAAQ,GAAG,UAAU,GAAG,MAAM,IAAI,SAAS,GAAG;AAAA;AAAA,EAE7G,QAAQ;AAAA,EACR,QAAQ,EAAE,KAAK,IAAI,QAAQ,GAAG,YAAY,IAAI,eAAe,GAAG,QAAQ,EAAE;AAAA,EAC1E,aAAa,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG;AAAA,EAC3C,KAAK,EAAE,OAAO,GAAG,UAAU,EAAE;AAAA,EAC7B,UAAU,EAAE,QAAQ,IAAI,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG;AAAA,EACpD,MAAM,EAAE,QAAQ,IAAI,MAAM,GAAG;AAAA,EAC7B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,MAAM;AAAA;AAAA,EAEN,YAAY;AAAA;AAAA,EAEZ,oBAAoB;AAAA;AAAA,EAEpB,aAAa;AAAA;AAAA,EAEb,UAAU;AAAA,EACV,cAAc;AAAA;AAAA,EAEd,aAAa,EAAE,KAAK,GAAG,KAAK,EAAE;AAChC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/AlertDialog/AlertDialog.parts.tsx"],"sourcesContent":["/**\n * The AlertDialog's action row, kept apart from the dialog shell so each stays\n * inside the complexity budget: which button is filled, where each one sits,\n * and how the row wraps.\n */\nimport type { ButtonProps } from '@mui/material/Button/index.js';\nimport Button from '@mui/material/Button/index.js';\nimport CircularProgress from '@mui/material/CircularProgress/index.js';\nimport DialogActions from '@mui/material/DialogActions/index.js';\nimport { styled, useTheme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { rem } from '../../../tokens/relative';\nimport { EN_US_UI_LABELS } from '../../../en-US.labels';\n\nimport type { AlertDialogProps } from './AlertDialog.types';\n\n/**\n * The action row WRAPS WHOLE BUTTONS; it never breaks a label inside one. A pair\n * that did not fit side by side used to squeeze the longer label onto two\n * lines, so one button stood twice as tall as its neighbour. Now the row breaks\n * instead. `wrap-reverse` puts the LAST button — the primary, whichever\n * `emphasis` picks — on top when it stacks, the same slot it holds on the right\n * when the row fits. A pair that fits is laid out exactly as before: natural\n * widths, pushed to the right.\n *\n * `fill` — set only by `emphasis=\"cancel\"` — lets the buttons grow to share the\n * row, and so to take the full width of their line once it stacks. It is not\n * the default because growing also stretches a pair that fits, which would\n * redraw every existing dialog's footer.\n *\n * Rendered with `disableSpacing`: MUI's own spacing is a left margin on every\n * button after the first, which would indent a button that wrapped onto a line\n * of its own. The gaps below replace it, at the same 16px across.\n */\nconst StyledDialogActions = styled(DialogActions, {\n shouldForwardProp: (prop) => prop !== 'fill',\n})<{ fill: boolean }>(({ theme, fill }) => ({\n padding: theme.spacing(2, 3, 3, 3),\n flexWrap: 'wrap-reverse',\n columnGap: theme.spacing(2),\n rowGap: theme.spacing(1),\n // A 40px tap target, the floor the kit's own Button keeps (FUT-3269): MUI's\n // medium button is 37px, short of it on a phone.\n '& > .MuiButton-root': { minHeight: rem(theme, 40) },\n ...(fill && { '& > .MuiButton-root': { flexGrow: 1, minHeight: rem(theme, 40) } }),\n}));\n\nconst confirmButtonColor = (variant: AlertDialogProps['variant']): 'error' | 'primary' =>\n variant === 'destructive' ? 'error' : 'primary';\n\ninterface ButtonLook {\n variant: ButtonProps['variant'];\n color: ButtonProps['color'];\n}\n\n/**\n * How each button is drawn, by `emphasis`: the emphasised one is filled, the\n * other a NEUTRAL outline. Primary / neutral both ways round — a confirm that\n * steps down drops the variant's colour too, so the one filled button is the\n * only coloured thing in the row and the eye has a single place to land.\n */\nconst buttonLooks = (\n emphasis: AlertDialogProps['emphasis'],\n variant: AlertDialogProps['variant'],\n): { cancel: ButtonLook; confirm: ButtonLook } =>\n emphasis === 'cancel'\n ? {\n cancel: { variant: 'contained', color: 'primary' },\n confirm: { variant: 'outlined', color: 'inherit' },\n }\n : {\n cancel: { variant: 'outlined', color: 'inherit' },\n confirm: { variant: 'contained', color: confirmButtonColor(variant) },\n };\n\n/**\n * The emphasis that applies: `cancel` only while the cancel is shown. A hidden\n * cancel with the confirm stepped down would leave a dialog with no primary.\n */\nconst effectiveEmphasis = (\n emphasis: AlertDialogProps['emphasis'],\n showCancel: boolean,\n): NonNullable<AlertDialogProps['emphasis']> =>\n emphasis === 'cancel' && showCancel ? 'cancel' : 'confirm';\n\n/**\n * Whether the cancel owns focus on open: what `initialFocus` says, and when it\n * says nothing, the emphasised button — so Enter on a steered question keeps\n * the work instead of completing the loss.\n */\nconst focusesCancel = (\n initialFocus: AlertDialogProps['initialFocus'],\n emphasis: NonNullable<AlertDialogProps['emphasis']>,\n): boolean => (initialFocus ?? emphasis) === 'cancel';\n\n/** The two buttons in slot order: the emphasised one LAST, the primary slot. */\nconst inSlotOrder = (\n emphasis: AlertDialogProps['emphasis'],\n cancel: React.ReactNode,\n confirm: React.ReactNode,\n): React.ReactNode[] => (emphasis === 'cancel' ? [confirm, cancel] : [cancel, confirm]);\n\n/**\n * Action row: the optional cancel button and the (variant-coloured) confirm,\n * with the emphasised one LAST — the primary slot, on the right or, once the\n * row stacks, on top. DOM order is the tab order, so it follows the same rule.\n */\nexport function AlertDialogFooter({\n variant,\n cancelText = EN_US_UI_LABELS.alertDialog.cancel,\n confirmText = EN_US_UI_LABELS.alertDialog.confirm,\n showCancel = true,\n loading = false,\n confirmDisabled = false,\n initialFocus,\n emphasis: asked,\n onCancel,\n onConfirm,\n dataTestId,\n}: Pick<\n AlertDialogProps,\n | 'variant'\n | 'cancelText'\n | 'confirmText'\n | 'showCancel'\n | 'loading'\n | 'confirmDisabled'\n | 'initialFocus'\n | 'emphasis'\n> & {\n onCancel: () => void;\n onConfirm: () => void;\n dataTestId: string;\n}): React.ReactElement {\n const theme = useTheme();\n const emphasis = effectiveEmphasis(asked, showCancel);\n const look = buttonLooks(emphasis, variant);\n const cancelFocused = focusesCancel(initialFocus, emphasis);\n const cancel = showCancel && (\n <Button\n key=\"cancel\"\n onClick={onCancel}\n variant={look.cancel.variant}\n color={look.cancel.color}\n disabled={loading}\n autoFocus={cancelFocused}\n data-testid={`${dataTestId}-cancel-button`}\n >\n {cancelText}\n </Button>\n );\n const confirm = (\n <Button\n key=\"confirm\"\n onClick={onConfirm}\n variant={look.confirm.variant}\n color={look.confirm.color}\n disabled={confirmDisabled || loading}\n startIcon={\n loading ? (\n <CircularProgress\n size={rem(theme, 16)}\n color=\"inherit\"\n data-testid={`${dataTestId}-loading-spinner`}\n />\n ) : undefined\n }\n autoFocus={!cancelFocused}\n data-testid={`${dataTestId}-confirm-button`}\n >\n {confirmText}\n </Button>\n );\n return (\n <StyledDialogActions\n disableSpacing\n fill={emphasis === 'cancel'}\n data-testid={`${dataTestId}-actions`}\n >\n {inSlotOrder(emphasis, cancel, confirm)}\n </StyledDialogActions>\n );\n}\n"],"mappings":";;;;;;;;AAMA,OAAO,YAAY;AACnB,OAAO,sBAAsB;AAC7B,OAAO,mBAAmB;AAC1B,SAAS,QAAQ,gBAAgB;AAmI7B;AAzGJ,IAAM,sBAAsB,OAAO,eAAe;AAAA,EAChD,mBAAmB,CAAC,SAAS,SAAS;AACxC,CAAC,EAAqB,CAAC,EAAE,OAAO,KAAK,OAAO;AAAA,EAC1C,SAAS,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC;AAAA,EACjC,UAAU;AAAA,EACV,WAAW,MAAM,QAAQ,CAAC;AAAA,EAC1B,QAAQ,MAAM,QAAQ,CAAC;AAAA;AAAA;AAAA,EAGvB,uBAAuB,EAAE,WAAW,IAAI,OAAO,EAAE,EAAE;AAAA,EACnD,GAAI,QAAQ,EAAE,uBAAuB,EAAE,UAAU,GAAG,WAAW,IAAI,OAAO,EAAE,EAAE,EAAE;AAClF,EAAE;AAEF,IAAM,qBAAqB,CAAC,YAC1B,YAAY,gBAAgB,UAAU;AAaxC,IAAM,cAAc,CAClB,UACA,YAEA,aAAa,WACT;AAAA,EACE,QAAQ,EAAE,SAAS,aAAa,OAAO,UAAU;AAAA,EACjD,SAAS,EAAE,SAAS,YAAY,OAAO,UAAU;AACnD,IACA;AAAA,EACE,QAAQ,EAAE,SAAS,YAAY,OAAO,UAAU;AAAA,EAChD,SAAS,EAAE,SAAS,aAAa,OAAO,mBAAmB,OAAO,EAAE;AACtE;AAMN,IAAM,oBAAoB,CACxB,UACA,eAEA,aAAa,YAAY,aAAa,WAAW;AAOnD,IAAM,gBAAgB,CACpB,cACA,cACa,gBAAgB,cAAc;AAG7C,IAAM,cAAc,CAClB,UACA,QACA,YACuB,aAAa,WAAW,CAAC,SAAS,MAAM,IAAI,CAAC,QAAQ,OAAO;AAO9E,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA,aAAa,gBAAgB,YAAY;AAAA,EACzC,cAAc,gBAAgB,YAAY;AAAA,EAC1C,aAAa;AAAA,EACb,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AACF,GAcuB;AACrB,QAAM,QAAQ,SAAS;AACvB,QAAM,WAAW,kBAAkB,OAAO,UAAU;AACpD,QAAM,OAAO,YAAY,UAAU,OAAO;AAC1C,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAC1D,QAAM,SAAS,cACb;AAAA,IAAC;AAAA;AAAA,MAEC,SAAS;AAAA,MACT,SAAS,KAAK,OAAO;AAAA,MACrB,OAAO,KAAK,OAAO;AAAA,MACnB,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAa,GAAG,UAAU;AAAA,MAEzB;AAAA;AAAA,IARG;AAAA,EASN;AAEF,QAAM,UACJ;AAAA,IAAC;AAAA;AAAA,MAEC,SAAS;AAAA,MACT,SAAS,KAAK,QAAQ;AAAA,MACtB,OAAO,KAAK,QAAQ;AAAA,MACpB,UAAU,mBAAmB;AAAA,MAC7B,WACE,UACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,IAAI,OAAO,EAAE;AAAA,UACnB,OAAM;AAAA,UACN,eAAa,GAAG,UAAU;AAAA;AAAA,MAC5B,IACE;AAAA,MAEN,WAAW,CAAC;AAAA,MACZ,eAAa,GAAG,UAAU;AAAA,MAEzB;AAAA;AAAA,IAjBG;AAAA,EAkBN;AAEF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,gBAAc;AAAA,MACd,MAAM,aAAa;AAAA,MACnB,eAAa,GAAG,UAAU;AAAA,MAEzB,sBAAY,UAAU,QAAQ,OAAO;AAAA;AAAA,EACxC;AAEJ;","names":[]}
|