@multiplatform.one/components 7.11.0 → 7.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/Accordion/index.cjs +2 -6
- package/dist/cjs/Accordion/index.native.js +2 -6
- package/dist/cjs/Accordion/index.native.js.map +1 -1
- package/dist/cjs/AnimateHeight/index.cjs +1 -3
- package/dist/cjs/AnimateHeight/index.native.js +1 -3
- package/dist/cjs/AnimateHeight/index.native.js.map +1 -1
- package/dist/cjs/Chip/index.cjs +8 -1
- package/dist/cjs/Chip/index.native.js +8 -1
- package/dist/cjs/Chip/index.native.js.map +1 -1
- package/dist/cjs/SheetModal/index.native.js +70 -22
- package/dist/cjs/SheetModal/index.native.js.map +1 -1
- package/dist/cjs/Tabs/index.cjs +2 -6
- package/dist/cjs/Tabs/index.native.js +2 -6
- package/dist/cjs/Tabs/index.native.js.map +1 -1
- package/dist/cjs/Text.cjs +2 -0
- package/dist/cjs/Text.native.js +2 -0
- package/dist/cjs/Text.native.js.map +1 -1
- package/dist/cjs/TreeSelect/index.cjs +55 -52
- package/dist/cjs/TreeSelect/index.native.js +55 -52
- package/dist/cjs/TreeSelect/index.native.js.map +1 -1
- package/dist/cjs/Video/PlayerChrome.cjs +3 -9
- package/dist/cjs/Video/PlayerChrome.native.js +3 -9
- package/dist/cjs/Video/PlayerChrome.native.js.map +1 -1
- package/dist/cjs/code/Code/index.cjs +6 -0
- package/dist/cjs/code/Code/index.native.js +7 -4
- package/dist/cjs/code/Code/index.native.js.map +1 -1
- package/dist/cjs/code/CodeBlock/index.cjs +4 -5
- package/dist/cjs/code/CodeBlock/index.native.js +4 -5
- package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
- package/dist/cjs/icons/themed.cjs +2 -1
- package/dist/cjs/icons/themed.native.js +2 -1
- package/dist/cjs/icons/themed.native.js.map +1 -1
- package/dist/cjs/knobImmuneExports.cjs +3 -3
- package/dist/cjs/knobImmuneExports.native.js +3 -3
- package/dist/cjs/layouts/PaneScaffold.cjs +70 -42
- package/dist/cjs/layouts/PaneScaffold.native.js +75 -48
- package/dist/cjs/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/cjs/layouts/page.cjs +2 -1
- package/dist/cjs/layouts/page.native.js +2 -1
- package/dist/cjs/layouts/page.native.js.map +1 -1
- package/dist/cjs/layouts/sidebar.cjs +1 -3
- package/dist/cjs/layouts/sidebar.native.js +1 -3
- package/dist/cjs/layouts/sidebar.native.js.map +1 -1
- package/dist/cjs/surfaces.cjs +0 -32
- package/dist/cjs/surfaces.native.js +0 -32
- package/dist/cjs/surfaces.native.js.map +1 -1
- package/dist/cjs/views/List.cjs +1 -3
- package/dist/cjs/views/List.native.js +1 -3
- package/dist/cjs/views/List.native.js.map +1 -1
- package/dist/cjs/views/Matrix.cjs +568 -0
- package/dist/cjs/views/Matrix.native.js +612 -0
- package/dist/cjs/views/Matrix.native.js.map +1 -0
- package/dist/cjs/views/ganttMath.cjs +2 -0
- package/dist/cjs/views/ganttMath.native.js +2 -0
- package/dist/cjs/views/ganttMath.native.js.map +1 -1
- package/dist/cjs/views/index.cjs +16 -0
- package/dist/cjs/views/index.native.js +16 -0
- package/dist/cjs/views/index.native.js.map +1 -1
- package/dist/cjs/views/matrixMath.cjs +157 -0
- package/dist/cjs/views/matrixMath.native.js +229 -0
- package/dist/cjs/views/matrixMath.native.js.map +1 -0
- package/dist/esm/Accordion/index.mjs +3 -7
- package/dist/esm/Accordion/index.mjs.map +1 -1
- package/dist/esm/Accordion/index.native.js +3 -7
- package/dist/esm/Accordion/index.native.js.map +1 -1
- package/dist/esm/AnimateHeight/index.mjs +2 -4
- package/dist/esm/AnimateHeight/index.mjs.map +1 -1
- package/dist/esm/AnimateHeight/index.native.js +2 -4
- package/dist/esm/AnimateHeight/index.native.js.map +1 -1
- package/dist/esm/Avatar/getInitials.native.js.map +1 -1
- package/dist/esm/Avatar/index.native.js.map +1 -1
- package/dist/esm/Badge/index.native.js.map +1 -1
- package/dist/esm/Button.native.js.map +1 -1
- package/dist/esm/Carousel/index.native.js.map +1 -1
- package/dist/esm/Chip/index.mjs +9 -2
- package/dist/esm/Chip/index.mjs.map +1 -1
- package/dist/esm/Chip/index.native.js +9 -2
- package/dist/esm/Chip/index.native.js.map +1 -1
- package/dist/esm/Collapsible.native.js.map +1 -1
- package/dist/esm/ContextMenu/index.native.js.map +1 -1
- package/dist/esm/CopyField/index.native.js.map +1 -1
- package/dist/esm/DotIndicator/index.native.js.map +1 -1
- package/dist/esm/DropdownMenu/index.native.js.map +1 -1
- package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
- package/dist/esm/Heading.native.js.map +1 -1
- package/dist/esm/Meter/index.native.js.map +1 -1
- package/dist/esm/Meter/meter.native.js.map +1 -1
- package/dist/esm/Pagination.native.js.map +1 -1
- package/dist/esm/ProgressSteps/index.native.js.map +1 -1
- package/dist/esm/Quote/index.native.js.map +1 -1
- package/dist/esm/Separator/index.native.js.map +1 -1
- package/dist/esm/SheetModal/detents.native.js.map +1 -1
- package/dist/esm/SheetModal/index.native.js +71 -23
- package/dist/esm/SheetModal/index.native.js.map +1 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/SkipLink/index.native.js.map +1 -1
- package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
- package/dist/esm/SortableList/index.native.js.map +1 -1
- package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
- package/dist/esm/Tabs/index.mjs +3 -7
- package/dist/esm/Tabs/index.mjs.map +1 -1
- package/dist/esm/Tabs/index.native.js +3 -7
- package/dist/esm/Tabs/index.native.js.map +1 -1
- package/dist/esm/Text.mjs +3 -1
- package/dist/esm/Text.mjs.map +1 -1
- package/dist/esm/Text.native.js +3 -1
- package/dist/esm/Text.native.js.map +1 -1
- package/dist/esm/Toast/index.native.js.map +1 -1
- package/dist/esm/Toast/store.native.js.map +1 -1
- package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
- package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
- package/dist/esm/TreeSelect/index.mjs +56 -53
- package/dist/esm/TreeSelect/index.mjs.map +1 -1
- package/dist/esm/TreeSelect/index.native.js +56 -53
- package/dist/esm/TreeSelect/index.native.js.map +1 -1
- package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
- package/dist/esm/Video/PlayerChrome.mjs +4 -10
- package/dist/esm/Video/PlayerChrome.mjs.map +1 -1
- package/dist/esm/Video/PlayerChrome.native.js +4 -10
- package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
- package/dist/esm/Video/VideoFrame.native.js.map +1 -1
- package/dist/esm/Video/types.native.js.map +1 -1
- package/dist/esm/Wheel/index.native.js.map +1 -1
- package/dist/esm/Wizard/index.native.js.map +1 -1
- package/dist/esm/actions/ActionBar.native.js.map +1 -1
- package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
- package/dist/esm/actions/devWarn.native.js.map +1 -1
- package/dist/esm/actions/index.native.js.map +1 -1
- package/dist/esm/actions/severity.native.js.map +1 -1
- package/dist/esm/charts/Axis.native.js.map +1 -1
- package/dist/esm/charts/BarChart.native.js.map +1 -1
- package/dist/esm/charts/ChartDataView.native.js.map +1 -1
- package/dist/esm/charts/ChartSurface.native.js.map +1 -1
- package/dist/esm/charts/LineChart.native.js.map +1 -1
- package/dist/esm/charts/PieChart.native.js.map +1 -1
- package/dist/esm/charts/Sparkline.native.js.map +1 -1
- package/dist/esm/charts/accessibility.native.js.map +1 -1
- package/dist/esm/charts/composeChart.native.js.map +1 -1
- package/dist/esm/charts/index.native.js.map +1 -1
- package/dist/esm/charts/math.native.js.map +1 -1
- package/dist/esm/charts/series.native.js.map +1 -1
- package/dist/esm/charts/svg.native.js.map +1 -1
- package/dist/esm/charts/types.native.js.map +1 -1
- package/dist/esm/charts/useChartTheme.native.js.map +1 -1
- package/dist/esm/code/Code/index.mjs +6 -0
- package/dist/esm/code/Code/index.mjs.map +1 -1
- package/dist/esm/code/Code/index.native.js +10 -7
- package/dist/esm/code/Code/index.native.js.map +1 -1
- package/dist/esm/code/CodeBlock/index.mjs +5 -6
- package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
- package/dist/esm/code/CodeBlock/index.native.js +5 -6
- package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
- package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
- package/dist/esm/code/Highlighter/index.native.js.map +1 -1
- package/dist/esm/code/Pre/index.native.js.map +1 -1
- package/dist/esm/code/index.native.js.map +1 -1
- package/dist/esm/componentColors.native.js.map +1 -1
- package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
- package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
- package/dist/esm/feedback/copy.native.js.map +1 -1
- package/dist/esm/feedback/index.native.js.map +1 -1
- package/dist/esm/feedback/notify.native.js.map +1 -1
- package/dist/esm/feedback/route.native.js.map +1 -1
- package/dist/esm/feedback/store.native.js.map +1 -1
- package/dist/esm/hooks/index.native.js.map +1 -1
- package/dist/esm/hooks/useClipboard.native.js.map +1 -1
- package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
- package/dist/esm/icons/CheckRegular.native.js.map +1 -1
- package/dist/esm/icons/MinusRegular.native.js.map +1 -1
- package/dist/esm/icons/index.native.js.map +1 -1
- package/dist/esm/icons/lucide.native.js.map +1 -1
- package/dist/esm/icons/themed.mjs +3 -2
- package/dist/esm/icons/themed.mjs.map +1 -1
- package/dist/esm/icons/themed.native.js +3 -2
- package/dist/esm/icons/themed.native.js.map +1 -1
- package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
- package/dist/esm/images/Image/shared.native.js.map +1 -1
- package/dist/esm/images/Image/types.native.js.map +1 -1
- package/dist/esm/images/index.native.js.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/knobImmuneExports.mjs +3 -3
- package/dist/esm/knobImmuneExports.native.js +3 -3
- package/dist/esm/knobImmuneExports.native.js.map +1 -1
- package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
- package/dist/esm/layouts/Debug/index.native.js.map +1 -1
- package/dist/esm/layouts/EmptyState.native.js.map +1 -1
- package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
- package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
- package/dist/esm/layouts/PaneScaffold.mjs +70 -42
- package/dist/esm/layouts/PaneScaffold.mjs.map +1 -1
- package/dist/esm/layouts/PaneScaffold.native.js +75 -48
- package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
- package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
- package/dist/esm/layouts/attachments.native.js.map +1 -1
- package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
- package/dist/esm/layouts/index.native.js.map +1 -1
- package/dist/esm/layouts/page.mjs +3 -2
- package/dist/esm/layouts/page.mjs.map +1 -1
- package/dist/esm/layouts/page.native.js +3 -2
- package/dist/esm/layouts/page.native.js.map +1 -1
- package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
- package/dist/esm/layouts/sidebar.mjs +2 -4
- package/dist/esm/layouts/sidebar.mjs.map +1 -1
- package/dist/esm/layouts/sidebar.native.js +2 -4
- package/dist/esm/layouts/sidebar.native.js.map +1 -1
- package/dist/esm/layouts/tags.native.js.map +1 -1
- package/dist/esm/layouts/timeline.native.js.map +1 -1
- package/dist/esm/layouts/toolbar.native.js.map +1 -1
- package/dist/esm/shared/actionScan.native.js.map +1 -1
- package/dist/esm/shared/i18n.native.js.map +1 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/esm/shared/textRidesText.native.js.map +1 -1
- package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/esm/surfaces.mjs +1 -33
- package/dist/esm/surfaces.mjs.map +1 -1
- package/dist/esm/surfaces.native.js +1 -33
- package/dist/esm/surfaces.native.js.map +1 -1
- package/dist/esm/tamagui.native.js.map +1 -1
- package/dist/esm/testSetup.native.js.map +1 -1
- package/dist/esm/utils/setTinted.native.js.map +1 -1
- package/dist/esm/utils/unwrapText.native.js.map +1 -1
- package/dist/esm/views/Calendar.native.js.map +1 -1
- package/dist/esm/views/Dashboard.native.js.map +1 -1
- package/dist/esm/views/Gantt.native.js.map +1 -1
- package/dist/esm/views/ImageGrid.native.js.map +1 -1
- package/dist/esm/views/List.mjs +2 -4
- package/dist/esm/views/List.mjs.map +1 -1
- package/dist/esm/views/List.native.js +2 -4
- package/dist/esm/views/List.native.js.map +1 -1
- package/dist/esm/views/Matrix.mjs +543 -0
- package/dist/esm/views/Matrix.mjs.map +1 -0
- package/dist/esm/views/Matrix.native.js +573 -0
- package/dist/esm/views/Matrix.native.js.map +1 -0
- package/dist/esm/views/ReportBuilder.native.js.map +1 -1
- package/dist/esm/views/TreeView.native.js.map +1 -1
- package/dist/esm/views/calendarMath.native.js.map +1 -1
- package/dist/esm/views/ganttMath.mjs +1 -1
- package/dist/esm/views/ganttMath.mjs.map +1 -1
- package/dist/esm/views/ganttMath.native.js +1 -1
- package/dist/esm/views/ganttMath.native.js.map +1 -1
- package/dist/esm/views/index.mjs +4 -2
- package/dist/esm/views/index.mjs.map +1 -1
- package/dist/esm/views/index.native.js +4 -2
- package/dist/esm/views/index.native.js.map +1 -1
- package/dist/esm/views/kanbanDnd.native.js.map +1 -1
- package/dist/esm/views/matrixMath.mjs +122 -0
- package/dist/esm/views/matrixMath.mjs.map +1 -0
- package/dist/esm/views/matrixMath.native.js +191 -0
- package/dist/esm/views/matrixMath.native.js.map +1 -0
- package/dist/esm/views/treeViewDnd.native.js.map +1 -1
- package/dist/esm/views/treeViewLayout.native.js.map +1 -1
- package/dist/esm/views/treeViewMotion.native.js.map +1 -1
- package/dist/esm/views/treeViewReveal.native.js.map +1 -1
- package/dist/jsx/Accordion/index.mjs +3 -7
- package/dist/jsx/Accordion/index.mjs.map +1 -1
- package/dist/jsx/Accordion/index.native.js +2 -6
- package/dist/jsx/Accordion/index.native.js.map +1 -1
- package/dist/jsx/AnimateHeight/index.mjs +2 -4
- package/dist/jsx/AnimateHeight/index.mjs.map +1 -1
- package/dist/jsx/AnimateHeight/index.native.js +1 -3
- package/dist/jsx/AnimateHeight/index.native.js.map +1 -1
- package/dist/jsx/Chip/index.mjs +9 -2
- package/dist/jsx/Chip/index.mjs.map +1 -1
- package/dist/jsx/Chip/index.native.js +8 -1
- package/dist/jsx/Chip/index.native.js.map +1 -1
- package/dist/jsx/SheetModal/index.native.js +70 -22
- package/dist/jsx/SheetModal/index.native.js.map +1 -1
- package/dist/jsx/Tabs/index.mjs +3 -7
- package/dist/jsx/Tabs/index.mjs.map +1 -1
- package/dist/jsx/Tabs/index.native.js +2 -6
- package/dist/jsx/Tabs/index.native.js.map +1 -1
- package/dist/jsx/Text.mjs +3 -1
- package/dist/jsx/Text.mjs.map +1 -1
- package/dist/jsx/Text.native.js +2 -0
- package/dist/jsx/Text.native.js.map +1 -1
- package/dist/jsx/TreeSelect/index.mjs +56 -53
- package/dist/jsx/TreeSelect/index.mjs.map +1 -1
- package/dist/jsx/TreeSelect/index.native.js +55 -52
- package/dist/jsx/TreeSelect/index.native.js.map +1 -1
- package/dist/jsx/Video/PlayerChrome.mjs +4 -10
- package/dist/jsx/Video/PlayerChrome.mjs.map +1 -1
- package/dist/jsx/Video/PlayerChrome.native.js +3 -9
- package/dist/jsx/Video/PlayerChrome.native.js.map +1 -1
- package/dist/jsx/code/Code/index.mjs +6 -0
- package/dist/jsx/code/Code/index.mjs.map +1 -1
- package/dist/jsx/code/Code/index.native.js +7 -4
- package/dist/jsx/code/Code/index.native.js.map +1 -1
- package/dist/jsx/code/CodeBlock/index.mjs +5 -6
- package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
- package/dist/jsx/code/CodeBlock/index.native.js +4 -5
- package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
- package/dist/jsx/icons/themed.mjs +3 -2
- package/dist/jsx/icons/themed.mjs.map +1 -1
- package/dist/jsx/icons/themed.native.js +2 -1
- package/dist/jsx/icons/themed.native.js.map +1 -1
- package/dist/jsx/knobImmuneExports.mjs +3 -3
- package/dist/jsx/knobImmuneExports.native.js +3 -3
- package/dist/jsx/layouts/PaneScaffold.mjs +70 -42
- package/dist/jsx/layouts/PaneScaffold.mjs.map +1 -1
- package/dist/jsx/layouts/PaneScaffold.native.js +75 -48
- package/dist/jsx/layouts/PaneScaffold.native.js.map +1 -1
- package/dist/jsx/layouts/page.mjs +3 -2
- package/dist/jsx/layouts/page.mjs.map +1 -1
- package/dist/jsx/layouts/page.native.js +2 -1
- package/dist/jsx/layouts/page.native.js.map +1 -1
- package/dist/jsx/layouts/sidebar.mjs +2 -4
- package/dist/jsx/layouts/sidebar.mjs.map +1 -1
- package/dist/jsx/layouts/sidebar.native.js +1 -3
- package/dist/jsx/layouts/sidebar.native.js.map +1 -1
- package/dist/jsx/surfaces.mjs +1 -33
- package/dist/jsx/surfaces.mjs.map +1 -1
- package/dist/jsx/surfaces.native.js +0 -32
- package/dist/jsx/surfaces.native.js.map +1 -1
- package/dist/jsx/views/List.mjs +2 -4
- package/dist/jsx/views/List.mjs.map +1 -1
- package/dist/jsx/views/List.native.js +1 -3
- package/dist/jsx/views/List.native.js.map +1 -1
- package/dist/jsx/views/Matrix.mjs +543 -0
- package/dist/jsx/views/Matrix.mjs.map +1 -0
- package/dist/jsx/views/Matrix.native.js +612 -0
- package/dist/jsx/views/Matrix.native.js.map +1 -0
- package/dist/jsx/views/ganttMath.mjs +1 -1
- package/dist/jsx/views/ganttMath.mjs.map +1 -1
- package/dist/jsx/views/ganttMath.native.js +2 -0
- package/dist/jsx/views/ganttMath.native.js.map +1 -1
- package/dist/jsx/views/index.mjs +4 -2
- package/dist/jsx/views/index.mjs.map +1 -1
- package/dist/jsx/views/index.native.js +16 -0
- package/dist/jsx/views/index.native.js.map +1 -1
- package/dist/jsx/views/matrixMath.mjs +122 -0
- package/dist/jsx/views/matrixMath.mjs.map +1 -0
- package/dist/jsx/views/matrixMath.native.js +229 -0
- package/dist/jsx/views/matrixMath.native.js.map +1 -0
- package/package.json +10 -10
- package/src/Accordion/Accordion.spec.tsx +20 -0
- package/src/Accordion/index.tsx +7 -3
- package/src/AnimateHeight/index.tsx +143 -142
- package/src/Chip/Chip.borderLadder.spec.tsx +54 -0
- package/src/Chip/Chip.pressSlop.spec.tsx +57 -0
- package/src/Chip/index.tsx +15 -1
- package/src/Heading.native.spec.tsx +1 -1
- package/src/Separator/index.tsx +3 -3
- package/src/SheetModal/SheetModal.detentLayout.spec.tsx +173 -0
- package/src/SheetModal/SheetModal.touch.spec.tsx +63 -0
- package/src/SheetModal/index.native.tsx +95 -17
- package/src/Tabs/Tabs.hydration.spec.tsx +123 -0
- package/src/Tabs/index.tsx +3 -2
- package/src/Text.spec.tsx +32 -2
- package/src/Text.tsx +7 -6
- package/src/Toast/Toast.spec.tsx +50 -0
- package/src/Tooltip/Tooltip.spec.tsx +55 -2
- package/src/TreeSelect/TreeSelect.spec.tsx +15 -15
- package/src/TreeSelect/index.tsx +53 -50
- package/src/Video/PlayerChrome.tsx +4 -3
- package/src/Wheel/Wheel.spec.tsx +4 -4
- package/src/adjacency.l1.spec.tsx +175 -0
- package/src/code/Code/code.spec.tsx +2 -19
- package/src/code/Code/index.tsx +0 -6
- package/src/code/CodeBlock/index.tsx +5 -3
- package/src/code/highlightedCode.spec.tsx +2 -2
- package/src/code/hostProps.spec.tsx +55 -0
- package/src/icons/themed.hydration.spec.tsx +60 -0
- package/src/icons/themed.tsx +3 -6
- package/src/knobImmuneExports.ts +3 -3
- package/src/layouts/PaneScaffold.spec.tsx +148 -2
- package/src/layouts/PaneScaffold.stories.tsx +33 -1
- package/src/layouts/PaneScaffold.tsx +101 -48
- package/src/layouts/page.designLaw.spec.tsx +20 -0
- package/src/layouts/page.tsx +2 -0
- package/src/layouts/sidebar.tsx +2 -3
- package/src/overlayElevation.spec.tsx +39 -51
- package/src/surfaces.spec.tsx +13 -10
- package/src/surfaces.tsx +4 -33
- package/src/tamagui.spec.ts +4 -3
- package/src/textAccentSecondary.spec.tsx +100 -2
- package/src/views/List.tsx +2 -1
- package/src/views/Matrix.spec.tsx +339 -0
- package/src/views/Matrix.stories.spec.tsx +68 -0
- package/src/views/Matrix.stories.tsx +153 -0
- package/src/views/Matrix.tsx +738 -0
- package/src/views/ganttMath.ts +4 -2
- package/src/views/index.ts +30 -0
- package/src/views/matrixMath.spec.ts +237 -0
- package/src/views/matrixMath.ts +251 -0
- package/src/views/views.spec.tsx +3 -1
- package/types/Accordion/index.d.ts.map +1 -1
- package/types/AnimateHeight/index.d.ts.map +1 -1
- package/types/Chip/index.d.ts.map +1 -1
- package/types/Separator/index.d.ts +3 -3
- package/types/SheetModal/index.native.d.ts.map +1 -1
- package/types/Tabs/index.d.ts.map +1 -1
- package/types/Text.d.ts.map +1 -1
- package/types/TreeSelect/index.d.ts.map +1 -1
- package/types/Video/PlayerChrome.d.ts.map +1 -1
- package/types/code/Code/index.d.ts.map +1 -1
- package/types/code/CodeBlock/index.d.ts.map +1 -1
- package/types/icons/themed.d.ts.map +1 -1
- package/types/layouts/PaneScaffold.d.ts +14 -1
- package/types/layouts/PaneScaffold.d.ts.map +1 -1
- package/types/layouts/page.d.ts.map +1 -1
- package/types/layouts/sidebar.d.ts.map +1 -1
- package/types/surfaces.d.ts.map +1 -1
- package/types/views/List.d.ts.map +1 -1
- package/types/views/Matrix.d.ts +102 -0
- package/types/views/Matrix.d.ts.map +1 -0
- package/types/views/ganttMath.d.ts +4 -0
- package/types/views/ganttMath.d.ts.map +1 -1
- package/types/views/index.d.ts +3 -1
- package/types/views/index.d.ts.map +1 -1
- package/types/views/matrixMath.d.ts +114 -0
- package/types/views/matrixMath.d.ts.map +1 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { componentColors, sectionHeading } from "../componentColors";
|
|
2
|
+
import { AsyncBoundary } from "../layouts/AsyncBoundary";
|
|
3
|
+
import { Skeleton } from "../Skeleton";
|
|
4
|
+
import { ViewSwitcher } from "../layouts/ViewSwitcher";
|
|
5
|
+
import { withInterp } from "../shared/t";
|
|
6
|
+
import {
|
|
7
|
+
Intent,
|
|
8
|
+
MIN_PRESS_TARGET,
|
|
9
|
+
ensureKeyboardModalityTracking,
|
|
10
|
+
formatNumber,
|
|
11
|
+
hairline,
|
|
12
|
+
hairlineWidth,
|
|
13
|
+
keyboardFocusRingProps,
|
|
14
|
+
useReadableTextOn,
|
|
15
|
+
useResolvedKnobs,
|
|
16
|
+
wasKeyboardFocus,
|
|
17
|
+
} from "@multiplatform.one/theme";
|
|
18
|
+
import React, { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
|
|
19
|
+
import { useTranslation } from "../shared/i18n";
|
|
20
|
+
import { ScrollView as RNScrollView } from "react-native";
|
|
21
|
+
import { ScrollView, Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
|
|
22
|
+
import { formatRangeLabel } from "./ganttMath";
|
|
23
|
+
import {
|
|
24
|
+
alignMatrixRange,
|
|
25
|
+
matrixCellIntensity,
|
|
26
|
+
matrixCellKey,
|
|
27
|
+
computeMatrixRange,
|
|
28
|
+
generateBuckets,
|
|
29
|
+
matrixMaxValue,
|
|
30
|
+
summarizeMatrixCells,
|
|
31
|
+
type MatrixBucket,
|
|
32
|
+
type MatrixBucketRule,
|
|
33
|
+
type MatrixCell,
|
|
34
|
+
type MatrixCellSummary,
|
|
35
|
+
type MatrixIntent,
|
|
36
|
+
type MatrixRange,
|
|
37
|
+
} from "./matrixMath";
|
|
38
|
+
|
|
39
|
+
// Same split as the Gantt: the grid canvas needs real scroll events to drive
|
|
40
|
+
// the pinned axis follower, and Tamagui's styled ScrollView does not deliver
|
|
41
|
+
// onScroll on native.
|
|
42
|
+
const CanvasScroller = (isWeb ? ScrollView : RNScrollView) as typeof ScrollView;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Inline-END hairline rule for the label column edge (SB-M-1215 pattern
|
|
46
|
+
* shared with Calendar/Gantt: tamagui's RN `borderEndWidth` does not compile
|
|
47
|
+
* to working CSS on web).
|
|
48
|
+
*/
|
|
49
|
+
const hairlineEnd = isWeb
|
|
50
|
+
? {
|
|
51
|
+
borderInlineEndWidth: hairlineWidth,
|
|
52
|
+
borderInlineEndStyle: "solid" as any,
|
|
53
|
+
className: "mp-hairline-ie",
|
|
54
|
+
}
|
|
55
|
+
: { borderEndWidth: hairlineWidth };
|
|
56
|
+
|
|
57
|
+
/** Lowest paint strength a present cell takes, so a zero stays visible. */
|
|
58
|
+
const FILL_FLOOR = 0.14;
|
|
59
|
+
/** Intensity from which the cell text switches to on-fill ink. */
|
|
60
|
+
const ON_FILL_INK_FROM = 0.55;
|
|
61
|
+
const LEGEND_STEPS = [0, 0.25, 0.5, 0.75, 1] as const;
|
|
62
|
+
|
|
63
|
+
/** One subject row of the matrix. */
|
|
64
|
+
export interface MatrixRow {
|
|
65
|
+
/** Unique row identifier; `MatrixCell.rowId` points at it. */
|
|
66
|
+
id: string;
|
|
67
|
+
/** Row header text and the subject half of every cell's accessible name. */
|
|
68
|
+
label: string;
|
|
69
|
+
/** Any additional data */
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Which intersection a press was about. `summary` is undefined for an empty bucket. */
|
|
74
|
+
export interface MatrixCellRef {
|
|
75
|
+
row: MatrixRow;
|
|
76
|
+
bucket: MatrixBucket;
|
|
77
|
+
summary: MatrixCellSummary | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A legend entry beyond the built-in ramp: a solid swatch with a label. */
|
|
81
|
+
export interface MatrixLegendItem {
|
|
82
|
+
label: string;
|
|
83
|
+
intent?: MatrixIntent;
|
|
84
|
+
color?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MatrixViewProps extends Omit<YStackProps, "children"> {
|
|
88
|
+
/** Subjects, one row each, in display order. */
|
|
89
|
+
rows: MatrixRow[];
|
|
90
|
+
/** Data points; the view folds them into (row, bucket) intersections. */
|
|
91
|
+
cells: MatrixCell[];
|
|
92
|
+
/** Initial bucket rule the column axis is generated from (default: 'month'). */
|
|
93
|
+
rule?: MatrixBucketRule;
|
|
94
|
+
/** Called when the rule changes via the switcher. */
|
|
95
|
+
onRuleChange?: (rule: MatrixBucketRule) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Explicit window, snapped to bucket boundaries. Omitted, the window is
|
|
98
|
+
* exactly the buckets the data spans (no padding).
|
|
99
|
+
*/
|
|
100
|
+
range?: MatrixRange;
|
|
101
|
+
/** First day of week for week alignment: 0 = Sunday, 1 = Monday (default: 0) */
|
|
102
|
+
firstDayOfWeek?: 0 | 1;
|
|
103
|
+
/** Called when a cell is pressed (pointer, or Enter/Space on a focused cell). Empty buckets press too. */
|
|
104
|
+
onCellPress?: (ref: MatrixCellRef) => void;
|
|
105
|
+
/** Visible cell text (default: the summed value, else the point count). */
|
|
106
|
+
formatCell?: (summary: MatrixCellSummary) => string;
|
|
107
|
+
/** Whether data is loading (skeleton shows only while there are no rows) */
|
|
108
|
+
isLoading?: boolean;
|
|
109
|
+
/** Failed load (DG-ST-01 / W9). Wins over the empty state. */
|
|
110
|
+
error?: boolean | string | Error | ReactNode | null;
|
|
111
|
+
/** Retry handler for the default error UI. */
|
|
112
|
+
onRetry?: () => void;
|
|
113
|
+
/** Title for the default empty state */
|
|
114
|
+
emptyTitle?: string;
|
|
115
|
+
/** Description for the default empty state */
|
|
116
|
+
emptyDescription?: string;
|
|
117
|
+
/** Height of the grid in pixels (default steps with the size knob) */
|
|
118
|
+
height?: number;
|
|
119
|
+
/** Width of the subject column in pixels (default: 180) */
|
|
120
|
+
labelWidth?: number;
|
|
121
|
+
/** Width of one bucket column in pixels (default steps with the size knob; never below the press target) */
|
|
122
|
+
columnWidth?: number;
|
|
123
|
+
/** Header text of the subject column (default: "Subject"). */
|
|
124
|
+
subjectLabel?: string;
|
|
125
|
+
/** Show the day/week/month rule switcher (default: true) */
|
|
126
|
+
showRuleSwitcher?: boolean;
|
|
127
|
+
/** Show the legend strip under the grid (default: true) */
|
|
128
|
+
showLegend?: boolean;
|
|
129
|
+
/** Extra legend entries after the built-in empty + ramp swatches. */
|
|
130
|
+
legendItems?: MatrixLegendItem[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Fill resolution ───────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Semantic intents ride their hue sub-theme (`<Intent name>` → `$color9`,
|
|
137
|
+
* LC-05); the default identity stays on `$accentBackground`. Explicit
|
|
138
|
+
* colors pass through untouched (Axiom 11 / LC-27).
|
|
139
|
+
*/
|
|
140
|
+
function CellIntentWrap({ intent, children }: { intent?: MatrixIntent; children: ReactNode }) {
|
|
141
|
+
if (!intent || intent === "accent") return <>{children}</>;
|
|
142
|
+
return <Intent name={intent}>{children}</Intent>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveFill(color: string | undefined, intent: MatrixIntent | undefined): string {
|
|
146
|
+
return color ?? (intent && intent !== "accent" ? "$color9" : "$accentBackground");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function fillOpacity(intensity: number): number {
|
|
150
|
+
return FILL_FLOOR + (1 - FILL_FLOOR) * intensity;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Cell ──────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
interface MatrixCellBoxProps {
|
|
156
|
+
row: MatrixRow;
|
|
157
|
+
bucket: MatrixBucket;
|
|
158
|
+
summary: MatrixCellSummary | undefined;
|
|
159
|
+
intensity: number;
|
|
160
|
+
fill: string;
|
|
161
|
+
text: string;
|
|
162
|
+
name: string;
|
|
163
|
+
width: number;
|
|
164
|
+
height: number;
|
|
165
|
+
lastColumn: boolean;
|
|
166
|
+
onPress?: () => void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function MatrixCellBox({
|
|
170
|
+
row,
|
|
171
|
+
bucket,
|
|
172
|
+
summary,
|
|
173
|
+
intensity,
|
|
174
|
+
fill,
|
|
175
|
+
text,
|
|
176
|
+
name,
|
|
177
|
+
width,
|
|
178
|
+
height,
|
|
179
|
+
lastColumn,
|
|
180
|
+
onPress,
|
|
181
|
+
}: MatrixCellBoxProps) {
|
|
182
|
+
const { knobProps } = useResolvedKnobs();
|
|
183
|
+
const readable = useReadableTextOn(fill);
|
|
184
|
+
const [keyboardRing, setKeyboardRing] = useState(false);
|
|
185
|
+
const empty = summary === undefined;
|
|
186
|
+
const state = summary === undefined ? "empty" : summary.value === 0 ? "zero" : "value";
|
|
187
|
+
const ink = !empty && intensity >= ON_FILL_INK_FROM && readable ? readable : "$color";
|
|
188
|
+
const pressable = onPress !== undefined;
|
|
189
|
+
return (
|
|
190
|
+
<YStack
|
|
191
|
+
width={width}
|
|
192
|
+
height={height}
|
|
193
|
+
alignItems="center"
|
|
194
|
+
justifyContent="center"
|
|
195
|
+
position="relative"
|
|
196
|
+
overflow="hidden"
|
|
197
|
+
{...(lastColumn ? null : hairlineEnd)}
|
|
198
|
+
borderColor={componentColors.divider}
|
|
199
|
+
cursor={pressable ? "pointer" : undefined}
|
|
200
|
+
hoverStyle={pressable ? { backgroundColor: componentColors.interactive.hover } : undefined}
|
|
201
|
+
pressStyle={pressable ? { opacity: 0.8 } : undefined}
|
|
202
|
+
role={pressable ? "button" : undefined}
|
|
203
|
+
tabIndex={pressable ? 0 : undefined}
|
|
204
|
+
aria-label={name}
|
|
205
|
+
// LC-71: one ring on the cell (the perceived control), keyboard-only.
|
|
206
|
+
{...(keyboardRing ? keyboardFocusRingProps : { outlineWidth: 0 })}
|
|
207
|
+
onFocus={pressable ? () => setKeyboardRing(wasKeyboardFocus()) : undefined}
|
|
208
|
+
onBlur={pressable ? () => setKeyboardRing(false) : undefined}
|
|
209
|
+
onPress={onPress}
|
|
210
|
+
onKeyDown={
|
|
211
|
+
pressable
|
|
212
|
+
? (((ke: any) => {
|
|
213
|
+
if (ke.key !== "Enter" && ke.key !== " ") return;
|
|
214
|
+
ke.preventDefault?.();
|
|
215
|
+
onPress();
|
|
216
|
+
}) as any)
|
|
217
|
+
: undefined
|
|
218
|
+
}
|
|
219
|
+
{...{
|
|
220
|
+
"data-matrix-cell": `${row.id}:${bucket.key}`,
|
|
221
|
+
"data-matrix-cell-state": state,
|
|
222
|
+
...(summary?.intent ? { "data-matrix-cell-intent": summary.intent } : null),
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
{!empty && (
|
|
226
|
+
<YStack
|
|
227
|
+
position="absolute"
|
|
228
|
+
top={0}
|
|
229
|
+
left={0}
|
|
230
|
+
right={0}
|
|
231
|
+
bottom={0}
|
|
232
|
+
backgroundColor={fill as any}
|
|
233
|
+
opacity={fillOpacity(intensity)}
|
|
234
|
+
pointerEvents="none"
|
|
235
|
+
{...{ "data-matrix-fill": String(intensity) }}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
<Text
|
|
239
|
+
{...knobProps.label}
|
|
240
|
+
color={empty ? componentColors.text.subtle : ink}
|
|
241
|
+
numberOfLines={1}
|
|
242
|
+
pointerEvents="none"
|
|
243
|
+
zIndex={1}
|
|
244
|
+
aria-hidden
|
|
245
|
+
>
|
|
246
|
+
{empty ? "·" : text}
|
|
247
|
+
</Text>
|
|
248
|
+
</YStack>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Legend ────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
function LegendSwatch({
|
|
255
|
+
intensity,
|
|
256
|
+
fill,
|
|
257
|
+
empty,
|
|
258
|
+
}: {
|
|
259
|
+
intensity?: number;
|
|
260
|
+
fill?: string;
|
|
261
|
+
empty?: boolean;
|
|
262
|
+
}) {
|
|
263
|
+
const { knobProps } = useResolvedKnobs();
|
|
264
|
+
return (
|
|
265
|
+
<YStack
|
|
266
|
+
width={14}
|
|
267
|
+
height={14}
|
|
268
|
+
{...knobProps.borderRadius}
|
|
269
|
+
position="relative"
|
|
270
|
+
overflow="hidden"
|
|
271
|
+
alignItems="center"
|
|
272
|
+
justifyContent="center"
|
|
273
|
+
borderWidth={hairlineWidth}
|
|
274
|
+
borderColor={componentColors.surface.border}
|
|
275
|
+
{...{ "data-matrix-legend-swatch": empty ? "empty" : String(intensity ?? 1) }}
|
|
276
|
+
>
|
|
277
|
+
{empty ? (
|
|
278
|
+
<Text fontSize={10} lineHeight={10} color={componentColors.text.subtle} aria-hidden>
|
|
279
|
+
·
|
|
280
|
+
</Text>
|
|
281
|
+
) : (
|
|
282
|
+
<YStack
|
|
283
|
+
position="absolute"
|
|
284
|
+
top={0}
|
|
285
|
+
left={0}
|
|
286
|
+
right={0}
|
|
287
|
+
bottom={0}
|
|
288
|
+
backgroundColor={(fill ?? "$accentBackground") as any}
|
|
289
|
+
opacity={fillOpacity(intensity ?? 1)}
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
292
|
+
</YStack>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Loading skeleton (LC-20: mirrors the real anatomy) ────────
|
|
297
|
+
|
|
298
|
+
const SKELETON_ROWS = [96, 72, 112, 64, 88] as const;
|
|
299
|
+
|
|
300
|
+
function MatrixSkeleton({
|
|
301
|
+
labelWidth,
|
|
302
|
+
columnWidth,
|
|
303
|
+
rowHeight,
|
|
304
|
+
}: {
|
|
305
|
+
labelWidth: number;
|
|
306
|
+
columnWidth: number;
|
|
307
|
+
rowHeight: number;
|
|
308
|
+
}) {
|
|
309
|
+
const { t } = useTranslation();
|
|
310
|
+
const { knobProps } = useResolvedKnobs();
|
|
311
|
+
const edgePad = knobProps.panelPadding.padding;
|
|
312
|
+
const columns = [0, 1, 2, 3, 4, 5];
|
|
313
|
+
return (
|
|
314
|
+
<YStack
|
|
315
|
+
width="100%"
|
|
316
|
+
aria-busy
|
|
317
|
+
aria-label={t("Loading")}
|
|
318
|
+
{...{ "data-matrix-skeleton": "true", "data-async-skeleton": "matrix" }}
|
|
319
|
+
>
|
|
320
|
+
<XStack
|
|
321
|
+
height={rowHeight}
|
|
322
|
+
alignItems="center"
|
|
323
|
+
{...hairline.bottom}
|
|
324
|
+
borderColor="$borderColor"
|
|
325
|
+
>
|
|
326
|
+
<XStack width={labelWidth} paddingHorizontal={edgePad}>
|
|
327
|
+
<Skeleton width={64} height={12} />
|
|
328
|
+
</XStack>
|
|
329
|
+
{columns.map((column) => (
|
|
330
|
+
<XStack key={column} width={columnWidth} justifyContent="center">
|
|
331
|
+
<Skeleton width={Math.max(24, columnWidth - 24)} height={12} />
|
|
332
|
+
</XStack>
|
|
333
|
+
))}
|
|
334
|
+
</XStack>
|
|
335
|
+
{SKELETON_ROWS.map((labelSkeletonWidth, index) => (
|
|
336
|
+
<XStack
|
|
337
|
+
key={index}
|
|
338
|
+
height={rowHeight}
|
|
339
|
+
alignItems="center"
|
|
340
|
+
{...(index < SKELETON_ROWS.length - 1 ? hairline.bottom : null)}
|
|
341
|
+
borderColor={componentColors.divider}
|
|
342
|
+
>
|
|
343
|
+
<XStack width={labelWidth} paddingHorizontal={edgePad}>
|
|
344
|
+
<Skeleton width={labelSkeletonWidth} height={12} />
|
|
345
|
+
</XStack>
|
|
346
|
+
{columns.map((column) => (
|
|
347
|
+
<XStack key={column} width={columnWidth} justifyContent="center">
|
|
348
|
+
{(column + index) % 3 === 0 ? null : (
|
|
349
|
+
<Skeleton
|
|
350
|
+
variant="rounded"
|
|
351
|
+
width={Math.max(16, columnWidth - 16)}
|
|
352
|
+
height={Math.max(10, rowHeight - 20)}
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
</XStack>
|
|
356
|
+
))}
|
|
357
|
+
</XStack>
|
|
358
|
+
))}
|
|
359
|
+
</YStack>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── MatrixView ────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Generic matrix view: one row per subject, one column per time bucket, one
|
|
367
|
+
* cell per intersection carrying a value and a paint strength rather than a
|
|
368
|
+
* record. Account by month, person by week, service by day.
|
|
369
|
+
*
|
|
370
|
+
* The column axis is GENERATED from a bucket rule (day / week / month) over
|
|
371
|
+
* the data's range, using the Gantt's own tick placement and day math
|
|
372
|
+
* (`ganttMath`), so it is a real axis and not a hand-declared column set.
|
|
373
|
+
* A cell paints its fill at an intensity: explicit, or the summed value
|
|
374
|
+
* scaled against the grid's max, or full strength for bare presence. A real
|
|
375
|
+
* zero paints at the floor of the ramp; a bucket nothing landed in paints
|
|
376
|
+
* nothing and shows a dot, so an absence never reads as a zero. Every cell
|
|
377
|
+
* has a full accessible name and, with `onCellPress`, is a button.
|
|
378
|
+
*
|
|
379
|
+
* The header row stays pinned while rows scroll vertically (labels and rows
|
|
380
|
+
* share one vertical scroller so alignment is structural); the subject
|
|
381
|
+
* column stays pinned while the grid scrolls horizontally, the header strip
|
|
382
|
+
* being a scroll-follower of the grid scroller, which works on web and
|
|
383
|
+
* native alike (the Gantt mechanism). Loading / empty / error are honest
|
|
384
|
+
* first-class states (Axiom 6) via AsyncBoundary.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```tsx
|
|
388
|
+
* <MatrixView
|
|
389
|
+
* rows={accounts.map((a) => ({ id: a.id, label: a.name }))}
|
|
390
|
+
* cells={statements.map((s) => ({ rowId: s.account, date: s.date, value: s.count }))}
|
|
391
|
+
* rule="month"
|
|
392
|
+
* onCellPress={({ row, bucket }) => openStatements(row.id, bucket.start)}
|
|
393
|
+
* />
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
export function MatrixView({
|
|
397
|
+
rows,
|
|
398
|
+
cells,
|
|
399
|
+
rule: initialRule = "month",
|
|
400
|
+
onRuleChange,
|
|
401
|
+
range: rangeProp,
|
|
402
|
+
firstDayOfWeek = 0,
|
|
403
|
+
onCellPress,
|
|
404
|
+
formatCell,
|
|
405
|
+
isLoading = false,
|
|
406
|
+
error = null,
|
|
407
|
+
onRetry,
|
|
408
|
+
emptyTitle,
|
|
409
|
+
emptyDescription,
|
|
410
|
+
height: heightProp,
|
|
411
|
+
labelWidth = 180,
|
|
412
|
+
columnWidth: columnWidthProp,
|
|
413
|
+
subjectLabel,
|
|
414
|
+
showRuleSwitcher = true,
|
|
415
|
+
showLegend = true,
|
|
416
|
+
legendItems,
|
|
417
|
+
...stackProps
|
|
418
|
+
}: MatrixViewProps) {
|
|
419
|
+
const { t } = useTranslation();
|
|
420
|
+
const { knobProps } = useResolvedKnobs();
|
|
421
|
+
const [rule, setRule] = useState<MatrixBucketRule>(initialRule);
|
|
422
|
+
if (typeof document !== "undefined") ensureKeyboardModalityTracking();
|
|
423
|
+
|
|
424
|
+
// ── Knob-derived geometry (LC-18: size/space step the density) ──────────
|
|
425
|
+
const sz = knobProps.sizeToken as string;
|
|
426
|
+
const height = heightProp ?? (sz === "$3" ? 400 : sz === "$5" ? 560 : 480);
|
|
427
|
+
// Cell paint is the press target (LC-54): never below 44px on either axis.
|
|
428
|
+
const rowHeight = Math.max(MIN_PRESS_TARGET, sz === "$3" ? 44 : sz === "$5" ? 56 : 48);
|
|
429
|
+
const columnWidth = Math.max(
|
|
430
|
+
MIN_PRESS_TARGET,
|
|
431
|
+
columnWidthProp ?? (sz === "$3" ? 56 : sz === "$5" ? 72 : 64),
|
|
432
|
+
);
|
|
433
|
+
const axisHeight = sz === "$3" ? 40 : sz === "$5" ? 52 : 46;
|
|
434
|
+
|
|
435
|
+
// ── Axis + cells ─────────────────────────────────────────────────────────
|
|
436
|
+
const range = useMemo(
|
|
437
|
+
() =>
|
|
438
|
+
rangeProp
|
|
439
|
+
? alignMatrixRange(rangeProp, rule, firstDayOfWeek)
|
|
440
|
+
: computeMatrixRange(
|
|
441
|
+
cells.map((cell) => cell.date),
|
|
442
|
+
rule,
|
|
443
|
+
firstDayOfWeek,
|
|
444
|
+
),
|
|
445
|
+
[rangeProp, cells, rule, firstDayOfWeek],
|
|
446
|
+
);
|
|
447
|
+
const buckets = useMemo(
|
|
448
|
+
() => generateBuckets(range, rule, firstDayOfWeek),
|
|
449
|
+
[range, rule, firstDayOfWeek],
|
|
450
|
+
);
|
|
451
|
+
const summaries = useMemo(
|
|
452
|
+
() => summarizeMatrixCells(cells, rule, firstDayOfWeek),
|
|
453
|
+
[cells, rule, firstDayOfWeek],
|
|
454
|
+
);
|
|
455
|
+
const maxValue = useMemo(() => {
|
|
456
|
+
const rowIds = new Set(rows.map((row) => row.id));
|
|
457
|
+
const bucketKeys = new Set(buckets.map((bucket) => bucket.key));
|
|
458
|
+
return matrixMaxValue(
|
|
459
|
+
Array.from(summaries.values()).filter(
|
|
460
|
+
(summary) => rowIds.has(summary.rowId) && bucketKeys.has(summary.bucketKey),
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
}, [summaries, rows, buckets]);
|
|
464
|
+
const gridWidth = buckets.length * columnWidth;
|
|
465
|
+
const rangeLabel = useMemo(() => formatRangeLabel(range), [range]);
|
|
466
|
+
|
|
467
|
+
const changeRule = useCallback(
|
|
468
|
+
(next: MatrixBucketRule) => {
|
|
469
|
+
setRule(next);
|
|
470
|
+
onRuleChange?.(next);
|
|
471
|
+
},
|
|
472
|
+
[onRuleChange],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const cellText = useCallback(
|
|
476
|
+
(summary: MatrixCellSummary): string => {
|
|
477
|
+
if (formatCell) return formatCell(summary);
|
|
478
|
+
if (summary.label !== undefined) return summary.label;
|
|
479
|
+
return formatNumber(summary.value ?? summary.count);
|
|
480
|
+
},
|
|
481
|
+
[formatCell],
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// ── Pinned-axis sync (the Gantt follower) ────────────────────────────────
|
|
485
|
+
const axisScrollRef = useRef<any>(null);
|
|
486
|
+
const handleCanvasScroll = useCallback((event: any) => {
|
|
487
|
+
const x = event?.nativeEvent?.contentOffset?.x ?? 0;
|
|
488
|
+
axisScrollRef.current?.scrollTo?.({ x, animated: false });
|
|
489
|
+
}, []);
|
|
490
|
+
|
|
491
|
+
// Manual mirroring is not implemented here; force the scrollers LTR so the
|
|
492
|
+
// follower offsets stay 0-at-content-start on web.
|
|
493
|
+
const scrollerDirection = { direction: "ltr" as const };
|
|
494
|
+
const showSkeleton = !error && isLoading && rows.length === 0;
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<AsyncBoundary
|
|
498
|
+
loading={
|
|
499
|
+
showSkeleton ? (
|
|
500
|
+
<MatrixSkeleton labelWidth={labelWidth} columnWidth={columnWidth} rowHeight={rowHeight} />
|
|
501
|
+
) : (
|
|
502
|
+
false
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
empty={!error && !isLoading && rows.length === 0}
|
|
506
|
+
error={error}
|
|
507
|
+
onRetry={onRetry}
|
|
508
|
+
emptyTitle={emptyTitle ?? t("No subjects")}
|
|
509
|
+
emptyDescription={emptyDescription}
|
|
510
|
+
{...stackProps}
|
|
511
|
+
>
|
|
512
|
+
<YStack
|
|
513
|
+
height={height}
|
|
514
|
+
overflow="hidden"
|
|
515
|
+
backgroundColor="$background"
|
|
516
|
+
elevation={knobProps.elevation}
|
|
517
|
+
{...knobProps.containerRadius}
|
|
518
|
+
{...{
|
|
519
|
+
"data-matrix-view": rule,
|
|
520
|
+
"data-size": knobProps.size,
|
|
521
|
+
"data-density": knobProps.density,
|
|
522
|
+
}}
|
|
523
|
+
>
|
|
524
|
+
{/* Header */}
|
|
525
|
+
<XStack
|
|
526
|
+
{...knobProps.panelPadding}
|
|
527
|
+
alignItems="center"
|
|
528
|
+
justifyContent="space-between"
|
|
529
|
+
{...knobProps.gap}
|
|
530
|
+
{...hairline.bottom}
|
|
531
|
+
borderColor="$borderColor"
|
|
532
|
+
>
|
|
533
|
+
<Text {...knobProps.heading} {...sectionHeading} numberOfLines={1}>
|
|
534
|
+
{rangeLabel}
|
|
535
|
+
</Text>
|
|
536
|
+
{showRuleSwitcher && (
|
|
537
|
+
<ViewSwitcher
|
|
538
|
+
views={[
|
|
539
|
+
{ type: "day", label: t("Day") },
|
|
540
|
+
{ type: "week", label: t("Week") },
|
|
541
|
+
{ type: "month", label: t("Month") },
|
|
542
|
+
]}
|
|
543
|
+
currentView={rule}
|
|
544
|
+
onViewChange={(val) => changeRule(val as MatrixBucketRule)}
|
|
545
|
+
/>
|
|
546
|
+
)}
|
|
547
|
+
</XStack>
|
|
548
|
+
|
|
549
|
+
{/* Pinned axis row: the subject header cell + the bucket header strip
|
|
550
|
+
stay put while rows scroll vertically. The strip follows the grid
|
|
551
|
+
scroller, so headers and cells can never drift apart. */}
|
|
552
|
+
<XStack
|
|
553
|
+
{...hairline.bottom}
|
|
554
|
+
borderColor="$borderColor"
|
|
555
|
+
{...{ "data-matrix-axis-row": "true" }}
|
|
556
|
+
>
|
|
557
|
+
<XStack
|
|
558
|
+
width={labelWidth}
|
|
559
|
+
flexShrink={0}
|
|
560
|
+
height={axisHeight}
|
|
561
|
+
alignItems="center"
|
|
562
|
+
paddingHorizontal={knobProps.panelPadding.padding}
|
|
563
|
+
{...hairlineEnd}
|
|
564
|
+
borderColor="$borderColor"
|
|
565
|
+
{...{ backgroundColor: componentColors.surface.background }}
|
|
566
|
+
>
|
|
567
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor} numberOfLines={1}>
|
|
568
|
+
{subjectLabel ?? t("Subject")}
|
|
569
|
+
</Text>
|
|
570
|
+
</XStack>
|
|
571
|
+
<ScrollView
|
|
572
|
+
ref={axisScrollRef}
|
|
573
|
+
horizontal
|
|
574
|
+
flex={1}
|
|
575
|
+
scrollEnabled={false}
|
|
576
|
+
showsHorizontalScrollIndicator={false}
|
|
577
|
+
style={scrollerDirection as any}
|
|
578
|
+
>
|
|
579
|
+
<XStack
|
|
580
|
+
width={gridWidth}
|
|
581
|
+
height={axisHeight}
|
|
582
|
+
{...{
|
|
583
|
+
backgroundColor: componentColors.surface.background,
|
|
584
|
+
"data-matrix-axis": rule,
|
|
585
|
+
}}
|
|
586
|
+
>
|
|
587
|
+
{buckets.map((bucket, index) => (
|
|
588
|
+
<XStack
|
|
589
|
+
key={bucket.key}
|
|
590
|
+
width={columnWidth}
|
|
591
|
+
height={axisHeight}
|
|
592
|
+
alignItems="center"
|
|
593
|
+
justifyContent="center"
|
|
594
|
+
{...(index === buckets.length - 1 ? null : hairlineEnd)}
|
|
595
|
+
borderColor={componentColors.divider}
|
|
596
|
+
aria-label={bucket.name}
|
|
597
|
+
{...{ "data-matrix-column": bucket.key }}
|
|
598
|
+
>
|
|
599
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor} numberOfLines={1}>
|
|
600
|
+
{bucket.label}
|
|
601
|
+
</Text>
|
|
602
|
+
</XStack>
|
|
603
|
+
))}
|
|
604
|
+
</XStack>
|
|
605
|
+
</ScrollView>
|
|
606
|
+
</XStack>
|
|
607
|
+
|
|
608
|
+
{/* Body: subject labels + grid share one vertical scroller so rows stay
|
|
609
|
+
aligned; the grid owns its horizontal scroller (LC-10). */}
|
|
610
|
+
<ScrollView flex={1}>
|
|
611
|
+
<XStack {...{ "data-matrix-scroll-body": "true" }}>
|
|
612
|
+
<YStack
|
|
613
|
+
width={labelWidth}
|
|
614
|
+
flexShrink={0}
|
|
615
|
+
{...hairlineEnd}
|
|
616
|
+
borderColor="$borderColor"
|
|
617
|
+
{...{ "data-matrix-labels": "true" }}
|
|
618
|
+
>
|
|
619
|
+
{rows.map((row, index) => (
|
|
620
|
+
<XStack
|
|
621
|
+
key={row.id}
|
|
622
|
+
height={rowHeight}
|
|
623
|
+
alignItems="center"
|
|
624
|
+
paddingHorizontal={knobProps.panelPadding.padding}
|
|
625
|
+
{...(index === rows.length - 1 ? null : hairline.bottom)}
|
|
626
|
+
borderColor={componentColors.divider}
|
|
627
|
+
{...{ "data-matrix-row-label": row.id }}
|
|
628
|
+
>
|
|
629
|
+
<Text {...knobProps.body} numberOfLines={1} color="$color">
|
|
630
|
+
{row.label}
|
|
631
|
+
</Text>
|
|
632
|
+
</XStack>
|
|
633
|
+
))}
|
|
634
|
+
</YStack>
|
|
635
|
+
|
|
636
|
+
<CanvasScroller
|
|
637
|
+
horizontal
|
|
638
|
+
{...(isWeb ? { flex: 1 } : null)}
|
|
639
|
+
showsHorizontalScrollIndicator
|
|
640
|
+
onScroll={handleCanvasScroll}
|
|
641
|
+
scrollEventThrottle={16}
|
|
642
|
+
style={(isWeb ? scrollerDirection : [{ flex: 1 }, scrollerDirection]) as any}
|
|
643
|
+
>
|
|
644
|
+
<YStack width={gridWidth} {...{ "data-matrix-canvas": "true" }}>
|
|
645
|
+
{rows.map((row, rowIndex) => (
|
|
646
|
+
<XStack
|
|
647
|
+
key={row.id}
|
|
648
|
+
height={rowHeight}
|
|
649
|
+
{...(rowIndex === rows.length - 1 ? null : hairline.bottom)}
|
|
650
|
+
borderColor={componentColors.divider}
|
|
651
|
+
{...{ "data-matrix-row": row.id }}
|
|
652
|
+
>
|
|
653
|
+
{buckets.map((bucket, columnIndex) => {
|
|
654
|
+
const summary = summaries.get(matrixCellKey(row.id, bucket.key));
|
|
655
|
+
const intensity = summary ? matrixCellIntensity(summary, maxValue) : 0;
|
|
656
|
+
const fill = resolveFill(summary?.color, summary?.intent);
|
|
657
|
+
const text = summary ? cellText(summary) : t("No data");
|
|
658
|
+
const name = withInterp(t("{{subject}}, {{bucket}}: {{value}}"), {
|
|
659
|
+
subject: row.label,
|
|
660
|
+
bucket: bucket.name,
|
|
661
|
+
value: text,
|
|
662
|
+
});
|
|
663
|
+
return (
|
|
664
|
+
<CellIntentWrap
|
|
665
|
+
key={bucket.key}
|
|
666
|
+
intent={summary?.color ? undefined : summary?.intent}
|
|
667
|
+
>
|
|
668
|
+
<MatrixCellBox
|
|
669
|
+
row={row}
|
|
670
|
+
bucket={bucket}
|
|
671
|
+
summary={summary}
|
|
672
|
+
intensity={intensity}
|
|
673
|
+
fill={fill}
|
|
674
|
+
text={text}
|
|
675
|
+
name={name}
|
|
676
|
+
width={columnWidth}
|
|
677
|
+
height={rowHeight}
|
|
678
|
+
lastColumn={columnIndex === buckets.length - 1}
|
|
679
|
+
onPress={
|
|
680
|
+
onCellPress ? () => onCellPress({ row, bucket, summary }) : undefined
|
|
681
|
+
}
|
|
682
|
+
/>
|
|
683
|
+
</CellIntentWrap>
|
|
684
|
+
);
|
|
685
|
+
})}
|
|
686
|
+
</XStack>
|
|
687
|
+
))}
|
|
688
|
+
</YStack>
|
|
689
|
+
</CanvasScroller>
|
|
690
|
+
</XStack>
|
|
691
|
+
</ScrollView>
|
|
692
|
+
|
|
693
|
+
{showLegend && (
|
|
694
|
+
<XStack
|
|
695
|
+
{...knobProps.panelPadding}
|
|
696
|
+
alignItems="center"
|
|
697
|
+
flexWrap="wrap"
|
|
698
|
+
{...knobProps.gap}
|
|
699
|
+
{...hairline.top}
|
|
700
|
+
borderColor="$borderColor"
|
|
701
|
+
{...{ "data-matrix-legend": "true" }}
|
|
702
|
+
>
|
|
703
|
+
<XStack alignItems="center" gap="$1.5">
|
|
704
|
+
<LegendSwatch empty />
|
|
705
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor}>
|
|
706
|
+
{t("No data")}
|
|
707
|
+
</Text>
|
|
708
|
+
</XStack>
|
|
709
|
+
<XStack alignItems="center" gap="$1.5">
|
|
710
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor}>
|
|
711
|
+
{t("Less")}
|
|
712
|
+
</Text>
|
|
713
|
+
{LEGEND_STEPS.map((step) => (
|
|
714
|
+
<LegendSwatch key={step} intensity={step} />
|
|
715
|
+
))}
|
|
716
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor}>
|
|
717
|
+
{t("More")}
|
|
718
|
+
</Text>
|
|
719
|
+
</XStack>
|
|
720
|
+
{legendItems?.map((item, index) => (
|
|
721
|
+
<CellIntentWrap
|
|
722
|
+
key={`${item.label}-${index}`}
|
|
723
|
+
intent={item.color ? undefined : item.intent}
|
|
724
|
+
>
|
|
725
|
+
<XStack alignItems="center" gap="$1.5">
|
|
726
|
+
<LegendSwatch fill={resolveFill(item.color, item.intent)} />
|
|
727
|
+
<Text {...knobProps.label} color={knobProps.textAccentColor}>
|
|
728
|
+
{item.label}
|
|
729
|
+
</Text>
|
|
730
|
+
</XStack>
|
|
731
|
+
</CellIntentWrap>
|
|
732
|
+
))}
|
|
733
|
+
</XStack>
|
|
734
|
+
)}
|
|
735
|
+
</YStack>
|
|
736
|
+
</AsyncBoundary>
|
|
737
|
+
);
|
|
738
|
+
}
|