@teamofsilicons/silicon-ui 0.0.0-stage → 0.2.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/LICENSE +22 -0
- package/README.md +81 -2
- package/check.mjs +236 -0
- package/components/accordion/accordion.d.ts +13 -0
- package/components/accordion/accordion.js +35 -0
- package/components/accordion/accordion.module.css +15 -0
- package/components/action-button/action-button.d.ts +11 -0
- package/components/action-button/action-button.js +57 -0
- package/components/action-button/action-button.module.css +15 -0
- package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
- package/components/activity-heatmap/activity-heatmap.js +303 -0
- package/components/activity-heatmap/activity-heatmap.module.css +83 -0
- package/components/alert/alert.d.ts +13 -0
- package/components/alert/alert.js +79 -0
- package/components/alert/alert.module.css +15 -0
- package/components/animated-counter/animated-counter.d.ts +13 -0
- package/components/animated-counter/animated-counter.js +73 -0
- package/components/animated-counter/animated-counter.module.css +12 -0
- package/components/announcement-bar/announcement-bar.d.ts +53 -0
- package/components/announcement-bar/announcement-bar.js +187 -0
- package/components/announcement-bar/announcement-bar.module.css +60 -0
- package/components/avatar/avatar.d.ts +8 -0
- package/components/avatar/avatar.js +18 -0
- package/components/avatar/avatar.module.css +60 -0
- package/components/avatar-group/avatar-group.d.ts +13 -0
- package/components/avatar-group/avatar-group.js +24 -0
- package/components/avatar-group/avatar-group.module.css +27 -0
- package/components/badge/badge.d.ts +10 -0
- package/components/badge/badge.js +66 -0
- package/components/badge/badge.module.css +52 -0
- package/components/bar-chart/bar-chart.d.ts +32 -0
- package/components/bar-chart/bar-chart.js +327 -0
- package/components/bar-chart/bar-chart.module.css +49 -0
- package/components/billing-toggle/billing-toggle.d.ts +35 -0
- package/components/billing-toggle/billing-toggle.js +83 -0
- package/components/billing-toggle/billing-toggle.module.css +139 -0
- package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
- package/components/bottom-sheet/bottom-sheet.js +386 -0
- package/components/bottom-sheet/bottom-sheet.module.css +69 -0
- package/components/breadcrumb/breadcrumb.d.ts +13 -0
- package/components/breadcrumb/breadcrumb.js +19 -0
- package/components/breadcrumb/breadcrumb.module.css +12 -0
- package/components/brush-chart/brush-chart.d.ts +45 -0
- package/components/brush-chart/brush-chart.js +426 -0
- package/components/brush-chart/brush-chart.module.css +69 -0
- package/components/button/button.d.ts +10 -0
- package/components/button/button.js +97 -0
- package/components/button/button.module.css +39 -0
- package/components/button-group/button-group.d.ts +59 -0
- package/components/button-group/button-group.js +280 -0
- package/components/button-group/button-group.module.css +87 -0
- package/components/button-link/button-link.d.ts +24 -0
- package/components/button-link/button-link.js +11 -0
- package/components/button-link/button-link.module.css +7 -0
- package/components/calendar/calendar.d.ts +22 -0
- package/components/calendar/calendar.js +253 -0
- package/components/calendar/calendar.module.css +51 -0
- package/components/card/card.d.ts +19 -0
- package/components/card/card.js +108 -0
- package/components/card/card.module.css +51 -0
- package/components/card-stack/card-stack.d.ts +34 -0
- package/components/card-stack/card-stack.js +291 -0
- package/components/card-stack/card-stack.module.css +63 -0
- package/components/carousel/carousel.d.ts +28 -0
- package/components/carousel/carousel.js +332 -0
- package/components/carousel/carousel.module.css +42 -0
- package/components/chat-thread/chat-thread.d.ts +135 -0
- package/components/chat-thread/chat-thread.js +406 -0
- package/components/chat-thread/chat-thread.module.css +242 -0
- package/components/checkbox/checkbox.d.ts +7 -0
- package/components/checkbox/checkbox.js +23 -0
- package/components/checkbox/checkbox.module.css +15 -0
- package/components/chip-group/chip-group.d.ts +24 -0
- package/components/chip-group/chip-group.js +196 -0
- package/components/chip-group/chip-group.module.css +38 -0
- package/components/code-block/code-block.d.ts +11 -0
- package/components/code-block/code-block.js +133 -0
- package/components/code-block/code-block.module.css +43 -0
- package/components/color-picker/color-picker.d.ts +40 -0
- package/components/color-picker/color-picker.js +449 -0
- package/components/color-picker/color-picker.module.css +111 -0
- package/components/combobox/combobox.d.ts +19 -0
- package/components/combobox/combobox.js +167 -0
- package/components/combobox/combobox.module.css +28 -0
- package/components/command-palette/command-palette.d.ts +20 -0
- package/components/command-palette/command-palette.js +151 -0
- package/components/command-palette/command-palette.module.css +94 -0
- package/components/comment-thread/comment-thread.d.ts +120 -0
- package/components/comment-thread/comment-thread.js +343 -0
- package/components/comment-thread/comment-thread.module.css +195 -0
- package/components/confirm-morph/confirm-morph.d.ts +65 -0
- package/components/confirm-morph/confirm-morph.js +355 -0
- package/components/confirm-morph/confirm-morph.module.css +146 -0
- package/components/context-menu/context-menu.d.ts +18 -0
- package/components/context-menu/context-menu.js +148 -0
- package/components/context-menu/context-menu.module.css +17 -0
- package/components/copy-button/copy-button.d.ts +10 -0
- package/components/copy-button/copy-button.js +69 -0
- package/components/copy-button/copy-button.module.css +21 -0
- package/components/countdown/countdown.d.ts +31 -0
- package/components/countdown/countdown.js +177 -0
- package/components/countdown/countdown.module.css +49 -0
- package/components/date-picker/date-picker.d.ts +17 -0
- package/components/date-picker/date-picker.js +100 -0
- package/components/date-picker/date-picker.module.css +34 -0
- package/components/date-range-picker/date-range-picker.d.ts +44 -0
- package/components/date-range-picker/date-range-picker.js +534 -0
- package/components/date-range-picker/date-range-picker.module.css +214 -0
- package/components/dialog/dialog.d.ts +11 -0
- package/components/dialog/dialog.js +52 -0
- package/components/dialog/dialog.module.css +36 -0
- package/components/donut-chart/donut-chart.d.ts +46 -0
- package/components/donut-chart/donut-chart.js +382 -0
- package/components/donut-chart/donut-chart.module.css +48 -0
- package/components/drawer/drawer.d.ts +20 -0
- package/components/drawer/drawer.js +111 -0
- package/components/drawer/drawer.module.css +97 -0
- package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
- package/components/dropdown-menu/dropdown-menu.js +39 -0
- package/components/dropdown-menu/dropdown-menu.module.css +38 -0
- package/components/elastic-slider/elastic-slider.d.ts +39 -0
- package/components/elastic-slider/elastic-slider.js +261 -0
- package/components/elastic-slider/elastic-slider.module.css +40 -0
- package/components/empty-state/empty-state.d.ts +12 -0
- package/components/empty-state/empty-state.js +69 -0
- package/components/empty-state/empty-state.module.css +13 -0
- package/components/expandable-card/expandable-card.d.ts +13 -0
- package/components/expandable-card/expandable-card.js +89 -0
- package/components/expandable-card/expandable-card.module.css +22 -0
- package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
- package/components/expanding-button-group/expanding-button-group.js +258 -0
- package/components/expanding-button-group/expanding-button-group.module.css +44 -0
- package/components/expanding-search/expanding-search.d.ts +38 -0
- package/components/expanding-search/expanding-search.js +242 -0
- package/components/expanding-search/expanding-search.module.css +52 -0
- package/components/file-dropzone/file-dropzone.d.ts +44 -0
- package/components/file-dropzone/file-dropzone.js +379 -0
- package/components/file-dropzone/file-dropzone.module.css +115 -0
- package/components/file-upload/file-upload.d.ts +22 -0
- package/components/file-upload/file-upload.js +192 -0
- package/components/file-upload/file-upload.module.css +49 -0
- package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
- package/components/filter-toolbar/filter-toolbar.js +446 -0
- package/components/filter-toolbar/filter-toolbar.module.css +70 -0
- package/components/floating-button-group/floating-button-group.d.ts +51 -0
- package/components/floating-button-group/floating-button-group.js +215 -0
- package/components/floating-button-group/floating-button-group.module.css +74 -0
- package/components/gauge/gauge.d.ts +18 -0
- package/components/gauge/gauge.js +101 -0
- package/components/gauge/gauge.module.css +22 -0
- package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
- package/components/hold-to-confirm/hold-to-confirm.js +165 -0
- package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
- package/components/hover-card/hover-card.d.ts +38 -0
- package/components/hover-card/hover-card.js +145 -0
- package/components/hover-card/hover-card.module.css +20 -0
- package/components/image-compare/image-compare.d.ts +27 -0
- package/components/image-compare/image-compare.js +267 -0
- package/components/image-compare/image-compare.module.css +25 -0
- package/components/in-view-title/in-view-title.d.ts +17 -0
- package/components/in-view-title/in-view-title.js +70 -0
- package/components/in-view-title/in-view-title.module.css +13 -0
- package/components/inline-edit/inline-edit.d.ts +27 -0
- package/components/inline-edit/inline-edit.js +315 -0
- package/components/inline-edit/inline-edit.module.css +73 -0
- package/components/input/input.d.ts +7 -0
- package/components/input/input.js +60 -0
- package/components/input/input.module.css +22 -0
- package/components/json-viewer/json-viewer.d.ts +48 -0
- package/components/json-viewer/json-viewer.js +463 -0
- package/components/json-viewer/json-viewer.module.css +92 -0
- package/components/line-chart/line-chart.d.ts +58 -0
- package/components/line-chart/line-chart.js +383 -0
- package/components/line-chart/line-chart.module.css +60 -0
- package/components/mention-input/mention-input.d.ts +72 -0
- package/components/mention-input/mention-input.js +417 -0
- package/components/mention-input/mention-input.module.css +134 -0
- package/components/metric-card/metric-card.d.ts +8 -0
- package/components/metric-card/metric-card.js +44 -0
- package/components/metric-card/metric-card.module.css +18 -0
- package/components/money-input/money-input.d.ts +48 -0
- package/components/money-input/money-input.js +503 -0
- package/components/money-input/money-input.module.css +77 -0
- package/components/morph-select/morph-select.d.ts +50 -0
- package/components/morph-select/morph-select.js +395 -0
- package/components/morph-select/morph-select.module.css +88 -0
- package/components/multi-select/multi-select.d.ts +18 -0
- package/components/multi-select/multi-select.js +105 -0
- package/components/multi-select/multi-select.module.css +37 -0
- package/components/notification-center/notification-center.d.ts +24 -0
- package/components/notification-center/notification-center.js +146 -0
- package/components/notification-center/notification-center.module.css +79 -0
- package/components/number-field/number-field.d.ts +41 -0
- package/components/number-field/number-field.js +455 -0
- package/components/number-field/number-field.module.css +71 -0
- package/components/otp-input/otp-input.d.ts +13 -0
- package/components/otp-input/otp-input.js +129 -0
- package/components/otp-input/otp-input.module.css +28 -0
- package/components/pagination/pagination.d.ts +7 -0
- package/components/pagination/pagination.js +60 -0
- package/components/pagination/pagination.module.css +16 -0
- package/components/password-field/password-field.d.ts +14 -0
- package/components/password-field/password-field.js +51 -0
- package/components/password-field/password-field.module.css +27 -0
- package/components/password-strength/password-strength.d.ts +107 -0
- package/components/password-strength/password-strength.js +161 -0
- package/components/password-strength/password-strength.module.css +57 -0
- package/components/phone-input/phone-input.d.ts +73 -0
- package/components/phone-input/phone-input.js +612 -0
- package/components/phone-input/phone-input.module.css +98 -0
- package/components/popover/popover.d.ts +6 -0
- package/components/popover/popover.js +19 -0
- package/components/popover/popover.module.css +32 -0
- package/components/progress/progress.d.ts +8 -0
- package/components/progress/progress.js +42 -0
- package/components/progress/progress.module.css +1 -0
- package/components/radio-cards/radio-cards.d.ts +35 -0
- package/components/radio-cards/radio-cards.js +110 -0
- package/components/radio-cards/radio-cards.module.css +84 -0
- package/components/radio-group/radio-group.d.ts +13 -0
- package/components/radio-group/radio-group.js +55 -0
- package/components/radio-group/radio-group.module.css +30 -0
- package/components/resizable-panels/resizable-panels.d.ts +37 -0
- package/components/resizable-panels/resizable-panels.js +338 -0
- package/components/resizable-panels/resizable-panels.module.css +40 -0
- package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
- package/components/rich-text-editor/rich-text-editor.js +1733 -0
- package/components/rich-text-editor/rich-text-editor.module.css +201 -0
- package/components/ridgeline/ridgeline.d.ts +37 -0
- package/components/ridgeline/ridgeline.js +346 -0
- package/components/ridgeline/ridgeline.module.css +58 -0
- package/components/scroll-area/scroll-area.d.ts +40 -0
- package/components/scroll-area/scroll-area.js +153 -0
- package/components/scroll-area/scroll-area.module.css +79 -0
- package/components/search-field/search-field.d.ts +7 -0
- package/components/search-field/search-field.js +20 -0
- package/components/search-field/search-field.module.css +17 -0
- package/components/segmented-control/segmented-control.d.ts +16 -0
- package/components/segmented-control/segmented-control.js +56 -0
- package/components/segmented-control/segmented-control.module.css +15 -0
- package/components/select/select.d.ts +15 -0
- package/components/select/select.js +36 -0
- package/components/select/select.module.css +35 -0
- package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
- package/components/shortcut-recorder/shortcut-recorder.js +380 -0
- package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
- package/components/signature-pad/signature-pad.d.ts +72 -0
- package/components/signature-pad/signature-pad.js +450 -0
- package/components/signature-pad/signature-pad.module.css +84 -0
- package/components/skeleton/skeleton.d.ts +13 -0
- package/components/skeleton/skeleton.js +53 -0
- package/components/skeleton/skeleton.module.css +11 -0
- package/components/slider/slider.d.ts +45 -0
- package/components/slider/slider.js +347 -0
- package/components/slider/slider.module.css +45 -0
- package/components/slope-chart/slope-chart.d.ts +38 -0
- package/components/slope-chart/slope-chart.js +183 -0
- package/components/slope-chart/slope-chart.module.css +51 -0
- package/components/slot-text/slot-text.d.ts +26 -0
- package/components/slot-text/slot-text.js +119 -0
- package/components/slot-text/slot-text.module.css +19 -0
- package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
- package/components/sortable-data-table/sortable-data-table.js +240 -0
- package/components/sortable-data-table/sortable-data-table.module.css +116 -0
- package/components/sparkline/sparkline.d.ts +19 -0
- package/components/sparkline/sparkline.js +239 -0
- package/components/sparkline/sparkline.module.css +26 -0
- package/components/split-button/split-button.d.ts +19 -0
- package/components/split-button/split-button.js +33 -0
- package/components/split-button/split-button.module.css +51 -0
- package/components/stepper/stepper.d.ts +36 -0
- package/components/stepper/stepper.js +108 -0
- package/components/stepper/stepper.module.css +74 -0
- package/components/streamgraph/streamgraph.d.ts +51 -0
- package/components/streamgraph/streamgraph.js +376 -0
- package/components/streamgraph/streamgraph.module.css +55 -0
- package/components/swipe-actions/swipe-actions.d.ts +35 -0
- package/components/swipe-actions/swipe-actions.js +330 -0
- package/components/swipe-actions/swipe-actions.module.css +64 -0
- package/components/switch/switch.d.ts +7 -0
- package/components/switch/switch.js +45 -0
- package/components/switch/switch.module.css +12 -0
- package/components/tabs/tabs.d.ts +8 -0
- package/components/tabs/tabs.js +138 -0
- package/components/tabs/tabs.module.css +27 -0
- package/components/tag-input/tag-input.d.ts +15 -0
- package/components/tag-input/tag-input.js +150 -0
- package/components/tag-input/tag-input.module.css +34 -0
- package/components/text-morph/text-morph.d.ts +13 -0
- package/components/text-morph/text-morph.js +69 -0
- package/components/text-morph/text-morph.module.css +8 -0
- package/components/text-reveal/text-reveal.d.ts +14 -0
- package/components/text-reveal/text-reveal.js +20 -0
- package/components/text-reveal/text-reveal.module.css +13 -0
- package/components/text-shimmer/text-shimmer.d.ts +18 -0
- package/components/text-shimmer/text-shimmer.js +90 -0
- package/components/text-shimmer/text-shimmer.module.css +9 -0
- package/components/textarea/textarea.d.ts +7 -0
- package/components/textarea/textarea.js +56 -0
- package/components/textarea/textarea.module.css +22 -0
- package/components/theme-switch/theme-switch.d.ts +11 -0
- package/components/theme-switch/theme-switch.js +37 -0
- package/components/theme-switch/theme-switch.module.css +31 -0
- package/components/time-picker/time-picker.d.ts +13 -0
- package/components/time-picker/time-picker.js +95 -0
- package/components/time-picker/time-picker.module.css +23 -0
- package/components/timeline/timeline.d.ts +47 -0
- package/components/timeline/timeline.js +171 -0
- package/components/timeline/timeline.module.css +51 -0
- package/components/toast/toast.d.ts +8 -0
- package/components/toast/toast.js +118 -0
- package/components/toast/toast.module.css +100 -0
- package/components/toast-stack/toast-stack.d.ts +57 -0
- package/components/toast-stack/toast-stack.js +399 -0
- package/components/toast-stack/toast-stack.module.css +61 -0
- package/components/tooltip/tooltip.d.ts +8 -0
- package/components/tooltip/tooltip.js +62 -0
- package/components/tooltip/tooltip.module.css +18 -0
- package/components/tree-view/tree-view.d.ts +17 -0
- package/components/tree-view/tree-view.js +147 -0
- package/components/tree-view/tree-view.module.css +22 -0
- package/components/treemap/treemap.d.ts +35 -0
- package/components/treemap/treemap.js +375 -0
- package/components/treemap/treemap.module.css +59 -0
- package/components/usage-meter/usage-meter.d.ts +24 -0
- package/components/usage-meter/usage-meter.js +296 -0
- package/components/usage-meter/usage-meter.module.css +61 -0
- package/components/user-menu/user-menu.d.ts +62 -0
- package/components/user-menu/user-menu.js +316 -0
- package/components/user-menu/user-menu.module.css +97 -0
- package/components/waffle-chart/waffle-chart.d.ts +39 -0
- package/components/waffle-chart/waffle-chart.js +244 -0
- package/components/waffle-chart/waffle-chart.module.css +52 -0
- package/components.json +1330 -0
- package/eslint.js +53 -0
- package/lib/escape.d.ts +19 -0
- package/lib/escape.js +50 -0
- package/lib/media.d.ts +353 -0
- package/lib/media.js +65 -0
- package/lib/menu-highlight.d.ts +28 -0
- package/lib/menu-highlight.js +43 -0
- package/lib/motion-tokens.d.ts +60 -0
- package/lib/motion-tokens.js +25 -0
- package/lib/return-focus.d.ts +9 -0
- package/lib/return-focus.js +53 -0
- package/lib/silicon-provider.d.ts +60 -0
- package/lib/silicon-provider.js +47 -0
- package/lib/use-copy-feedback.d.ts +8 -0
- package/lib/use-copy-feedback.js +36 -0
- package/lib/use-today.d.ts +2 -0
- package/lib/use-today.js +29 -0
- package/package.json +76 -4
- package/styles.css +218 -0
- package/styles.css.d.ts +1 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { animate, motion, useInView, useMotionValue, useReducedMotion, useSpring, useTransform } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import styles from "./waffle-chart.module.css";
|
|
7
|
+
const { spring } = motionTokens;
|
|
8
|
+
const GAP = 3;
|
|
9
|
+
/** accentKey takes the first series and the next three categories the supporting series. Anything past that falls back to neutral steps that alternate in lightness. */
|
|
10
|
+
const SHADES = [40, 20, 30, 14];
|
|
11
|
+
/** A second, colour-free channel: neutral categories cycle through square, round, and framed cells, so neighbours differ in shape as well as lightness. */
|
|
12
|
+
const SHAPES = ["square", "round", "framed"];
|
|
13
|
+
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
14
|
+
const grouped = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
|
|
15
|
+
const subscribeNothing = () => () => { };
|
|
16
|
+
function useReducedMotionSafe() {
|
|
17
|
+
const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false);
|
|
18
|
+
return !!useReducedMotion() && hydrated;
|
|
19
|
+
}
|
|
20
|
+
/** Whole cells per category by largest remainder, so the counts always add up to the grid. */
|
|
21
|
+
function allocate(data, total, cells) {
|
|
22
|
+
if (total <= 0)
|
|
23
|
+
return data.map(() => 0);
|
|
24
|
+
const exact = data.map(item => Math.max(0, item.value) / total * cells);
|
|
25
|
+
const counts = exact.map(Math.floor);
|
|
26
|
+
let left = cells - counts.reduce((sum, count) => sum + count, 0);
|
|
27
|
+
const order = exact.map((value, index) => ({ index, rest: value - Math.floor(value) })).sort((a, b) => b.rest - a.rest);
|
|
28
|
+
for (let k = 0; left > 0 && k < order.length; k++, left--)
|
|
29
|
+
counts[order[k].index]++;
|
|
30
|
+
return counts;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Hands cells to categories while moving as few as possible: each category keeps the cells it already had,
|
|
34
|
+
* surplus cells join the categories that grew, and every block takes its cells in their previous order so paths never cross.
|
|
35
|
+
*/
|
|
36
|
+
function reassign(previous, data, counts) {
|
|
37
|
+
const bySlot = [...previous].sort((a, b) => a.slot - b.slot);
|
|
38
|
+
const kept = new Map();
|
|
39
|
+
const pool = [];
|
|
40
|
+
const want = new Map(data.map((item, index) => [item.key, counts[index]]));
|
|
41
|
+
for (const cell of bySlot) {
|
|
42
|
+
const list = cell.key !== null ? kept.get(cell.key) ?? [] : [];
|
|
43
|
+
if (cell.key !== null && list.length < (want.get(cell.key) ?? 0)) {
|
|
44
|
+
list.push(cell);
|
|
45
|
+
kept.set(cell.key, list);
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
pool.push(cell);
|
|
49
|
+
}
|
|
50
|
+
const next = [];
|
|
51
|
+
let slot = 0;
|
|
52
|
+
data.forEach((item, index) => {
|
|
53
|
+
const own = kept.get(item.key) ?? [];
|
|
54
|
+
while (own.length < counts[index] && pool.length)
|
|
55
|
+
own.push(pool.shift());
|
|
56
|
+
own.sort((a, b) => a.slot - b.slot).forEach(cell => next.push({ id: cell.id, key: item.key, slot: slot++ }));
|
|
57
|
+
});
|
|
58
|
+
pool.forEach(cell => next.push({ id: cell.id, key: null, slot: slot++ }));
|
|
59
|
+
return next.sort((a, b) => a.id - b.id);
|
|
60
|
+
}
|
|
61
|
+
/** A share that rolls to its new value on a spring and holds its width with tabular figures. */
|
|
62
|
+
function Rolling({ value, decimals, reduced }) {
|
|
63
|
+
const shown = useMotionValue(value);
|
|
64
|
+
const text = useTransform(shown, current => `${current.toFixed(decimals)}%`);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (reduced) {
|
|
67
|
+
shown.jump(value);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const controls = animate(shown, value, spring.smooth);
|
|
71
|
+
return () => controls.stop();
|
|
72
|
+
}, [reduced, shown, value]);
|
|
73
|
+
return _jsx(motion.span, { children: text });
|
|
74
|
+
}
|
|
75
|
+
export function WaffleChart({ data, label, unit = "", formatValue, rows = 10, columns = 10, accentKey, activeKey, defaultActiveKey = null, onActiveChange, legend = true, decimals = 0, emptyLabel = "No data", ref, className }) {
|
|
76
|
+
const reduced = useReducedMotionSafe();
|
|
77
|
+
const figure = useRef(null);
|
|
78
|
+
const grid = useRef(null);
|
|
79
|
+
const tip = useRef(null);
|
|
80
|
+
useImperativeHandle(ref, () => figure.current);
|
|
81
|
+
const inView = useInView(figure, { once: true, amount: .35 });
|
|
82
|
+
const count = Math.max(1, rows * columns);
|
|
83
|
+
const total = data.reduce((sum, item) => sum + Math.max(0, item.value), 0);
|
|
84
|
+
const empty = !data.length || total <= 0;
|
|
85
|
+
const accent = accentKey === undefined ? data[0]?.key ?? null : accentKey;
|
|
86
|
+
// Cells are persistent objects; each data change reassigns them so they travel instead of repainting.
|
|
87
|
+
const signature = `${count}|${data.map(item => `${item.key}:${item.value}`).join(",")}`;
|
|
88
|
+
const counts = useMemo(() => allocate(data, total, count), [signature]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
89
|
+
const [layout, setLayout] = useState(() => ({ cells: reassign(Array.from({ length: count }, (_, id) => ({ id, key: null, slot: id })), data, counts), moved: new Set(), version: 0 }));
|
|
90
|
+
const [seen, setSeen] = useState(signature);
|
|
91
|
+
if (seen !== signature) {
|
|
92
|
+
setSeen(signature);
|
|
93
|
+
const base = Array.from({ length: count }, (_, id) => layout.cells[id] ?? { id, key: null, slot: id });
|
|
94
|
+
const next = reassign(base, data, counts);
|
|
95
|
+
setLayout({ cells: next, moved: new Set(next.filter(cell => base[cell.id].slot !== cell.slot || base[cell.id].key !== cell.key).map(cell => cell.id)), version: layout.version + 1 });
|
|
96
|
+
}
|
|
97
|
+
const { cells, moved, version } = layout;
|
|
98
|
+
const [ownActive, setOwnActive] = useState(defaultActiveKey);
|
|
99
|
+
const pinned = activeKey !== undefined ? activeKey : ownActive;
|
|
100
|
+
const setPinned = (key) => { if (activeKey === undefined)
|
|
101
|
+
setOwnActive(key); onActiveChange?.(key); };
|
|
102
|
+
const [pointer, setPointer] = useState(null);
|
|
103
|
+
const [preview, setPreview] = useState(null);
|
|
104
|
+
const focusKey = pointer?.key ?? preview ?? pinned;
|
|
105
|
+
const active = focusKey && data.some(item => item.key === focusKey) ? focusKey : null;
|
|
106
|
+
const [side, setSide] = useState(0);
|
|
107
|
+
useLayoutEffect(() => {
|
|
108
|
+
const node = grid.current;
|
|
109
|
+
if (!node)
|
|
110
|
+
return;
|
|
111
|
+
const read = () => setSide(node.clientWidth);
|
|
112
|
+
read();
|
|
113
|
+
if (typeof ResizeObserver === "undefined")
|
|
114
|
+
return;
|
|
115
|
+
const observer = new ResizeObserver(read);
|
|
116
|
+
observer.observe(node);
|
|
117
|
+
return () => observer.disconnect();
|
|
118
|
+
}, []);
|
|
119
|
+
const size = side ? (side - GAP * (columns - 1)) / columns : 0;
|
|
120
|
+
const height = size ? size * rows + GAP * (rows - 1) : 0;
|
|
121
|
+
// Column by column from the bottom-left corner, like a glass filling up.
|
|
122
|
+
const place = (slot) => ({ x: Math.floor(slot / rows) * (size + GAP), y: (rows - 1 - (slot % rows)) * (size + GAP) });
|
|
123
|
+
const colorOf = (key) => {
|
|
124
|
+
if (key === null)
|
|
125
|
+
return "transparent";
|
|
126
|
+
const index = data.findIndex(item => item.key === key), item = data[index];
|
|
127
|
+
if (!item)
|
|
128
|
+
return "transparent";
|
|
129
|
+
if (item.color)
|
|
130
|
+
return item.color;
|
|
131
|
+
if (key === accent)
|
|
132
|
+
return "var(--series-1)";
|
|
133
|
+
const rank = data.filter(entry => entry.key !== accent).findIndex(entry => entry.key === key);
|
|
134
|
+
return rank < 3 ? `var(--series-${rank + 2})` : `color-mix(in oklab, var(--foreground) ${SHADES[(rank - 3) % SHADES.length]}%, var(--surface))`;
|
|
135
|
+
};
|
|
136
|
+
const shapeOf = (key) => {
|
|
137
|
+
if (key === null || key === accent)
|
|
138
|
+
return "square";
|
|
139
|
+
const rank = data.filter(entry => entry.key !== accent).findIndex(entry => entry.key === key);
|
|
140
|
+
return rank < 0 ? "square" : SHAPES[rank % SHAPES.length];
|
|
141
|
+
};
|
|
142
|
+
// The tooltip glides between cells on its own spring and stays inside the figure.
|
|
143
|
+
const tipX = useMotionValue(0), tipY = useMotionValue(0);
|
|
144
|
+
const follow = { stiffness: 520, damping: 44, restDelta: .01 };
|
|
145
|
+
const glideX = useSpring(tipX, follow), glideY = useSpring(tipY, follow);
|
|
146
|
+
const wasShown = useRef(false);
|
|
147
|
+
useLayoutEffect(() => {
|
|
148
|
+
const bubble = tip.current, host = figure.current, board = grid.current;
|
|
149
|
+
if (!pointer || !bubble || !host || !board || !size) {
|
|
150
|
+
wasShown.current = false;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const hostBox = host.getBoundingClientRect(), boardBox = board.getBoundingClientRect();
|
|
154
|
+
const { x, y } = place(pointer.slot);
|
|
155
|
+
const cx = boardBox.left - hostBox.left + x + size / 2, cy = boardBox.top - hostBox.top + y;
|
|
156
|
+
const tw = bubble.offsetWidth, th = bubble.offsetHeight;
|
|
157
|
+
let top = cy - th - 10;
|
|
158
|
+
if (top < 0)
|
|
159
|
+
top = cy + size + 10;
|
|
160
|
+
const left = clamp(cx - tw / 2, 0, Math.max(0, hostBox.width - tw));
|
|
161
|
+
tipX.set(left);
|
|
162
|
+
tipY.set(clamp(top, 0, Math.max(0, hostBox.height - th)));
|
|
163
|
+
if (!wasShown.current || reduced) {
|
|
164
|
+
glideX.jump(tipX.get());
|
|
165
|
+
glideY.jump(tipY.get());
|
|
166
|
+
}
|
|
167
|
+
wasShown.current = true;
|
|
168
|
+
});
|
|
169
|
+
const slotAt = (clientX, clientY) => {
|
|
170
|
+
const box = grid.current?.getBoundingClientRect();
|
|
171
|
+
if (!box || !size)
|
|
172
|
+
return null;
|
|
173
|
+
const col = clamp(Math.floor((clientX - box.left) / (size + GAP)), 0, columns - 1);
|
|
174
|
+
const row = clamp(Math.floor((clientY - box.top) / (size + GAP)), 0, rows - 1);
|
|
175
|
+
return col * rows + (rows - 1 - row);
|
|
176
|
+
};
|
|
177
|
+
const pointAt = (slot) => {
|
|
178
|
+
if (slot === null)
|
|
179
|
+
return null;
|
|
180
|
+
const cell = cells.find(item => item.slot === slot);
|
|
181
|
+
return cell?.key ? { key: cell.key, slot } : null;
|
|
182
|
+
};
|
|
183
|
+
const onPointerMove = (event) => { if (event.pointerType === "mouse" || event.buttons)
|
|
184
|
+
setPointer(pointAt(slotAt(event.clientX, event.clientY))); };
|
|
185
|
+
const onPointerDown = (event) => { if (event.pointerType !== "mouse")
|
|
186
|
+
event.currentTarget.setPointerCapture?.(event.pointerId); setPointer(pointAt(slotAt(event.clientX, event.clientY))); };
|
|
187
|
+
// Arrows walk the grid cell by cell; Page keys jump between categories.
|
|
188
|
+
const onKeyDown = (event) => {
|
|
189
|
+
if (empty)
|
|
190
|
+
return;
|
|
191
|
+
if (event.key === "Escape" && pointer) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
setPointer(null);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const filled = cells.filter(cell => cell.key !== null).length;
|
|
197
|
+
const at = pointer?.slot ?? 0;
|
|
198
|
+
const col = Math.floor(at / rows), row = at % rows;
|
|
199
|
+
const blocks = counts.map((size, index) => ({ start: counts.slice(0, index).reduce((sum, value) => sum + value, 0), size })).filter(block => block.size > 0);
|
|
200
|
+
const block = Math.max(0, blocks.findIndex(item => at >= item.start && at < item.start + item.size));
|
|
201
|
+
const moves = {
|
|
202
|
+
ArrowUp: () => col * rows + Math.min(rows - 1, row + 1),
|
|
203
|
+
ArrowDown: () => col * rows + Math.max(0, row - 1),
|
|
204
|
+
ArrowRight: () => Math.min(columns - 1, col + 1) * rows + row,
|
|
205
|
+
ArrowLeft: () => Math.max(0, col - 1) * rows + row,
|
|
206
|
+
PageDown: () => blocks[Math.min(blocks.length - 1, block + 1)].start,
|
|
207
|
+
PageUp: () => at > blocks[block].start ? blocks[block].start : blocks[Math.max(0, block - 1)].start,
|
|
208
|
+
Home: () => 0,
|
|
209
|
+
End: () => filled - 1,
|
|
210
|
+
};
|
|
211
|
+
const move = moves[event.key];
|
|
212
|
+
if (!move)
|
|
213
|
+
return;
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
setPointer(pointAt(clamp(pointer ? move() : 0, 0, Math.max(0, filled - 1))));
|
|
216
|
+
};
|
|
217
|
+
const format = (value, item) => `${formatValue ? formatValue(value, item) : grouped.format(value)}${unit ? ` ${unit}` : ""}`;
|
|
218
|
+
const share = (item) => total > 0 ? Math.max(0, item.value) / total * 100 : 0;
|
|
219
|
+
const reading = pointer ? data.find(item => item.key === pointer.key) ?? null : null;
|
|
220
|
+
const announce = reading ? `${reading.label}, ${share(reading).toFixed(decimals)}%, ${format(reading.value, reading)}` : "";
|
|
221
|
+
const summary = empty ? `${label}. ${emptyLabel}.` : `${label}. ${data.map(item => `${item.label} ${share(item).toFixed(decimals)}%`).join(", ")}.`;
|
|
222
|
+
const shownX = reduced ? tipX : glideX, shownY = reduced ? tipY : glideY;
|
|
223
|
+
const settle = reduced ? { duration: 0 } : spring.morph;
|
|
224
|
+
return _jsxs("figure", { ref: figure, className: [styles.figure, className].filter(Boolean).join(" "), "aria-label": label, "data-legend": legend || undefined, children: [_jsxs("div", { className: styles.body, children: [_jsxs("div", { className: styles.board, children: [_jsx("div", { ref: grid, className: styles.grid, style: { height: height || undefined, aspectRatio: height ? undefined : `${columns} / ${rows}` }, "data-active": active ? true : undefined, role: "group", tabIndex: empty ? -1 : 0, "aria-roledescription": "waffle chart", "aria-label": `${label}. ${count} cells. Use arrow keys to move between cells, Page Up and Page Down to jump between categories.`, onPointerMove: onPointerMove, onPointerDown: onPointerDown, onPointerUp: event => { if (event.pointerType !== "mouse")
|
|
225
|
+
setPointer(null); }, onPointerCancel: () => setPointer(null), onPointerLeave: event => { if (event.pointerType === "mouse")
|
|
226
|
+
setPointer(null); }, onKeyDown: onKeyDown, onBlur: () => setPointer(null), onFocus: event => { if (event.currentTarget.matches(":focus-visible") && !empty)
|
|
227
|
+
setPointer(current => current ?? pointAt(0)); }, children: size > 0 && cells.map(cell => {
|
|
228
|
+
const { x, y } = place(cell.slot);
|
|
229
|
+
const travel = moved.has(cell.id) && !reduced;
|
|
230
|
+
const delay = reduced ? 0 : (cell.slot / count) * .32;
|
|
231
|
+
const on = cell.key !== null && cell.key === active;
|
|
232
|
+
const shown = inView || reduced;
|
|
233
|
+
// A travelling cell dips while it flies, so the flow reads as cells lifting out of one block and landing in another.
|
|
234
|
+
const dip = travel ? [1, .62 + (version % 2) * .001, 1] : 1;
|
|
235
|
+
return _jsx(motion.span, { className: styles.cell, "aria-hidden": "true", "data-on": on || undefined, "data-cursor": pointer?.slot === cell.slot || undefined, "data-empty": cell.key === null || undefined, "data-shape": shapeOf(cell.key), style: { width: size, height: size, "--cell": colorOf(cell.key), "--delay": `${delay}s` }, initial: reduced ? false : { x, y, scale: .4, opacity: 0 }, animate: shown ? { x, y, scale: dip, opacity: cell.key === null ? 0 : 1 } : { x, y, scale: .4, opacity: 0 }, transition: reduced ? { duration: 0 } : {
|
|
236
|
+
x: { ...settle, delay }, y: { ...settle, delay },
|
|
237
|
+
scale: travel ? { duration: .62, times: [0, .42, 1], ease: motionTokens.ease.inOut, delay } : { ...spring.snappy, delay: delay * .6 },
|
|
238
|
+
opacity: { duration: motionTokens.duration.standard, ease: motionTokens.ease.standard, delay: delay * .6 },
|
|
239
|
+
} }, cell.id);
|
|
240
|
+
}) }), !empty && _jsxs(motion.div, { ref: tip, className: styles.tooltip, "data-shown": pointer ? true : undefined, style: { x: shownX, y: shownY }, "aria-hidden": "true", children: [_jsxs("p", { className: styles.tipRow, children: [_jsx("span", { className: styles.tipSwatch, "data-shape": shapeOf(reading?.key ?? null), style: { "--cell": colorOf(reading?.key ?? null) } }), _jsx("span", { className: styles.tipName, children: reading?.label ?? "" }), _jsx("span", { className: styles.tipValue, children: reading ? `${share(reading).toFixed(decimals)}%` : "" })] }), _jsx("p", { className: styles.tipMeta, children: reading ? format(reading.value, reading) : "" })] }), empty && _jsx("p", { className: styles.message, children: emptyLabel })] }), legend && !empty && _jsx("ul", { className: styles.legend, "aria-label": `${label} categories`, onPointerLeave: () => setPreview(null), children: data.map(item => _jsx("li", { children: _jsxs("button", { type: "button", className: styles.item, "aria-pressed": pinned === item.key, "data-on": active === item.key || undefined, "data-dim": (active !== null && active !== item.key) || undefined, onClick: () => setPinned(pinned === item.key ? null : item.key), onPointerEnter: event => { if (event.pointerType === "mouse")
|
|
241
|
+
setPreview(item.key); }, onFocus: event => { if (event.currentTarget.matches(":focus-visible"))
|
|
242
|
+
setPreview(item.key); }, onBlur: () => setPreview(null), children: [_jsx("span", { className: styles.swatch, "data-shape": shapeOf(item.key), style: { "--cell": colorOf(item.key) }, "aria-hidden": "true" }), _jsx("span", { className: styles.itemLabel, children: item.label }), _jsx("span", { className: styles.itemShare, children: _jsx(Rolling, { value: share(item), decimals: decimals, reduced: reduced }) })] }) }, item.key)) })] }), _jsx("p", { className: styles.srOnly, "aria-live": "polite", "aria-atomic": "true", children: announce }), _jsx("p", { className: styles.srOnly, children: summary }), !empty && _jsx("div", { className: styles.srOnly, children: _jsxs("table", { children: [_jsx("caption", { children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share" }), _jsx("th", { scope: "col", children: "Cells" })] }) }), _jsx("tbody", { children: data.map((item, index) => _jsxs("tr", { children: [_jsx("th", { scope: "row", children: item.label }), _jsx("td", { children: format(item.value, item) }), _jsxs("td", { children: [share(item).toFixed(decimals), "%"] }), _jsx("td", { children: counts[index] })] }, item.key)) })] }) })] });
|
|
243
|
+
}
|
|
244
|
+
export default WaffleChart;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.figure { position: relative; min-width: 0; margin: 0; color: var(--foreground); font-family: var(--font-body); letter-spacing: var(--tracking-body); container-type: inline-size; }
|
|
2
|
+
|
|
3
|
+
/* Series colors (--series-1 to --series-4) come from registry/foundation.css. */
|
|
4
|
+
.body { display: grid; min-width: 0; grid-template-columns: minmax(0, 1fr); align-items: center; gap: var(--space-section); }
|
|
5
|
+
@container (min-width: 420px) {
|
|
6
|
+
.figure[data-legend] .body { grid-template-columns: minmax(0, 232px) minmax(0, 1fr); gap: var(--space-section); }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.board { min-width: 0; }
|
|
10
|
+
.grid { position: relative; width: 100%; max-width: 300px; margin-inline: auto; outline: none; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; cursor: default; }
|
|
11
|
+
|
|
12
|
+
/* Each cell is one unit. Its colour changes mid-flight with the same stagger as its travel. */
|
|
13
|
+
.cell { --inner: 0 0 0 0 transparent; --outer: 0 0 0 0 transparent; position: absolute; top: 0; left: 0; border-radius: 22%; background: var(--cell); box-shadow: var(--inner), var(--outer); will-change: transform; transition: background-color var(--duration-considered) var(--ease-standard) var(--delay, 0s), border-radius var(--duration-considered) var(--ease-standard) var(--delay, 0s), box-shadow var(--duration-considered) var(--ease-standard) var(--delay, 0s), opacity var(--duration-fast) var(--ease-standard); }
|
|
14
|
+
.grid[data-active] .cell:not([data-on]) { opacity: .26 !important; transition: background-color var(--duration-considered) var(--ease-standard) var(--delay, 0s), border-radius var(--duration-considered) var(--ease-standard) var(--delay, 0s), opacity var(--duration-standard) var(--ease-standard); }
|
|
15
|
+
.cell[data-cursor] { --outer: 0 0 0 2px var(--surface), 0 0 0 3.5px color-mix(in oklab, var(--foreground) 55%, transparent); }
|
|
16
|
+
|
|
17
|
+
/* Shape is the colour-free second channel: a cell morphs between these as it changes category. */
|
|
18
|
+
.cell[data-shape="round"], .swatch[data-shape="round"], .tipSwatch[data-shape="round"] { border-radius: 50%; corner-shape: round; }
|
|
19
|
+
.cell[data-shape="framed"] { --inner: inset 0 0 0 2.5px var(--cell), inset 0 0 0 3.25px var(--surface); }
|
|
20
|
+
.swatch[data-shape="framed"], .tipSwatch[data-shape="framed"] { box-shadow: inset 0 0 0 2px var(--cell), inset 0 0 0 3px var(--surface); }
|
|
21
|
+
|
|
22
|
+
.tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 150px; max-width: min(240px, 100%); gap: 2px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
|
|
23
|
+
.tooltip[data-shown] { opacity: 1; scale: 1; }
|
|
24
|
+
.tipRow { display: flex; min-width: 0; align-items: center; gap: 8px; margin: 0; font-size: var(--text-sm); line-height: 1.3; }
|
|
25
|
+
.tipSwatch { width: 10px; height: 10px; flex: none; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--cell); transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
26
|
+
.tipName { min-width: 0; flex: 1; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
.tipValue { flex: none; margin-left: 8px; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
28
|
+
.tipMeta { margin: 0 0 0 18px; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
29
|
+
|
|
30
|
+
.message { display: grid; min-height: 160px; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); }
|
|
31
|
+
|
|
32
|
+
/* The legend doubles as the reading: each share rolls to its new value. */
|
|
33
|
+
.legend { display: grid; min-width: 0; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 2px var(--space-related); margin: 0; padding: 0; list-style: none; }
|
|
34
|
+
@container (min-width: 420px) { .figure[data-legend] .legend { grid-template-columns: minmax(0, 1fr); } }
|
|
35
|
+
/* Below the side-by-side layout two columns would clip names like "Wind and solar", so the legend reads as one list. */
|
|
36
|
+
/* Stacked, the legend shares the grid's measure and its row padding hangs outside, so swatches and shares line up with the cell edges. */
|
|
37
|
+
@container (max-width: 419px) { .legend { grid-template-columns: minmax(0, 1fr); margin-inline: max(-10px, calc((100% - 320px) / 2)); } }
|
|
38
|
+
.item { display: grid; width: 100%; min-height: 36px; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 4px 10px; border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); text-align: left; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
|
|
39
|
+
.item:active { transform: scale(.98); }
|
|
40
|
+
.item[data-on] { color: var(--foreground); background: color-mix(in oklab, var(--foreground) 5%, transparent); }
|
|
41
|
+
.item[data-dim] { opacity: .5; }
|
|
42
|
+
.item[aria-pressed="true"] { color: var(--foreground); }
|
|
43
|
+
@media (hover: hover) and (pointer: fine) { .item:hover { color: var(--foreground); } }
|
|
44
|
+
.swatch { width: 10px; height: 10px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--cell); }
|
|
45
|
+
.itemLabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
46
|
+
.itemShare { color: var(--foreground); font-size: var(--text-base); font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; min-width: 3.2em; }
|
|
47
|
+
|
|
48
|
+
.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
49
|
+
|
|
50
|
+
@media (prefers-reduced-motion: reduce) {
|
|
51
|
+
.cell, .grid[data-active] .cell:not([data-on]), .tooltip, .tipSwatch, .item { transition: none; }
|
|
52
|
+
}
|