@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,98 @@
|
|
|
1
|
+
/* No row gap: grid tracks clamp a negative margin at zero, so a closed message row would still pay the gap. Each row carries its own space instead. */
|
|
2
|
+
.field { --pi-h: var(--control-height-md); --pi-panel-w: 320px; position: relative; display: grid; min-width: 0; font-family: var(--font-body); letter-spacing: var(--tracking-body); }
|
|
3
|
+
.field[data-open] { z-index: 30; }
|
|
4
|
+
.label { justify-self: start; margin-bottom: var(--space-related); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); }
|
|
5
|
+
|
|
6
|
+
/* The shell matches the labeled input: one border, one radius, and a quiet border change on hover and focus. */
|
|
7
|
+
.control { position: relative; display: flex; min-width: 0; height: var(--pi-h); box-sizing: border-box; border: 1px solid var(--border-strong); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); transition: border-color var(--duration-fast) var(--ease-standard); }
|
|
8
|
+
@media (hover: hover) and (pointer: fine) { .control:hover:not([data-invalid]) { border-color: var(--foreground); } }
|
|
9
|
+
.control:has(.number:focus) { border-color: var(--foreground); }
|
|
10
|
+
.control[data-invalid] { border-color: var(--danger); }
|
|
11
|
+
.field[data-disabled] .control { opacity: .5; background: var(--surface-muted); }
|
|
12
|
+
|
|
13
|
+
/* The anchor holds the closed button's place and springs to a new width when the calling code changes length. */
|
|
14
|
+
.anchor { position: relative; flex: none; height: 100%; }
|
|
15
|
+
.anchor::after { content: ""; position: absolute; top: 10px; right: 0; bottom: 10px; width: 1px; background: var(--border); transition: opacity var(--duration-fast) var(--ease-standard); }
|
|
16
|
+
.field[data-open] .anchor::after { opacity: 0; }
|
|
17
|
+
|
|
18
|
+
/* One material for both states. Open, it steps out over the shell's own border so the two edges never double up. */
|
|
19
|
+
.shape { position: absolute; z-index: 2; top: 0; left: 0; overflow: clip; background: var(--surface); transition: box-shadow var(--duration-considered) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
|
|
20
|
+
.shape::after { content: ""; position: absolute; inset: 0; z-index: 3; border: 1px solid var(--border); border-radius: inherit; corner-shape: inherit; opacity: 0; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
|
|
21
|
+
.field[data-open] .shape { translate: -1px -1px; background: var(--surface-raised); box-shadow: var(--shadow-floating); }
|
|
22
|
+
.field[data-open] .shape::after { opacity: 1; }
|
|
23
|
+
.field[data-disabled] .shape { background: transparent; }
|
|
24
|
+
|
|
25
|
+
.lid { position: absolute; z-index: 1; top: 0; right: 0; left: 0; height: calc(var(--pi-h) - 2px); }
|
|
26
|
+
|
|
27
|
+
.trigger { box-sizing: border-box; display: flex; height: calc(var(--pi-h) - 2px); align-items: center; padding: 0 36px 0 12px; font-size: var(--text-sm); font-weight: 500; white-space: nowrap; }
|
|
28
|
+
.lid .trigger { position: absolute; inset: 3px auto 3px 3px; width: calc(100% - 6px); height: auto; margin: 0; padding: 0 33px 0 9px; border: 0; border-radius: calc(var(--radius-control) - 4px); corner-shape: var(--corner-shape); background: transparent; color: var(--foreground); cursor: pointer; font-family: inherit; letter-spacing: inherit; text-align: left; -webkit-tap-highlight-color: transparent; touch-action: manipulation; transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
29
|
+
@media (hover: hover) and (pointer: fine) { .field:not([data-open], [data-disabled]) .lid .trigger:hover { background: var(--surface-muted); } }
|
|
30
|
+
.lid .trigger:active:not(:disabled) { background: var(--surface-muted); }
|
|
31
|
+
.lid .trigger:disabled { cursor: not-allowed; }
|
|
32
|
+
/* No focus rings (product decision): keyboard focus reads as the same quiet fill as hover. */
|
|
33
|
+
.lid .trigger:focus-visible { background: var(--surface-muted); }
|
|
34
|
+
.clear:focus-visible, .closeHit:focus-visible { background: color-mix(in oklab, var(--foreground) 8%, transparent); }
|
|
35
|
+
/* An invisible copy that says how wide the closed button wants to be. */
|
|
36
|
+
.measure { visibility: hidden; pointer-events: none; width: max-content; }
|
|
37
|
+
|
|
38
|
+
.slot { position: relative; display: block; min-width: 0; height: 100%; flex: 1; }
|
|
39
|
+
.slot[data-hidden] { opacity: 0; transition: opacity var(--duration-instant) var(--ease-standard); }
|
|
40
|
+
.layer { position: absolute; inset: 0; display: flex; align-items: center; gap: 6px; }
|
|
41
|
+
.flag { display: inline-grid; width: 20px; flex: none; place-items: center; font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font-body); font-size: 17px; font-weight: 400; line-height: 1; }
|
|
42
|
+
.dial { font-variant-numeric: tabular-nums; }
|
|
43
|
+
|
|
44
|
+
.chevron { position: absolute; top: 50%; right: 12px; display: grid; width: 16px; height: 16px; margin-top: -8px; place-items: center; color: var(--text-muted); pointer-events: none; }
|
|
45
|
+
|
|
46
|
+
/* Search takes the lid's place while the list is open. */
|
|
47
|
+
.searchRow { position: absolute; inset: 0; display: flex; align-items: center; gap: var(--space-related); padding: 0 2px 0 13px; }
|
|
48
|
+
.searchRow[inert] { pointer-events: none; }
|
|
49
|
+
.searchIcon { flex: none; color: var(--text-muted); }
|
|
50
|
+
.search { min-width: 0; height: 100%; flex: 1; padding: 0; border: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); letter-spacing: var(--tracking-body); outline: none; }
|
|
51
|
+
.search::placeholder { color: var(--text-muted); }
|
|
52
|
+
.clear { display: grid; width: 24px; height: 24px; flex: none; place-items: center; padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round; background: color-mix(in oklab, var(--foreground) 8%, transparent); color: var(--text-secondary); cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
|
53
|
+
/* Sits over the shared chevron, so the chevron closes the list. */
|
|
54
|
+
.closeHit { width: 36px; height: 36px; flex: none; padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
55
|
+
@media (hover: hover) and (pointer: fine) {
|
|
56
|
+
.closeHit:hover, .clear:hover { background: color-mix(in oklab, var(--foreground) 8%, transparent); }
|
|
57
|
+
.clear:hover { color: var(--foreground); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* The list hangs under the lid at its final width, so rows never reflow while the surface springs open. */
|
|
61
|
+
.listFace { position: absolute; top: calc(var(--pi-h) - 2px); left: 0; box-sizing: border-box; width: var(--pi-panel-w); padding: 0 6px 6px; }
|
|
62
|
+
.listFace::before { content: ""; position: absolute; top: 0; right: 14px; left: 14px; height: 1px; background: var(--border-subtle); }
|
|
63
|
+
.listFace[inert] { pointer-events: none; }
|
|
64
|
+
.scroll { max-height: 280px; overflow-y: auto; overscroll-behavior: contain; padding-top: 6px; scrollbar-width: thin; }
|
|
65
|
+
.options { position: relative; display: grid; gap: 2px; }
|
|
66
|
+
.highlight { position: absolute; top: 0; right: 0; left: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: color-mix(in oklab, var(--foreground) 6.5%, transparent); pointer-events: none; }
|
|
67
|
+
.group { display: grid; gap: 2px; }
|
|
68
|
+
.group + .group { margin-top: 4px; }
|
|
69
|
+
.groupLabel { padding: 8px 10px 4px; color: var(--text-muted); font-size: var(--text-xs); }
|
|
70
|
+
.option { position: relative; display: flex; min-height: 36px; box-sizing: border-box; align-items: center; gap: 10px; padding: 0 10px; border-radius: var(--radius-control); corner-shape: var(--corner-shape); color: var(--text-secondary); cursor: pointer; font-size: var(--text-sm); user-select: none; -webkit-user-select: none; transition: color var(--duration-fast) var(--ease-standard); }
|
|
71
|
+
.option[data-active], .option[aria-selected="true"] { color: var(--foreground); }
|
|
72
|
+
.name { min-width: 0; flex: 1; overflow: hidden; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
73
|
+
.meta { flex: none; color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
|
|
74
|
+
.check { display: grid; width: 16px; height: 16px; flex: none; place-items: center; color: var(--foreground); opacity: 0; transform: scale(.6); transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-spring) var(--ease-spring); }
|
|
75
|
+
.check[data-on] { opacity: 1; transform: none; }
|
|
76
|
+
.empty { margin: 0; padding: 12px 10px 14px; color: var(--text-muted); font-size: var(--text-sm); }
|
|
77
|
+
|
|
78
|
+
/* The number and its guide share one box. The guide paints the typed part invisibly, then the rest of the example faintly after it. */
|
|
79
|
+
.numberWrap { position: relative; display: flex; min-width: 0; flex: 1; align-items: center; }
|
|
80
|
+
.number, .guide { box-sizing: border-box; padding: 0 36px 0 12px; font-family: inherit; font-size: var(--text-sm); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-body); line-height: var(--leading-body); }
|
|
81
|
+
.number { position: relative; z-index: 1; width: 100%; height: 100%; min-width: 0; margin: 0; border: 0; border-radius: 0 var(--radius-control) var(--radius-control) 0; corner-shape: var(--corner-shape); background: transparent; color: var(--foreground); outline: none; }
|
|
82
|
+
.number:disabled { cursor: not-allowed; }
|
|
83
|
+
.guide { position: absolute; inset: 0; display: flex; align-items: center; overflow: hidden; color: color-mix(in oklab, var(--text-muted) 72%, transparent); pointer-events: none; white-space: pre; }
|
|
84
|
+
.guideTyped { visibility: hidden; }
|
|
85
|
+
.valid { position: absolute; z-index: 1; top: 50%; right: 12px; display: grid; width: 18px; height: 18px; margin-top: -9px; place-items: center; color: var(--success); pointer-events: none; }
|
|
86
|
+
|
|
87
|
+
/* The row's space is padding inside the clipped slot, so the height animates from a true zero. */
|
|
88
|
+
.messageSlot { position: relative; display: block; overflow: hidden; }
|
|
89
|
+
.hint, .error { display: block; padding-top: var(--space-related); font-size: var(--text-xs); line-height: var(--leading-body); }
|
|
90
|
+
.hint { color: var(--text-muted); }
|
|
91
|
+
.error { color: var(--danger); }
|
|
92
|
+
|
|
93
|
+
.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
|
94
|
+
|
|
95
|
+
@media (prefers-reduced-motion: reduce) {
|
|
96
|
+
.shape, .shape::after, .check, .slot, .control { transition-duration: 0ms; }
|
|
97
|
+
}
|
|
98
|
+
@media (prefers-contrast: more) { .shape::after { border-color: var(--border-strong); } }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
|
+
export declare const Popover: import("react").FC<PopoverPrimitive.PopoverProps>;
|
|
3
|
+
export declare const PopoverClose: import("react").ForwardRefExoticComponent<PopoverPrimitive.PopoverCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
4
|
+
/** The trigger anchors the panel, so it opts out of press-scale: a scaled rect measured on open would shift the panel as the trigger springs back. */
|
|
5
|
+
export declare const PopoverTrigger: import("react").ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
export declare const PopoverContent: import("react").ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { ESCAPE_LAYER_ATTRIBUTE, layerEscape } from "../../lib/escape.js";
|
|
6
|
+
import styles from "./popover.module.css";
|
|
7
|
+
export const Popover = PopoverPrimitive.Root;
|
|
8
|
+
export const PopoverClose = PopoverPrimitive.Close;
|
|
9
|
+
/** The trigger anchors the panel, so it opts out of press-scale: a scaled rect measured on open would shift the panel as the trigger springs back. */
|
|
10
|
+
export const PopoverTrigger = forwardRef(function PopoverTrigger({ className, ...props }, ref) {
|
|
11
|
+
return _jsx(PopoverPrimitive.Trigger, { ...props, ref: ref, className: [styles.anchor, className].filter(Boolean).join(" ") });
|
|
12
|
+
});
|
|
13
|
+
PopoverTrigger.displayName = "PopoverTrigger";
|
|
14
|
+
export const PopoverContent = forwardRef(function PopoverContent({ className, align = "start", sideOffset = 6, collisionPadding = 10, onEscapeKeyDown, ...props }, ref) {
|
|
15
|
+
return _jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ...props, [ESCAPE_LAYER_ATTRIBUTE]: "",
|
|
16
|
+
// Escape inside belongs to an open list, calendar or question first (lib/escape.ts).
|
|
17
|
+
onEscapeKeyDown: layerEscape(onEscapeKeyDown), ref: ref, align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: [styles.content, className].filter(Boolean).join(" ") }) });
|
|
18
|
+
});
|
|
19
|
+
PopoverContent.displayName = "PopoverContent";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Press feedback on the anchor stays in color. Radix measures the trigger when the panel opens, so a scaled trigger would move the panel. */
|
|
2
|
+
.anchor:active:not(:disabled) { transform: none; }
|
|
3
|
+
.content {
|
|
4
|
+
--popover-x: 0px;
|
|
5
|
+
--popover-y: -5px;
|
|
6
|
+
z-index: 80;
|
|
7
|
+
min-width: 12rem;
|
|
8
|
+
max-width: min(22rem, calc(100vw - 20px));
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
border: 1px solid var(--border);
|
|
11
|
+
border-radius: var(--radius-container);
|
|
12
|
+
background: var(--surface-raised);
|
|
13
|
+
color: var(--foreground);
|
|
14
|
+
box-shadow: var(--shadow-floating);
|
|
15
|
+
transform-origin: var(--radix-popover-content-transform-origin);
|
|
16
|
+
transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring);
|
|
17
|
+
}
|
|
18
|
+
/* The panel starts a few pixels toward its trigger and settles on a spring; it leaves faster than it arrives.
|
|
19
|
+
Transitions instead of keyframes, so a close that interrupts the open, or a reopen during the close, reverses from where the panel is. */
|
|
20
|
+
.content[data-side="top"] { --popover-y: 5px; }
|
|
21
|
+
.content[data-side="left"] { --popover-x: 5px; --popover-y: 0px; }
|
|
22
|
+
.content[data-side="right"] { --popover-x: -5px; --popover-y: 0px; }
|
|
23
|
+
@starting-style {
|
|
24
|
+
.content[data-state="open"] { opacity: 0; transform: translate(var(--popover-x), var(--popover-y)) scale(.97); }
|
|
25
|
+
}
|
|
26
|
+
/* Radix unmounts when the exit animation ends, so a no-op keyframe times the exit while the transition does the visual work. */
|
|
27
|
+
.content[data-state="closed"] { opacity: 0; transform: translate(calc(var(--popover-x) * .5), calc(var(--popover-y) * .5)) scale(.98); transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); animation: popover-exit var(--duration-fast) linear both; pointer-events: none; }
|
|
28
|
+
.content:focus { outline: none; }
|
|
29
|
+
@keyframes popover-exit { to { --popover-exit: 1; } }
|
|
30
|
+
@media (prefers-reduced-motion: reduce) {
|
|
31
|
+
.content, .content[data-state="closed"] { transform: none; transition: opacity var(--duration-instant) var(--ease-standard); }
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
2
|
+
value?: number;
|
|
3
|
+
max?: number;
|
|
4
|
+
label?: string;
|
|
5
|
+
showValue?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function Progress({ value, max, label, showValue, className, ...props }: ProgressProps): import("react").JSX.Element;
|
|
8
|
+
export default Progress;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { AnimatePresence, animate, motion, useIsPresent, useMotionValue, useReducedMotion, useTransform } from "motion/react";
|
|
5
|
+
import { Check } from "lucide-react";
|
|
6
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
7
|
+
import styles from "./progress.module.css";
|
|
8
|
+
const exitFast = { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] };
|
|
9
|
+
const textIn = { opacity: 0, y: "0.3em", filter: `blur(${motionTokens.blur.soft}px)` };
|
|
10
|
+
const textOut = { opacity: 0, y: "-0.3em", filter: `blur(${motionTokens.blur.subtle}px)`, transition: exitFast };
|
|
11
|
+
const iconIn = { opacity: 0, scale: .6, filter: `blur(${motionTokens.blur.subtle}px)` };
|
|
12
|
+
const shown = { opacity: 1, y: "0em", scale: 1, filter: "blur(0px)" };
|
|
13
|
+
const fadeOut = { opacity: 0, transition: { duration: motionTokens.duration.instant } };
|
|
14
|
+
/** Outgoing copies are hidden from assistive tech while they fade. */
|
|
15
|
+
function Swap(props) {
|
|
16
|
+
const present = useIsPresent();
|
|
17
|
+
return _jsx(motion.span, { ...props, "aria-hidden": present ? props["aria-hidden"] : true });
|
|
18
|
+
}
|
|
19
|
+
export function Progress({ value = 0, max = 100, label, showValue = false, className, ...props }) {
|
|
20
|
+
const reduce = useReducedMotion();
|
|
21
|
+
const safeMax = max > 0 ? max : 100;
|
|
22
|
+
const safeValue = Math.min(Math.max(value, 0), safeMax);
|
|
23
|
+
const percentage = Math.round((safeValue / safeMax) * 100);
|
|
24
|
+
const complete = percentage >= 100;
|
|
25
|
+
// One spring drives both the fill and the counted label, so the number always matches the bar.
|
|
26
|
+
const progress = useMotionValue(percentage);
|
|
27
|
+
// The fill slides in from the left instead of scaling, so its rounded end keeps its shape at every value.
|
|
28
|
+
const x = useTransform(progress, latest => `${Math.min(Math.max(latest, 0), 100) - 100}%`);
|
|
29
|
+
const counted = useTransform(progress, latest => `${Math.round(Math.min(Math.max(latest, 0), 100))}%`);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (reduce) {
|
|
32
|
+
progress.jump(percentage);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const controls = animate(progress, percentage, motionTokens.spring.smooth);
|
|
36
|
+
return () => controls.stop();
|
|
37
|
+
}, [percentage, progress, reduce]);
|
|
38
|
+
const enter = reduce ? { duration: motionTokens.duration.instant } : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] };
|
|
39
|
+
const classes = [styles.progress, className].filter(Boolean).join(" ");
|
|
40
|
+
return _jsxs("div", { ...props, className: classes, "data-complete": complete ? "" : undefined, role: "progressbar", "aria-label": label ?? "Progress", "aria-valuemin": 0, "aria-valuemax": safeMax, "aria-valuenow": safeValue, "aria-valuetext": `${percentage}%`, children: [(label || showValue) ? _jsxs("div", { className: styles.meta, children: [label ? _jsx("span", { className: styles.label, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(Swap, { className: styles.line, initial: reduce ? { opacity: 0 } : textIn, animate: shown, exit: reduce ? fadeOut : textOut, transition: enter, children: label }, label) }) }) : _jsx("span", {}), showValue ? _jsxs("span", { className: styles.value, children: [_jsx(AnimatePresence, { initial: false, children: complete && _jsx(motion.span, { className: styles.done, initial: reduce ? { opacity: 0 } : iconIn, animate: shown, exit: reduce ? fadeOut : { ...iconIn, transition: exitFast }, transition: reduce ? enter : { ...motionTokens.spring.snappy, delay: .24 }, children: _jsx(Check, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }, "done") }), _jsx(motion.span, { className: styles.count, children: counted })] }) : null] }) : null, _jsx("div", { className: styles.track, children: _jsx(motion.span, { className: styles.fill, style: { x } }) })] });
|
|
41
|
+
}
|
|
42
|
+
export default Progress;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.progress { width: 100%; color: var(--text-secondary); font-size: var(--text-sm); } .meta { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 8px; line-height: var(--leading-body); } .label { position: relative; min-width: 0; } .line { display: block; } .value { display: inline-flex; flex: none; align-items: center; gap: 4px; color: var(--text-muted); font-variant-numeric: tabular-nums; } .count { display: inline-grid; justify-items: end; } /* A hidden "100%" row reserves the widest count, so the check never shifts as the number gains a digit. */ .count::before { content: "100%"; height: 0; overflow: hidden; visibility: hidden; } .done { display: grid; place-items: center; color: var(--success); } .track { height: 7px; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-muted); } .fill { display: block; width: 100%; height: 100%; border-radius: inherit; corner-shape: inherit; background: var(--accent); transition: background-color var(--duration-standard) var(--ease-standard); } .progress[data-complete] .fill { background: var(--success); transition-delay: 200ms; } @media (prefers-reduced-motion: reduce) { .fill, .progress[data-complete] .fill { transition: none; } }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface RadioCardOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
/** One or two short lines under the label. */
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
/** A price, estimate, or other value. Sits at the end of a list row, or under the text in a grid card. */
|
|
8
|
+
meta?: ReactNode;
|
|
9
|
+
/** Plain decorative icon beside the label. */
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Short reason shown in place of the description when the option is disabled. */
|
|
13
|
+
disabledReason?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Selectable option cards for choices that need more than a label: plans, shipping speeds, regions. One selection ring
|
|
17
|
+
* glides from card to card on a spring, so the eye follows the change. Behaves as a native radio group: one tab stop,
|
|
18
|
+
* arrow keys move and select, and a hidden input carries the value in forms.
|
|
19
|
+
*/
|
|
20
|
+
export interface RadioCardsProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
|
|
21
|
+
options: RadioCardOption[];
|
|
22
|
+
value?: string | null;
|
|
23
|
+
defaultValue?: string | null;
|
|
24
|
+
onValueChange?: (value: string) => void;
|
|
25
|
+
/** "grid" places cards in responsive columns; "list" stacks full width rows. */
|
|
26
|
+
layout?: "grid" | "list";
|
|
27
|
+
/** Narrowest a grid column may get before the grid drops a column, in px. */
|
|
28
|
+
minColumnWidth?: number;
|
|
29
|
+
/** Form field name. Renders a hidden input with the selected value. */
|
|
30
|
+
name?: string;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare const RadioCards: import("react").ForwardRefExoticComponent<RadioCardsProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
35
|
+
export default RadioCards;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useCallback, useId, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { animate, motion, useMotionValue, useReducedMotion } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import styles from "./radio-cards.module.css";
|
|
7
|
+
const standard = [...motionTokens.ease.standard];
|
|
8
|
+
const physical = (visualDuration, bounce) => {
|
|
9
|
+
const root = 2 * Math.PI / (visualDuration * 1.2);
|
|
10
|
+
return { type: "spring", stiffness: root * root, damping: 2 * (1 - bounce) * root, mass: 1 };
|
|
11
|
+
};
|
|
12
|
+
const GLIDE = physical(.42, .14);
|
|
13
|
+
const subscribe = () => () => { };
|
|
14
|
+
function useReducedFlag() {
|
|
15
|
+
const hydrated = useSyncExternalStore(subscribe, () => true, () => false);
|
|
16
|
+
return !!useReducedMotion() && hydrated;
|
|
17
|
+
}
|
|
18
|
+
export const RadioCards = forwardRef(function RadioCards({ options, value, defaultValue = null, onValueChange, layout = "grid", minColumnWidth = 180, name, required, disabled = false, className, style, ...rest }, forwardedRef) {
|
|
19
|
+
const reduced = useReducedFlag();
|
|
20
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
21
|
+
const rootRef = useRef(null);
|
|
22
|
+
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
23
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
24
|
+
const selected = value !== undefined ? value : internal;
|
|
25
|
+
const selectedIndex = options.findIndex(option => option.value === selected);
|
|
26
|
+
const usable = (option) => !disabled && !option.disabled;
|
|
27
|
+
const tabStop = selectedIndex >= 0 && usable(options[selectedIndex]) ? selectedIndex : options.findIndex(usable);
|
|
28
|
+
const select = (next) => {
|
|
29
|
+
if (next === selected)
|
|
30
|
+
return;
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
setInternal(next);
|
|
33
|
+
onValueChange?.(next);
|
|
34
|
+
};
|
|
35
|
+
/* The ring: one element that springs to the selected card's box. At rest it follows layout changes exactly. */
|
|
36
|
+
const x = useMotionValue(0), y = useMotionValue(0), w = useMotionValue(0), h = useMotionValue(0), o = useMotionValue(0);
|
|
37
|
+
const placed = useRef(false);
|
|
38
|
+
const place = useCallback((spring) => {
|
|
39
|
+
const node = selectedIndex < 0 ? null : rootRef.current?.querySelector(`[data-card="${selectedIndex}"]`);
|
|
40
|
+
if (!node) {
|
|
41
|
+
o.set(0);
|
|
42
|
+
placed.current = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const box = [node.offsetLeft, node.offsetTop, node.offsetWidth, node.offsetHeight];
|
|
46
|
+
if (!spring || !placed.current || reduced) {
|
|
47
|
+
x.jump(box[0]);
|
|
48
|
+
y.jump(box[1]);
|
|
49
|
+
w.jump(box[2]);
|
|
50
|
+
h.jump(box[3]);
|
|
51
|
+
if (!placed.current && !reduced && spring) {
|
|
52
|
+
o.jump(0);
|
|
53
|
+
animate(o, 1, { duration: motionTokens.duration.fast, ease: standard });
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
o.jump(1);
|
|
57
|
+
placed.current = true;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
animate(x, box[0], GLIDE);
|
|
61
|
+
animate(y, box[1], GLIDE);
|
|
62
|
+
animate(w, box[2], GLIDE);
|
|
63
|
+
animate(h, box[3], GLIDE);
|
|
64
|
+
o.set(1);
|
|
65
|
+
}, [h, o, reduced, selectedIndex, w, x, y]);
|
|
66
|
+
const placeRef = useRef(place);
|
|
67
|
+
useLayoutEffect(() => { placeRef.current = place; place(true); }, [place]);
|
|
68
|
+
// A resize only snaps the ring to the new layout; it never replays the glide.
|
|
69
|
+
useLayoutEffect(() => {
|
|
70
|
+
const root = rootRef.current;
|
|
71
|
+
if (!root || typeof ResizeObserver === "undefined")
|
|
72
|
+
return;
|
|
73
|
+
const observer = new ResizeObserver(() => placeRef.current(false));
|
|
74
|
+
observer.observe(root);
|
|
75
|
+
return () => observer.disconnect();
|
|
76
|
+
}, []);
|
|
77
|
+
const cards = () => Array.from(rootRef.current?.querySelectorAll("[data-card]") ?? []);
|
|
78
|
+
const onKeyDown = (event, index) => {
|
|
79
|
+
const step = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 0;
|
|
80
|
+
if (event.key === " " || event.key === "Enter") {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
if (usable(options[index]))
|
|
83
|
+
select(options[index].value);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!step && event.key !== "Home" && event.key !== "End")
|
|
87
|
+
return;
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
const count = options.length;
|
|
90
|
+
const rtl = step && (event.key === "ArrowLeft" || event.key === "ArrowRight") && getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
|
|
91
|
+
let at = event.key === "Home" ? -1 : event.key === "End" ? count : index;
|
|
92
|
+
const dir = event.key === "Home" ? 1 : event.key === "End" ? -1 : step * rtl;
|
|
93
|
+
for (let tries = 0; tries < count; tries++) {
|
|
94
|
+
at = (at + dir + count) % count;
|
|
95
|
+
if (usable(options[at])) {
|
|
96
|
+
cards()[at]?.focus();
|
|
97
|
+
select(options[at].value);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return _jsxs("div", { ref: rootRef, role: "radiogroup", "aria-disabled": disabled || undefined, "aria-required": required || undefined, className: [styles.root, className].filter(Boolean).join(" "), "data-layout": layout, style: { "--min-column": `${minColumnWidth}px`, ...style }, ...rest, children: [_jsx(motion.span, { className: styles.ring, style: { x, y, width: w, height: h, opacity: o }, "aria-hidden": "true" }), options.map((option, index) => {
|
|
103
|
+
const checked = index === selectedIndex, off = !usable(option);
|
|
104
|
+
const labelId = `${uid}-${index}-label`, descriptionId = `${uid}-${index}-description`;
|
|
105
|
+
const description = off && option.disabledReason ? option.disabledReason : option.description;
|
|
106
|
+
return _jsxs("div", { role: "radio", "data-card": index, className: styles.card, "aria-checked": checked, "aria-disabled": off || undefined, "aria-labelledby": labelId, "aria-describedby": description ? descriptionId : undefined, tabIndex: index === tabStop ? 0 : -1, "data-checked": checked || undefined, onClick: () => { if (!off)
|
|
107
|
+
select(option.value); }, onKeyDown: event => onKeyDown(event, index), children: [_jsx("span", { className: styles.indicator, "aria-hidden": "true", children: _jsx(motion.span, { className: styles.indicatorDot, initial: false, animate: { scale: checked ? 1 : 0 }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy }) }), _jsxs("span", { className: styles.body, children: [_jsxs("span", { id: labelId, className: styles.label, children: [option.icon && _jsx("span", { className: styles.icon, children: option.icon }), _jsx("span", { className: styles.labelText, children: option.label })] }), description && _jsx("span", { id: descriptionId, className: styles.description, children: description })] }), option.meta && _jsx("span", { className: styles.meta, children: option.meta })] }, option.value);
|
|
108
|
+
}), name && _jsx("input", { type: "hidden", name: name, value: selected ?? "", required: required })] });
|
|
109
|
+
});
|
|
110
|
+
export default RadioCards;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: grid;
|
|
4
|
+
gap: 10px;
|
|
5
|
+
color: var(--foreground);
|
|
6
|
+
font-family: var(--font-body);
|
|
7
|
+
font-size: var(--text-sm);
|
|
8
|
+
letter-spacing: var(--tracking-body);
|
|
9
|
+
line-height: var(--leading-body);
|
|
10
|
+
}
|
|
11
|
+
.root *, .root *::before, .root *::after { box-sizing: border-box; }
|
|
12
|
+
.root[data-layout="grid"] { grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--min-column, 180px)), 1fr)); }
|
|
13
|
+
.root[data-layout="list"] { grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
|
14
|
+
|
|
15
|
+
/* The one moving part: a ring that springs from card to card. It sits over the card border so the card itself never changes size. */
|
|
16
|
+
.ring {
|
|
17
|
+
position: absolute;
|
|
18
|
+
z-index: 1;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
border: 1.5px solid var(--control-on);
|
|
22
|
+
border-radius: var(--radius-control); corner-shape: var(--corner-shape);
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.card {
|
|
27
|
+
position: relative;
|
|
28
|
+
display: grid;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
align-items: start;
|
|
31
|
+
gap: 4px 12px;
|
|
32
|
+
padding: 14px 16px;
|
|
33
|
+
border: 1px solid var(--border);
|
|
34
|
+
border-radius: var(--radius-control); corner-shape: var(--corner-shape);
|
|
35
|
+
background: var(--surface);
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
user-select: none;
|
|
38
|
+
-webkit-tap-highlight-color: transparent;
|
|
39
|
+
transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard);
|
|
40
|
+
}
|
|
41
|
+
/* The body row takes any spare height, so prices sit on one baseline across a row even when a description wraps. */
|
|
42
|
+
.root[data-layout="grid"] .card { grid-template-areas: "body indicator" "meta meta"; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: minmax(0, 1fr) auto; }
|
|
43
|
+
.root[data-layout="list"] .card { grid-template-areas: "indicator body meta"; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; padding: 13px 16px 13px 14px; }
|
|
44
|
+
.card[data-checked] { background: color-mix(in oklab, var(--control-on) 4%, var(--surface)); }
|
|
45
|
+
.card[aria-disabled="true"] { background: var(--surface-muted); cursor: not-allowed; }
|
|
46
|
+
.card[aria-disabled="true"] :is(.label, .meta) { color: var(--text-muted); }
|
|
47
|
+
|
|
48
|
+
.indicator {
|
|
49
|
+
display: grid;
|
|
50
|
+
width: 18px;
|
|
51
|
+
height: 18px;
|
|
52
|
+
flex: none;
|
|
53
|
+
place-items: center;
|
|
54
|
+
grid-area: indicator;
|
|
55
|
+
border: 1.5px solid var(--border-strong);
|
|
56
|
+
border-radius: 50%; corner-shape: round;
|
|
57
|
+
transition: border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard);
|
|
58
|
+
}
|
|
59
|
+
.root[data-layout="grid"] .indicator { margin-top: 1px; }
|
|
60
|
+
.card[data-checked] .indicator { border-color: var(--control-on); background: var(--control-on); }
|
|
61
|
+
.indicatorDot { width: 6px; height: 6px; border-radius: 50%; corner-shape: round; background: var(--control-glyph); }
|
|
62
|
+
.card[aria-disabled="true"] .indicator { border-color: var(--border); }
|
|
63
|
+
|
|
64
|
+
.body { display: grid; min-width: 0; gap: 2px; grid-area: body; }
|
|
65
|
+
.label { display: flex; min-width: 0; align-items: center; gap: 8px; font-weight: 500; }
|
|
66
|
+
.labelText { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
67
|
+
.icon { display: grid; width: 18px; height: 18px; flex: none; place-items: center; color: var(--text-secondary); }
|
|
68
|
+
.icon > svg { width: 18px; height: 18px; }
|
|
69
|
+
.card[data-checked] .icon { color: var(--foreground); }
|
|
70
|
+
.description { color: var(--text-secondary); font-size: var(--text-xs); line-height: var(--leading-body); text-wrap: pretty; }
|
|
71
|
+
|
|
72
|
+
.meta { grid-area: meta; color: var(--foreground); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
73
|
+
.root[data-layout="grid"] .meta { margin-top: 10px; font-size: var(--text-lg); }
|
|
74
|
+
.root[data-layout="list"] .meta { font-weight: 500; }
|
|
75
|
+
|
|
76
|
+
@media (hover: hover) and (pointer: fine) {
|
|
77
|
+
.card:not([data-checked]):not([aria-disabled="true"]):hover { border-color: var(--border-strong); }
|
|
78
|
+
}
|
|
79
|
+
.card:not([data-checked]):not([aria-disabled="true"]):active { background: var(--surface-muted); }
|
|
80
|
+
.card:focus-visible:not([data-checked]) { border-color: var(--border-strong); background: var(--surface-muted); }
|
|
81
|
+
|
|
82
|
+
@media (prefers-reduced-motion: reduce) {
|
|
83
|
+
.card, .indicator { transition: none; }
|
|
84
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RadioGroupProps {
|
|
2
|
+
label: string;
|
|
3
|
+
options: {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}[];
|
|
8
|
+
value: string;
|
|
9
|
+
onValueChange: (value: string) => void;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
/** One highlight travels to the chosen row while the new dot springs in and the old one shrinks away, so a change reads as a single physical move. Arrow keys take the same path. Rows and text never resize. */
|
|
13
|
+
export declare function RadioGroup({ label, options, value, onValueChange, name }: RadioGroupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useLayoutEffect, useRef } from "react";
|
|
4
|
+
import { animate, motion, useReducedMotion } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import styles from "./radio-group.module.css";
|
|
7
|
+
/** Sizes the highlight to the chosen row. A new choice glides on the morph spring; the first paint and resizes place it at once. */
|
|
8
|
+
function place(highlight, row, at, glide) {
|
|
9
|
+
const next = row ? `${row.offsetTop} ${row.offsetHeight}` : "";
|
|
10
|
+
if (!highlight || next === at.current)
|
|
11
|
+
return;
|
|
12
|
+
const visible = at.current !== "";
|
|
13
|
+
at.current = next;
|
|
14
|
+
if (!row) {
|
|
15
|
+
animate(highlight, { opacity: 0 }, { duration: 0 });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const target = { y: row.offsetTop, height: row.offsetHeight, opacity: 1 };
|
|
19
|
+
if (glide && visible) {
|
|
20
|
+
animate(highlight, target, { ...motionTokens.spring.morph, opacity: { duration: 0 } });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Written to the element too, so the paint that drops the server fallback already shows the highlight in place.
|
|
24
|
+
Object.assign(highlight.style, { transform: `translateY(${target.y}px)`, height: `${target.height}px`, opacity: "1" });
|
|
25
|
+
animate(highlight, target, { duration: 0 });
|
|
26
|
+
}
|
|
27
|
+
/** One highlight travels to the chosen row while the new dot springs in and the old one shrinks away, so a change reads as a single physical move. Arrow keys take the same path. Rows and text never resize. */
|
|
28
|
+
export function RadioGroup({ label, options, value, onValueChange, name }) {
|
|
29
|
+
const id = useId();
|
|
30
|
+
const reduced = useReducedMotion();
|
|
31
|
+
const listRef = useRef(null);
|
|
32
|
+
const highlightRef = useRef(null);
|
|
33
|
+
const rows = useRef([]);
|
|
34
|
+
const shown = useRef(null);
|
|
35
|
+
const at = useRef("");
|
|
36
|
+
const selected = options.findIndex(option => option.value === value);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
place(highlightRef.current, rows.current[selected], at, shown.current !== null && shown.current !== selected && !reduced);
|
|
39
|
+
shown.current = selected;
|
|
40
|
+
listRef.current?.setAttribute("data-ready", "");
|
|
41
|
+
}, [selected, reduced]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const list = listRef.current;
|
|
44
|
+
if (!list || typeof ResizeObserver === "undefined")
|
|
45
|
+
return;
|
|
46
|
+
const observer = new ResizeObserver(() => place(highlightRef.current, rows.current[shown.current ?? -1], at, false));
|
|
47
|
+
observer.observe(list);
|
|
48
|
+
rows.current.forEach(row => row && observer.observe(row));
|
|
49
|
+
return () => observer.disconnect();
|
|
50
|
+
}, [options.length]);
|
|
51
|
+
return _jsxs("fieldset", { className: styles.group, children: [_jsx("legend", { children: label }), _jsxs("div", { ref: listRef, className: styles.options, children: [_jsx("span", { ref: highlightRef, className: styles.highlight, "aria-hidden": "true" }), options.map((option, index) => {
|
|
52
|
+
const checked = value === option.value;
|
|
53
|
+
return _jsxs("label", { className: styles.option, ref: node => { rows.current[index] = node; }, children: [_jsx("input", { type: "radio", name: name ?? id, value: option.value, checked: checked, onChange: () => onValueChange(option.value) }), _jsx("span", { className: styles.mark, "aria-hidden": "true", children: _jsx(motion.span, { className: styles.dot, initial: false, animate: checked ? { scale: 1, opacity: 1 } : { scale: .4, opacity: 0 }, transition: reduced ? { duration: 0 } : { ...motionTokens.spring.snappy, opacity: { duration: checked ? motionTokens.duration.fast : motionTokens.duration.instant } } }) }), _jsxs("span", { children: [_jsx("strong", { children: option.label }), option.description && _jsx("small", { children: option.description })] })] }, option.value);
|
|
54
|
+
})] })] });
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.group { display: grid; min-width: 0; margin: 0; border: 0; padding: 0; }
|
|
2
|
+
.group legend { margin-bottom: var(--space-related); padding: 0; font-size: var(--text-sm); font-weight: 500; }
|
|
3
|
+
/* One highlight travels between the rows: above their borders and fills, below their text, so it never crosses a label. */
|
|
4
|
+
.options { position: relative; display: grid; gap: var(--space-related); }
|
|
5
|
+
.highlight { position: absolute; top: 0; right: 0; left: 0; z-index: 1; box-sizing: border-box; border: 1px solid var(--control-on); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: color-mix(in oklab, var(--control-on) 5%, transparent); opacity: 0; pointer-events: none; }
|
|
6
|
+
/* Rows answer a press with color only; the mark is what moves. */
|
|
7
|
+
/* The mark sits centered on the left edge of the row, whether or not the option has a description. */
|
|
8
|
+
.option { position: relative; display: flex; align-items: center; gap: var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); padding: var(--space-group); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); }
|
|
9
|
+
@media (hover: hover) and (pointer: fine) { .option:hover { background: var(--surface-muted); } }
|
|
10
|
+
.option:active { background: var(--surface-muted); }
|
|
11
|
+
/* Until the highlight is placed (server paint or no script), the checked row carries the same look itself. */
|
|
12
|
+
.options:not([data-ready]) .option:has(input:checked) { border-color: var(--control-on); background: color-mix(in oklab, var(--control-on) 5%, transparent); }
|
|
13
|
+
.option input { position: absolute; opacity: 0; }
|
|
14
|
+
/* Rows are not stacking contexts, so their mark and copy rise above the highlight while their border and fill stay below it. */
|
|
15
|
+
.option > span { position: relative; z-index: 2; }
|
|
16
|
+
.mark { display: grid; width: 17px; height: 17px; flex: 0 0 auto; place-items: center; border: 1px solid var(--border-strong); border-radius: 50%; corner-shape: round; transition: border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), transform var(--duration-spring) var(--ease-spring); }
|
|
17
|
+
/* 7px keeps the dot on whole pixels inside the 15px interior of the mark. */
|
|
18
|
+
.mark::after, .dot { grid-area: 1 / 1; width: 7px; height: 7px; border-radius: 50%; corner-shape: round; background: var(--foreground); }
|
|
19
|
+
/* A faint dot previews the choice while the pointer is down, then the real dot springs in on release. */
|
|
20
|
+
.mark::after { content: ""; opacity: 0; transition: opacity var(--duration-instant) var(--ease-standard); }
|
|
21
|
+
.option:active:not(:has(input:checked)) .mark::after { opacity: .22; }
|
|
22
|
+
/* The mark alone answers a press: a quick dip, then a spring back. */
|
|
23
|
+
.option:active .mark { transform: scale(.88); transition: border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), transform 100ms var(--ease-standard); }
|
|
24
|
+
.option:has(input:checked) .mark { border-color: var(--control-on); background: var(--control-on); }
|
|
25
|
+
.option:has(input:checked) .dot { background: var(--control-glyph); }
|
|
26
|
+
.option strong, .option small { display: block; }
|
|
27
|
+
.option strong { color: var(--text-secondary); font-size: var(--text-sm); font-weight: 500; transition: color var(--duration-standard) var(--ease-standard); }
|
|
28
|
+
.option:has(input:checked) strong { color: var(--foreground); }
|
|
29
|
+
.option small { margin-top: 2px; color: var(--text-muted); font-size: var(--text-xs); }
|
|
30
|
+
@media (prefers-reduced-motion: reduce) { .option, .mark, .mark::after, .option strong { transition: none; } .option:active .mark { transform: none; } }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** One pane of a `ResizablePanels` group. It only carries configuration; the group renders it. */
|
|
3
|
+
export interface ResizablePanelProps {
|
|
4
|
+
/** Stable id, also used by the dividers' `aria-controls`. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Names the pane for its divider, for example "Folders". */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Starting share of the group. Shares are relative, so they need not add up to 100. */
|
|
9
|
+
defaultSize: number;
|
|
10
|
+
/** Width in px where the pane starts to resist. Its content never reflows below it; it clips and fades instead. */
|
|
11
|
+
minSize?: number;
|
|
12
|
+
maxSize?: number;
|
|
13
|
+
/** Dragging well past the minimum, or flicking toward the edge, snaps the pane closed. Its divider then shows a restore tab. */
|
|
14
|
+
collapsible?: boolean;
|
|
15
|
+
defaultCollapsed?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A horizontal split view whose panes people resize by dragging the dividers between them, the way a mail or code editor window works.
|
|
21
|
+
* Use it when people need to trade space between side-by-side content for the task at hand. Dividers follow the pointer 1:1, resist past
|
|
22
|
+
* a pane's limits, and snap collapsible panes closed. Each divider is a focusable separator: arrows resize, Home and End jump to the limits,
|
|
23
|
+
* Enter hides or restores a collapsible pane, and a double-click resets the layout. The group needs a definite height.
|
|
24
|
+
*/
|
|
25
|
+
export interface ResizablePanelsProps {
|
|
26
|
+
/** Names the group for assistive technology. */
|
|
27
|
+
label?: string;
|
|
28
|
+
/** `ResizablePanel` elements, in order. The set of panes should stay the same for the life of the group. */
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/** Called with each pane's share in percent after a resize settles. */
|
|
31
|
+
onLayoutChange?: (sizes: number[]) => void;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Carries a pane's configuration into `ResizablePanels`. Rendered on its own it shows its children unchanged. */
|
|
35
|
+
export declare function ResizablePanel({ children }: ResizablePanelProps): import("react").JSX.Element;
|
|
36
|
+
export declare function ResizablePanels({ label, children, onLayoutChange, className }: ResizablePanelsProps): import("react").JSX.Element;
|
|
37
|
+
export default ResizablePanels;
|