@12-apps/ui 6.93.0 → 6.93.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-catalog.md +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/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/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 +38 -36
- 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 +49 -47
- 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-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-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/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/ScheduleGrid.js +6 -6
- package/dist/feedback/ConfirmAction.js +6 -6
- 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/ScheduleGrid/schedule-tones.d.ts +8 -0
- package/dist/types/components/data-display/ScheduleGrid/schedule-tones.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/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-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-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
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import Box from '@mui/material/Box/index.js';
|
|
2
2
|
import type * as React from 'react';
|
|
3
|
-
import { useRef } from 'react';
|
|
3
|
+
import { Fragment, isValidElement, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { ScheduleNameCell } from '../ScheduleGrid/ScheduleNameCell';
|
|
6
6
|
import type { RulerAbilities, RulerScale } from './DayRuler.keyboard';
|
|
7
7
|
import { DAY_RULER_METRICS as M } from './DayRuler.metrics';
|
|
8
|
-
import {
|
|
9
|
-
import { formatHour, formatRange, percentOf, ticksOf } from './day-ruler-model';
|
|
10
|
-
import { SR_ONLY_SX } from '../ScheduleGrid/ScheduleGrid.styles';
|
|
8
|
+
import { useTextFit } from './useTextFits';
|
|
9
|
+
import { formatHour, formatRange, hitBoundsOf, percentOf, ticksOf, type BlockTextFit } from './day-ruler-model';
|
|
11
10
|
import {
|
|
12
11
|
actionsSx,
|
|
13
12
|
actualLabelSx,
|
|
@@ -18,18 +17,16 @@ import {
|
|
|
18
17
|
coverageCellSx,
|
|
19
18
|
coverageLabelSx,
|
|
20
19
|
coverageSx,
|
|
21
|
-
footerCellSx,
|
|
22
|
-
footerNoteSx,
|
|
23
20
|
handleSx,
|
|
24
21
|
lineSx,
|
|
25
22
|
nowSx,
|
|
26
23
|
rowSx,
|
|
27
24
|
trackSx,
|
|
28
|
-
trendSx,
|
|
29
25
|
whoSx,
|
|
26
|
+
type HitBounds,
|
|
30
27
|
type Span,
|
|
31
28
|
} from './DayRuler.styles';
|
|
32
|
-
import type { DayRulerBlock,
|
|
29
|
+
import type { DayRulerBlock, DayRulerRow, RulerHour } from './DayRuler.types';
|
|
33
30
|
import type { RulerDrag, RulerDragApi } from './useRulerDrag';
|
|
34
31
|
|
|
35
32
|
export const spanOf = (start: RulerHour, end: RulerHour, scale: RulerScale): Span => {
|
|
@@ -71,6 +68,8 @@ interface BlockProps {
|
|
|
71
68
|
span: Span;
|
|
72
69
|
/** The hours it is drawn at (its own, or a drag's). */
|
|
73
70
|
at: { start: RulerHour; end: RulerHour };
|
|
71
|
+
/** How far its hit band may reach on a narrow ruler, in % of the track (the midpoints to its neighbours). */
|
|
72
|
+
hit: HitBounds;
|
|
74
73
|
text: string;
|
|
75
74
|
ctx: RowContext;
|
|
76
75
|
testId: string;
|
|
@@ -98,25 +97,39 @@ function BreakBand({ block, at, testId }: { block: DayRulerBlock; at: BlockProps
|
|
|
98
97
|
return <Box component="span" aria-hidden sx={breakSx(gap)} data-dr-break="" data-testid={testId} />;
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
/** Where nothing can be measured, a block shorter than the prototype's 30% of the ruler says its start. */
|
|
101
|
+
function fallbackFit(block: DayRulerBlock, scale: RulerScale): BlockTextFit {
|
|
102
|
+
return (block.end - block.start) / (scale.to - scale.from) >= M.narrowShare ? 'full' : 'short';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What the bar's fit puts on it: `data-fit` for the styles, and — while the bar
|
|
107
|
+
* shows less than its full text — that full text as its name, so the name still
|
|
108
|
+
* says all of it.
|
|
109
|
+
*/
|
|
110
|
+
function fitProps(fit: BlockTextFit, text: string): { 'aria-label'?: string; 'data-fit'?: BlockTextFit } {
|
|
111
|
+
return fit === 'full' ? {} : { 'aria-label': text, 'data-fit': fit };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function RulerBlock({ row, block, span, at, hit, text, ctx, testId }: BlockProps): React.JSX.Element {
|
|
102
115
|
const { can, dragApi, scale } = ctx;
|
|
103
116
|
const movable = can.move && Boolean(block.movable);
|
|
104
117
|
const resizable = can.resize && Boolean(block.resizable);
|
|
105
118
|
const ref = useRef<HTMLButtonElement | null>(null);
|
|
106
|
-
|
|
107
|
-
const
|
|
119
|
+
const short = block.shortLabel ?? text;
|
|
120
|
+
const fit = useTextFit(ref, text, short, fallbackFit(block, scale));
|
|
108
121
|
const press = (mode: 'move' | 'start' | 'end') => (event: React.PointerEvent<HTMLElement>) =>
|
|
109
122
|
dragApi.startBlock(event, row.id, block, mode);
|
|
110
123
|
return (
|
|
111
124
|
<Box
|
|
112
125
|
component="button"
|
|
113
126
|
type="button"
|
|
114
|
-
sx={blockSx(block.tone, span, { selected: Boolean(block.selected), movable })}
|
|
127
|
+
sx={blockSx(block.tone, span, { selected: Boolean(block.selected), movable, hit })}
|
|
115
128
|
title={block.title ?? block.label}
|
|
129
|
+
{...fitProps(fit, text)}
|
|
116
130
|
aria-keyshortcuts={shortcutsOf(movable, resizable)}
|
|
117
131
|
aria-current={block.selected ? 'true' : undefined}
|
|
118
132
|
ref={ref}
|
|
119
|
-
data-narrow={fits ? undefined : ''}
|
|
120
133
|
data-tone={block.tone}
|
|
121
134
|
data-testid={testId}
|
|
122
135
|
onPointerDown={movable ? press('move') : undefined}
|
|
@@ -131,7 +144,7 @@ function RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): Re
|
|
|
131
144
|
{text}
|
|
132
145
|
</span>
|
|
133
146
|
<span data-dr-text="short" title={text}>
|
|
134
|
-
{
|
|
147
|
+
{short}
|
|
135
148
|
</span>
|
|
136
149
|
{resizable ? <Handle side="r" onPointerDown={press('end')} testId={`${ctx.testId}-handle-r-${block.id}`} /> : null}
|
|
137
150
|
</Box>
|
|
@@ -141,39 +154,51 @@ function RulerBlock({ row, block, span, at, text, ctx, testId }: BlockProps): Re
|
|
|
141
154
|
/** A row's blocks, each where a drag has it now (with its live hours) or where it is. */
|
|
142
155
|
function Blocks({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
|
|
143
156
|
const mine = ctx.drag && ctx.drag.rowId === row.id ? ctx.drag : null;
|
|
157
|
+
const drawn = row.blocks.map((block) => {
|
|
158
|
+
const live = mine?.blockId === block.id ? mine : null;
|
|
159
|
+
const start = live ? live.start : block.start;
|
|
160
|
+
const end = live ? live.end : block.end;
|
|
161
|
+
return { block, live, start, end, span: spanOf(start, end, ctx.scale) };
|
|
162
|
+
});
|
|
163
|
+
const preview = mine && mine.mode === 'create' ? previewSpan(mine, ctx.scale) : null;
|
|
164
|
+
// Every bar drawn on the row, the create's preview included, bounds its neighbours' hit bands.
|
|
165
|
+
const hits = hitBoundsOf([...drawn.map((d) => d.span), ...(preview ? [preview] : [])]);
|
|
144
166
|
return (
|
|
145
167
|
<>
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/>
|
|
161
|
-
);
|
|
162
|
-
})}
|
|
163
|
-
{mine && mine.mode === 'create' ? <CreatePreview row={row} drag={mine} ctx={ctx} /> : null}
|
|
168
|
+
{drawn.map(({ block, live, start, end, span }, index) => (
|
|
169
|
+
<RulerBlock
|
|
170
|
+
key={block.id}
|
|
171
|
+
row={row}
|
|
172
|
+
block={block}
|
|
173
|
+
span={span}
|
|
174
|
+
at={{ start, end }}
|
|
175
|
+
hit={hits[index]!}
|
|
176
|
+
text={live?.moved ? formatRange(start, end) : block.label}
|
|
177
|
+
ctx={ctx}
|
|
178
|
+
testId={`${ctx.testId}-block-${block.id}`}
|
|
179
|
+
/>
|
|
180
|
+
))}
|
|
181
|
+
{mine && preview ? <CreatePreview row={row} drag={mine} span={preview} hit={hits[drawn.length]!} ctx={ctx} /> : null}
|
|
164
182
|
</>
|
|
165
183
|
);
|
|
166
184
|
}
|
|
167
185
|
|
|
186
|
+
/** Where a create drag's preview is drawn: at least one snap long. */
|
|
187
|
+
function previewSpan(drag: RulerDrag, scale: RulerScale): Span {
|
|
188
|
+
return spanOf(drag.start, Math.max(drag.end, drag.start + scale.snap), scale);
|
|
189
|
+
}
|
|
190
|
+
|
|
168
191
|
/** The block a create drag is drawing, with its live hours. */
|
|
169
|
-
function CreatePreview(
|
|
192
|
+
function CreatePreview(props: { row: DayRulerRow; drag: RulerDrag; span: Span; hit: HitBounds; ctx: RowContext }): React.JSX.Element {
|
|
193
|
+
const { row, drag, span, hit, ctx } = props;
|
|
170
194
|
const range = formatRange(drag.start, drag.end);
|
|
171
195
|
return (
|
|
172
196
|
<RulerBlock
|
|
173
197
|
row={row}
|
|
174
198
|
block={{ id: '\u0000new', start: drag.start, end: drag.end, label: range, tone: 'plan' }}
|
|
175
|
-
span={
|
|
199
|
+
span={span}
|
|
176
200
|
at={{ start: drag.start, end: drag.end }}
|
|
201
|
+
hit={hit}
|
|
177
202
|
text={range}
|
|
178
203
|
ctx={ctx}
|
|
179
204
|
testId={`${ctx.testId}-preview`}
|
|
@@ -197,7 +222,7 @@ function Actual({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.
|
|
|
197
222
|
);
|
|
198
223
|
}
|
|
199
224
|
|
|
200
|
-
/** A row's track: the actual bar, its blocks (or the one being dragged, live)
|
|
225
|
+
/** A row's track: the actual bar, the now line under its blocks (or the one being dragged, live). */
|
|
201
226
|
function Track({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
|
|
202
227
|
const { scale, now, can, dragApi } = ctx;
|
|
203
228
|
const showNow = now !== undefined && now >= scale.from && now <= scale.to;
|
|
@@ -217,19 +242,42 @@ function Track({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.E
|
|
|
217
242
|
}}
|
|
218
243
|
>
|
|
219
244
|
<Actual row={row} ctx={ctx} />
|
|
220
|
-
|
|
245
|
+
{/* Before the blocks, so it paints under them and never through a label. */}
|
|
221
246
|
{showNow ? <Box sx={nowSx(percentOf(now, scale.from, scale.to))} data-testid={`${ctx.testId}-now`} /> : null}
|
|
247
|
+
<Blocks row={row} ctx={ctx} />
|
|
222
248
|
</Box>
|
|
223
249
|
);
|
|
224
250
|
}
|
|
225
251
|
|
|
226
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* Whether a node draws anything: nothing, a boolean, an empty string, an empty
|
|
254
|
+
* array and an empty fragment do not — the actions slot (and its 8px above)
|
|
255
|
+
* is rendered only for content.
|
|
256
|
+
*/
|
|
257
|
+
export function hasContent(node: React.ReactNode): boolean {
|
|
258
|
+
if (node === null || node === undefined || typeof node === 'boolean' || node === '') return false;
|
|
259
|
+
if (Array.isArray(node)) return node.some((child) => hasContent(child as React.ReactNode));
|
|
260
|
+
if (isValidElement<{ children?: React.ReactNode }>(node) && node.type === Fragment) return hasContent(node.props.children);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* One person: name, meta and chips, the row's actions under them · the track.
|
|
266
|
+
* The actions live in the name's column, never a column of their own: a
|
|
267
|
+
* column reserved for one row's "Iniciar agora" left every other row a blank
|
|
268
|
+
* band, and a per-row one would have cut the hours out of line (FUT-3831).
|
|
269
|
+
*/
|
|
227
270
|
export function RulerRow({ row, ctx }: { row: DayRulerRow; ctx: RowContext }): React.JSX.Element {
|
|
271
|
+
const hasActions = hasContent(row.actions);
|
|
228
272
|
return (
|
|
229
273
|
<Box sx={rowSx} data-testid={`${ctx.testId}-row-${row.id}`} role="listitem">
|
|
230
274
|
<ScheduleNameCell row={row} sx={whoSx} sizes={{ name: M.name, meta: M.meta, chipsTop: 0.75 }} />
|
|
231
275
|
<Track row={row} ctx={ctx} />
|
|
232
|
-
|
|
276
|
+
{hasActions ? (
|
|
277
|
+
<Box sx={actionsSx} data-testid={`${ctx.testId}-actions-${row.id}`}>
|
|
278
|
+
{row.actions}
|
|
279
|
+
</Box>
|
|
280
|
+
) : null}
|
|
233
281
|
</Box>
|
|
234
282
|
);
|
|
235
283
|
}
|
|
@@ -253,126 +301,10 @@ export function Coverage(props: { coverage: readonly number[]; label?: string; s
|
|
|
253
301
|
);
|
|
254
302
|
})}
|
|
255
303
|
</Box>
|
|
256
|
-
<span />
|
|
257
304
|
</Box>
|
|
258
305
|
);
|
|
259
306
|
}
|
|
260
307
|
|
|
261
|
-
/** 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). */
|
|
262
|
-
export function trendPoints(values: readonly (number | null)[], hours: number): string {
|
|
263
|
-
const present = values.slice(0, hours).filter((value): value is number => value !== null);
|
|
264
|
-
const max = Math.max(0, ...present);
|
|
265
|
-
return values
|
|
266
|
-
.slice(0, hours)
|
|
267
|
-
.flatMap((value, index) => {
|
|
268
|
-
if (value === null) return [];
|
|
269
|
-
const x = ((index + 0.5) * 100) / hours;
|
|
270
|
-
const y = max > 0 ? 38 - (value / max) * 34 : 38;
|
|
271
|
-
return [`${x.toFixed(2)},${y.toFixed(2)}`];
|
|
272
|
-
})
|
|
273
|
-
.join(' ');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/** One hour's default words: "20:00 · 4"; just the hour when it says nothing. */
|
|
277
|
-
const hourWords = (hour: RulerHour, text: string): string => (text ? `${formatHour(hour)} · ${text}` : formatHour(hour));
|
|
278
|
-
|
|
279
|
-
/** What a reader hears for a marked hour: the host's `title`, else the hour, its count and that it is short. */
|
|
280
|
-
function markedWords(cell: DayRulerFooterCell, hour: RulerHour, marked: string): string {
|
|
281
|
-
return cell.title ?? `${hourWords(hour, cell.text)} · ${marked}`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
interface FooterContext {
|
|
285
|
-
hours: number;
|
|
286
|
-
from: RulerHour;
|
|
287
|
-
/** `labels.footerMarked`. */
|
|
288
|
-
marked: string;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function FooterCells({ cells, ctx }: { cells: readonly DayRulerFooterCell[]; ctx: FooterContext }): React.JSX.Element {
|
|
292
|
-
return (
|
|
293
|
-
<Box sx={coverageSx(ctx.hours)}>
|
|
294
|
-
{Array.from({ length: ctx.hours }, (_, index) => {
|
|
295
|
-
const cell = cells[index] ?? { text: '' };
|
|
296
|
-
const hour = ctx.from + index;
|
|
297
|
-
return (
|
|
298
|
-
<Box
|
|
299
|
-
key={index}
|
|
300
|
-
component="span"
|
|
301
|
-
sx={footerCellSx(Boolean(cell.marked))}
|
|
302
|
-
title={cell.marked ? markedWords(cell, hour, ctx.marked) : (cell.title ?? hourWords(hour, cell.text))}
|
|
303
|
-
data-marked={cell.marked ? '' : undefined}
|
|
304
|
-
>
|
|
305
|
-
{/* A marked hour is said in words too, not only by its outline and a tooltip touch never shows. */}
|
|
306
|
-
{cell.marked ? (
|
|
307
|
-
<>
|
|
308
|
-
<span aria-hidden>{cell.text}</span>
|
|
309
|
-
<Box component="span" sx={SR_ONLY_SX}>
|
|
310
|
-
{markedWords(cell, hour, ctx.marked)}
|
|
311
|
-
</Box>
|
|
312
|
-
</>
|
|
313
|
-
) : (
|
|
314
|
-
cell.text
|
|
315
|
-
)}
|
|
316
|
-
</Box>
|
|
317
|
-
);
|
|
318
|
-
})}
|
|
319
|
-
</Box>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** A trend's figures in words, for the reader the drawn line does not reach: "11:00 4 · 12:00 6". */
|
|
324
|
-
function trendWords(values: readonly (number | null)[], ctx: FooterContext): string {
|
|
325
|
-
return values
|
|
326
|
-
.slice(0, ctx.hours)
|
|
327
|
-
.flatMap((value, index) => (value === null ? [] : [`${formatHour(ctx.from + index)} ${Math.round(value * 10) / 10}`]))
|
|
328
|
-
.join(' · ');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function FooterFigures({ row, ctx }: { row: DayRulerFooterRow; ctx: FooterContext }): React.JSX.Element {
|
|
332
|
-
if (row.trend) {
|
|
333
|
-
return (
|
|
334
|
-
<Box component="span" sx={{ display: 'block', minWidth: 0 }}>
|
|
335
|
-
<Box component="svg" viewBox="0 0 100 40" preserveAspectRatio="none" sx={trendSx} aria-hidden>
|
|
336
|
-
<polyline fill="none" stroke="currentColor" strokeWidth={1.5} vectorEffect="non-scaling-stroke" points={trendPoints(row.trend, ctx.hours)} />
|
|
337
|
-
</Box>
|
|
338
|
-
<Box component="span" sx={SR_ONLY_SX}>
|
|
339
|
-
{trendWords(row.trend, ctx)}
|
|
340
|
-
</Box>
|
|
341
|
-
</Box>
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
if (row.note !== undefined) {
|
|
345
|
-
return (
|
|
346
|
-
<Box component="span" sx={footerNoteSx(Boolean(row.marked))} data-marked={row.marked ? '' : undefined}>
|
|
347
|
-
{row.note}
|
|
348
|
-
</Box>
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
return <FooterCells cells={row.cells ?? []} ctx={ctx} />;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* A group's lines under its rows (FUT-3882): counts per hour, a trend, or a
|
|
356
|
-
* sentence, on the tracks' columns. `marked` is `labels.footerMarked`.
|
|
357
|
-
*/
|
|
358
|
-
export function GroupFooter(props: { rows: readonly DayRulerFooterRow[]; scale: RulerScale; testId: string; marked: string }): React.JSX.Element {
|
|
359
|
-
const { rows, scale, testId } = props;
|
|
360
|
-
const ctx: FooterContext = { hours: scale.to - scale.from, from: scale.from, marked: props.marked };
|
|
361
|
-
return (
|
|
362
|
-
<>
|
|
363
|
-
{rows.map((row) => (
|
|
364
|
-
<Box key={row.id} sx={lineSx} data-testid={`${testId}-footer-${row.id}`}>
|
|
365
|
-
<Box component="span" sx={coverageLabelSx}>
|
|
366
|
-
{row.label}
|
|
367
|
-
</Box>
|
|
368
|
-
<FooterFigures row={row} ctx={ctx} />
|
|
369
|
-
<span />
|
|
370
|
-
</Box>
|
|
371
|
-
))}
|
|
372
|
-
</>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
308
|
/** The hour labels under the coverage row. */
|
|
377
309
|
export function Axis({ scale, testId }: { scale: RulerScale; testId: string }): React.JSX.Element {
|
|
378
310
|
return (
|
|
@@ -385,7 +317,6 @@ export function Axis({ scale, testId }: { scale: RulerScale; testId: string }):
|
|
|
385
317
|
</span>
|
|
386
318
|
))}
|
|
387
319
|
</Box>
|
|
388
|
-
<span />
|
|
389
320
|
</Box>
|
|
390
321
|
);
|
|
391
322
|
}
|
|
@@ -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']}
|