@delacour/react-native-ui 0.1.0-alpha.4 → 0.2.0-alpha.20261010061731
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +49 -5
- package/src/components/accordion/AGENTS.md +21 -9
- package/src/components/accordion/accordion-content.tsx +7 -4
- package/src/components/accordion/accordion-item.tsx +15 -13
- package/src/components/accordion/accordion.context.tsx +11 -5
- package/src/components/accordion/accordion.variants.test.ts +64 -3
- package/src/components/accordion/accordion.variants.ts +33 -9
- package/src/components/alert/AGENTS.md +102 -0
- package/src/components/alert/alert-action.tsx +17 -0
- package/src/components/alert/alert-close-button.tsx +50 -0
- package/src/components/alert/alert-content.tsx +15 -0
- package/src/components/alert/alert-description.tsx +16 -0
- package/src/components/alert/alert-glyphs.ts +25 -0
- package/src/components/alert/alert-indicator.tsx +40 -0
- package/src/components/alert/alert-title.tsx +18 -0
- package/src/components/alert/alert.context.tsx +69 -0
- package/src/components/alert/alert.tsx +160 -0
- package/src/components/alert/alert.types.ts +10 -0
- package/src/components/alert/alert.variants.test.ts +218 -0
- package/src/components/alert/alert.variants.ts +180 -0
- package/src/components/alert/index.ts +23 -0
- package/src/components/avatar/AGENTS.md +106 -0
- package/src/components/avatar/avatar-badge.tsx +51 -0
- package/src/components/avatar/avatar-group.tsx +139 -0
- package/src/components/avatar/avatar.context.tsx +93 -0
- package/src/components/avatar/avatar.tsx +201 -0
- package/src/components/avatar/avatar.utils.test.ts +174 -0
- package/src/components/avatar/avatar.utils.ts +141 -0
- package/src/components/avatar/avatar.variants.test.ts +251 -0
- package/src/components/avatar/avatar.variants.ts +270 -0
- package/src/components/avatar/index.ts +39 -0
- package/src/components/badge/AGENTS.md +10 -0
- package/src/components/badge/badge-close-button.tsx +2 -2
- package/src/components/badge/badge.context.tsx +2 -1
- package/src/components/badge/badge.tsx +13 -4
- package/src/components/badge/badge.variants.test.ts +26 -0
- package/src/components/badge/badge.variants.ts +21 -0
- package/src/components/bottom-sheet/AGENTS.md +186 -231
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
- package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
- package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
- package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
- package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
- package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
- package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
- package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
- package/src/components/bottom-sheet/index.ts +39 -17
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
- package/src/components/button/AGENTS.md +51 -11
- package/src/components/button/button-group.tsx +44 -8
- package/src/components/button/button.context.tsx +10 -0
- package/src/components/button/button.tsx +7 -1
- package/src/components/button/button.variants.test.ts +190 -0
- package/src/components/button/button.variants.ts +87 -6
- package/src/components/button/index.ts +2 -0
- package/src/components/calendar/AGENTS.md +142 -0
- package/src/components/calendar/calendar-caption.tsx +116 -0
- package/src/components/calendar/calendar-day.tsx +103 -0
- package/src/components/calendar/calendar-grid.tsx +183 -0
- package/src/components/calendar/calendar-header.tsx +34 -0
- package/src/components/calendar/calendar-nav.tsx +51 -0
- package/src/components/calendar/calendar-picker.tsx +128 -0
- package/src/components/calendar/calendar-weekdays.tsx +48 -0
- package/src/components/calendar/calendar.context.tsx +108 -0
- package/src/components/calendar/calendar.date.test.ts +456 -0
- package/src/components/calendar/calendar.date.ts +584 -0
- package/src/components/calendar/calendar.tsx +389 -0
- package/src/components/calendar/calendar.types.ts +87 -0
- package/src/components/calendar/calendar.variants.test.ts +363 -0
- package/src/components/calendar/calendar.variants.ts +426 -0
- package/src/components/calendar/index.ts +96 -0
- package/src/components/card/AGENTS.md +92 -0
- package/src/components/card/card-action.tsx +16 -0
- package/src/components/card/card-content.tsx +12 -0
- package/src/components/card/card-description.tsx +12 -0
- package/src/components/card/card-footer.tsx +30 -0
- package/src/components/card/card-header.tsx +45 -0
- package/src/components/card/card-title.tsx +25 -0
- package/src/components/card/card.context.tsx +64 -0
- package/src/components/card/card.tsx +100 -0
- package/src/components/card/card.types.ts +13 -0
- package/src/components/card/card.variants.test.ts +240 -0
- package/src/components/card/card.variants.ts +177 -0
- package/src/components/card/index.ts +15 -0
- package/src/components/chart/AGENTS.md +8 -0
- package/src/components/chart/chart.tsx +8 -1
- package/src/components/chip/AGENTS.md +113 -0
- package/src/components/chip/chip-close-button.tsx +48 -0
- package/src/components/chip/chip-end-content.tsx +15 -0
- package/src/components/chip/chip-label.tsx +21 -0
- package/src/components/chip/chip-start-content.tsx +16 -0
- package/src/components/chip/chip.context.tsx +66 -0
- package/src/components/chip/chip.tsx +268 -0
- package/src/components/chip/chip.types.ts +10 -0
- package/src/components/chip/chip.variants.test.ts +335 -0
- package/src/components/chip/chip.variants.ts +321 -0
- package/src/components/chip/index.ts +24 -0
- package/src/components/collapsible/AGENTS.md +97 -0
- package/src/components/collapsible/collapsible-content.tsx +97 -0
- package/src/components/collapsible/collapsible-description.tsx +17 -0
- package/src/components/collapsible/collapsible-indicator.tsx +92 -0
- package/src/components/collapsible/collapsible-title.tsx +17 -0
- package/src/components/collapsible/collapsible-trigger.tsx +133 -0
- package/src/components/collapsible/collapsible.context.tsx +91 -0
- package/src/components/collapsible/collapsible.tsx +167 -0
- package/src/components/collapsible/collapsible.types.ts +9 -0
- package/src/components/collapsible/collapsible.variants.test.ts +338 -0
- package/src/components/collapsible/collapsible.variants.ts +228 -0
- package/src/components/collapsible/index.ts +32 -0
- package/src/components/context-menu/AGENTS.md +103 -0
- package/src/components/context-menu/context-menu-content.tsx +64 -0
- package/src/components/context-menu/context-menu-item.tsx +22 -0
- package/src/components/context-menu/context-menu-preview.tsx +60 -0
- package/src/components/context-menu/context-menu-trigger.tsx +160 -0
- package/src/components/context-menu/context-menu.context.tsx +91 -0
- package/src/components/context-menu/context-menu.tsx +90 -0
- package/src/components/context-menu/context-menu.variants.test.ts +266 -0
- package/src/components/context-menu/context-menu.variants.ts +147 -0
- package/src/components/context-menu/index.ts +18 -0
- package/src/components/dialog/AGENTS.md +97 -0
- package/src/components/dialog/dialog-body.tsx +21 -0
- package/src/components/dialog/dialog-close.tsx +82 -0
- package/src/components/dialog/dialog-content.tsx +153 -0
- package/src/components/dialog/dialog-description.tsx +23 -0
- package/src/components/dialog/dialog-footer.tsx +32 -0
- package/src/components/dialog/dialog-header.tsx +20 -0
- package/src/components/dialog/dialog-title.tsx +42 -0
- package/src/components/dialog/dialog-trigger.tsx +54 -0
- package/src/components/dialog/dialog.context.tsx +90 -0
- package/src/components/dialog/dialog.tsx +119 -0
- package/src/components/dialog/dialog.variants.test.ts +224 -0
- package/src/components/dialog/dialog.variants.ts +173 -0
- package/src/components/dialog/index.ts +30 -0
- package/src/components/drawer/AGENTS.md +123 -0
- package/src/components/drawer/drawer-body.tsx +46 -0
- package/src/components/drawer/drawer-close.tsx +76 -0
- package/src/components/drawer/drawer-content.tsx +186 -0
- package/src/components/drawer/drawer-description.tsx +23 -0
- package/src/components/drawer/drawer-footer.tsx +21 -0
- package/src/components/drawer/drawer-header.tsx +42 -0
- package/src/components/drawer/drawer-title.tsx +35 -0
- package/src/components/drawer/drawer-trigger.tsx +54 -0
- package/src/components/drawer/drawer.context.tsx +114 -0
- package/src/components/drawer/drawer.tsx +119 -0
- package/src/components/drawer/drawer.variants.test.ts +392 -0
- package/src/components/drawer/drawer.variants.ts +244 -0
- package/src/components/drawer/index.ts +41 -0
- package/src/components/drawer/use-drawer-pan.ts +108 -0
- package/src/components/empty-state/AGENTS.md +90 -0
- package/src/components/empty-state/empty-state-content.tsx +17 -0
- package/src/components/empty-state/empty-state-description.tsx +12 -0
- package/src/components/empty-state/empty-state-header.tsx +17 -0
- package/src/components/empty-state/empty-state-media.tsx +56 -0
- package/src/components/empty-state/empty-state-title.tsx +18 -0
- package/src/components/empty-state/empty-state.context.tsx +65 -0
- package/src/components/empty-state/empty-state.tsx +84 -0
- package/src/components/empty-state/empty-state.types.ts +14 -0
- package/src/components/empty-state/empty-state.variants.test.ts +245 -0
- package/src/components/empty-state/empty-state.variants.ts +132 -0
- package/src/components/empty-state/index.ts +20 -0
- package/src/components/fab/AGENTS.md +119 -0
- package/src/components/fab/fab-action.tsx +118 -0
- package/src/components/fab/fab-group.tsx +214 -0
- package/src/components/fab/fab-label.tsx +18 -0
- package/src/components/fab/fab.context.tsx +125 -0
- package/src/components/fab/fab.tsx +156 -0
- package/src/components/fab/fab.types.ts +17 -0
- package/src/components/fab/fab.variants.test.ts +274 -0
- package/src/components/fab/fab.variants.ts +234 -0
- package/src/components/fab/index.ts +37 -0
- package/src/components/feedback/AGENTS.md +95 -0
- package/src/components/feedback/feedback-action.tsx +16 -0
- package/src/components/feedback/feedback-cancel.tsx +34 -0
- package/src/components/feedback/feedback-close.tsx +21 -0
- package/src/components/feedback/feedback-content.tsx +24 -0
- package/src/components/feedback/feedback-field.tsx +70 -0
- package/src/components/feedback/feedback-footer.tsx +21 -0
- package/src/components/feedback/feedback-panel.tsx +54 -0
- package/src/components/feedback/feedback-submit.tsx +74 -0
- package/src/components/feedback/feedback-title.tsx +34 -0
- package/src/components/feedback/feedback-trigger.tsx +20 -0
- package/src/components/feedback/feedback.context.tsx +73 -0
- package/src/components/feedback/feedback.tsx +135 -0
- package/src/components/feedback/feedback.variants.test.ts +185 -0
- package/src/components/feedback/feedback.variants.ts +128 -0
- package/src/components/feedback/index.ts +24 -0
- package/src/components/field/AGENTS.md +19 -0
- package/src/components/field/field-label.tsx +19 -5
- package/src/components/field/field.context.tsx +17 -0
- package/src/components/field/field.tsx +15 -2
- package/src/components/field/field.variants.test.ts +24 -0
- package/src/components/field/field.variants.ts +20 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/input/AGENTS.md +5 -0
- package/src/components/input/input.variants.test.ts +32 -0
- package/src/components/input/input.variants.ts +13 -1
- package/src/components/item/AGENTS.md +92 -0
- package/src/components/item/index.ts +25 -0
- package/src/components/item/item-actions.tsx +28 -0
- package/src/components/item/item-content.tsx +15 -0
- package/src/components/item/item-description.tsx +12 -0
- package/src/components/item/item-footer.tsx +13 -0
- package/src/components/item/item-group.tsx +32 -0
- package/src/components/item/item-header.tsx +13 -0
- package/src/components/item/item-media.tsx +37 -0
- package/src/components/item/item-separator.tsx +13 -0
- package/src/components/item/item-title.tsx +17 -0
- package/src/components/item/item.context.tsx +66 -0
- package/src/components/item/item.tsx +218 -0
- package/src/components/item/item.types.ts +12 -0
- package/src/components/item/item.variants.test.ts +263 -0
- package/src/components/item/item.variants.ts +200 -0
- package/src/components/kpi/AGENTS.md +125 -0
- package/src/components/kpi/index.ts +45 -0
- package/src/components/kpi/kpi-action.tsx +12 -0
- package/src/components/kpi/kpi-content.tsx +35 -0
- package/src/components/kpi/kpi-footer.tsx +52 -0
- package/src/components/kpi/kpi-group.tsx +70 -0
- package/src/components/kpi/kpi-header.tsx +25 -0
- package/src/components/kpi/kpi-icon.tsx +28 -0
- package/src/components/kpi/kpi-sparkline.tsx +145 -0
- package/src/components/kpi/kpi-stat.tsx +20 -0
- package/src/components/kpi/kpi-title.tsx +19 -0
- package/src/components/kpi/kpi-trend.tsx +105 -0
- package/src/components/kpi/kpi-value.tsx +32 -0
- package/src/components/kpi/kpi.context.tsx +111 -0
- package/src/components/kpi/kpi.tsx +154 -0
- package/src/components/kpi/kpi.types.ts +9 -0
- package/src/components/kpi/kpi.variants.test.ts +428 -0
- package/src/components/kpi/kpi.variants.ts +347 -0
- package/src/components/label/AGENTS.md +89 -0
- package/src/components/label/index.ts +10 -0
- package/src/components/label/label.tsx +97 -0
- package/src/components/label/label.variants.test.ts +176 -0
- package/src/components/label/label.variants.ts +123 -0
- package/src/components/menu/AGENTS.md +176 -0
- package/src/components/menu/index.ts +44 -0
- package/src/components/menu/menu-background.tsx +17 -0
- package/src/components/menu/menu-checkbox-item.tsx +57 -0
- package/src/components/menu/menu-content.tsx +303 -0
- package/src/components/menu/menu-item.tsx +162 -0
- package/src/components/menu/menu-label.tsx +14 -0
- package/src/components/menu/menu-radio-group.tsx +50 -0
- package/src/components/menu/menu-radio-item.tsx +57 -0
- package/src/components/menu/menu-separator.tsx +11 -0
- package/src/components/menu/menu-sub-content.tsx +72 -0
- package/src/components/menu/menu-sub-trigger.tsx +50 -0
- package/src/components/menu/menu-sub.tsx +75 -0
- package/src/components/menu/menu-trigger.tsx +57 -0
- package/src/components/menu/menu.context.tsx +214 -0
- package/src/components/menu/menu.tsx +88 -0
- package/src/components/menu/menu.types.ts +33 -0
- package/src/components/menu/menu.variants.test.ts +400 -0
- package/src/components/menu/menu.variants.ts +353 -0
- package/src/components/meter/AGENTS.md +135 -0
- package/src/components/meter/index.ts +43 -0
- package/src/components/meter/meter-fill.tsx +19 -0
- package/src/components/meter/meter-header.tsx +17 -0
- package/src/components/meter/meter-label.tsx +18 -0
- package/src/components/meter/meter-output.tsx +42 -0
- package/src/components/meter/meter-segments.tsx +83 -0
- package/src/components/meter/meter-track.tsx +16 -0
- package/src/components/meter/meter.context.tsx +62 -0
- package/src/components/meter/meter.tsx +286 -0
- package/src/components/meter/meter.types.ts +30 -0
- package/src/components/meter/meter.variants.test.ts +430 -0
- package/src/components/meter/meter.variants.ts +347 -0
- package/src/components/overlay/AGENTS.md +89 -0
- package/src/components/overlay/index.ts +27 -0
- package/src/components/overlay/overlay-portal.tsx +84 -0
- package/src/components/overlay/overlay-presence.test.ts +46 -0
- package/src/components/overlay/overlay-presence.ts +49 -0
- package/src/components/overlay/overlay-registry.test.ts +96 -0
- package/src/components/overlay/overlay-registry.ts +99 -0
- package/src/components/overlay/overlay-scrim.tsx +49 -0
- package/src/components/overlay/overlay.context.tsx +48 -0
- package/src/components/overlay/overlay.tsx +118 -0
- package/src/components/overlay/overlay.variants.test.ts +37 -0
- package/src/components/overlay/overlay.variants.ts +33 -0
- package/src/components/overlay/use-overlay-back-handler.ts +34 -0
- package/src/components/overlay/use-overlay-presence.ts +87 -0
- package/src/components/popover/AGENTS.md +138 -0
- package/src/components/popover/index.ts +48 -0
- package/src/components/popover/popover-anchor.tsx +49 -0
- package/src/components/popover/popover-arrow.tsx +89 -0
- package/src/components/popover/popover-close.tsx +73 -0
- package/src/components/popover/popover-content.tsx +235 -0
- package/src/components/popover/popover-description.tsx +18 -0
- package/src/components/popover/popover-title.tsx +33 -0
- package/src/components/popover/popover-trigger.tsx +56 -0
- package/src/components/popover/popover.context.tsx +71 -0
- package/src/components/popover/popover.position.test.ts +354 -0
- package/src/components/popover/popover.position.ts +286 -0
- package/src/components/popover/popover.tsx +123 -0
- package/src/components/popover/popover.variants.test.ts +135 -0
- package/src/components/popover/popover.variants.ts +85 -0
- package/src/components/popover/use-anchor-measure.ts +76 -0
- package/src/components/popover/use-anchored-content.ts +219 -0
- package/src/components/pressable/AGENTS.md +11 -1
- package/src/components/pressable/pressable.tsx +7 -2
- package/src/components/pressable/pressable.variants.test.ts +29 -0
- package/src/components/pressable/pressable.variants.ts +17 -0
- package/src/components/progress/AGENTS.md +120 -0
- package/src/components/progress/index.ts +40 -0
- package/src/components/progress/progress-fill.tsx +152 -0
- package/src/components/progress/progress-header.tsx +19 -0
- package/src/components/progress/progress-label.tsx +27 -0
- package/src/components/progress/progress-output.tsx +47 -0
- package/src/components/progress/progress-track.tsx +38 -0
- package/src/components/progress/progress.context.tsx +79 -0
- package/src/components/progress/progress.tsx +171 -0
- package/src/components/progress/progress.types.ts +23 -0
- package/src/components/progress/progress.variants.test.ts +335 -0
- package/src/components/progress/progress.variants.ts +284 -0
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/provider/AGENTS.md +38 -25
- package/src/components/provider/provider.tsx +38 -11
- package/src/components/radio/AGENTS.md +8 -10
- package/src/components/radio/radio.variants.test.ts +5 -6
- package/src/components/radio/radio.variants.ts +6 -7
- package/src/components/rating/AGENTS.md +143 -0
- package/src/components/rating/index.ts +43 -0
- package/src/components/rating/rating-output.tsx +39 -0
- package/src/components/rating/rating-star.tsx +70 -0
- package/src/components/rating/rating-stars.tsx +219 -0
- package/src/components/rating/rating.context.tsx +79 -0
- package/src/components/rating/rating.tsx +163 -0
- package/src/components/rating/rating.variants.test.ts +347 -0
- package/src/components/rating/rating.variants.ts +354 -0
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
- package/src/components/skeleton/AGENTS.md +120 -0
- package/src/components/skeleton/index.ts +15 -0
- package/src/components/skeleton/skeleton-clock.ts +48 -0
- package/src/components/skeleton/skeleton-group.tsx +64 -0
- package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
- package/src/components/skeleton/skeleton.context.tsx +45 -0
- package/src/components/skeleton/skeleton.tsx +227 -0
- package/src/components/skeleton/skeleton.variants.test.ts +245 -0
- package/src/components/skeleton/skeleton.variants.ts +222 -0
- package/src/components/slide-button/AGENTS.md +122 -0
- package/src/components/slide-button/index.ts +43 -0
- package/src/components/slide-button/slide-button-label.tsx +70 -0
- package/src/components/slide-button/slide-button-thumb.tsx +93 -0
- package/src/components/slide-button/slide-button.context.tsx +76 -0
- package/src/components/slide-button/slide-button.tsx +457 -0
- package/src/components/slide-button/slide-button.variants.test.ts +288 -0
- package/src/components/slide-button/slide-button.variants.ts +324 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider-thumb.tsx +33 -1
- package/src/components/slider/slider.variants.test.ts +52 -0
- package/src/components/slider/slider.variants.ts +56 -2
- package/src/components/stack-card/AGENTS.md +169 -0
- package/src/components/stack-card/index.ts +37 -0
- package/src/components/stack-card/stack-card-action.tsx +66 -0
- package/src/components/stack-card/stack-card-actions.tsx +18 -0
- package/src/components/stack-card/stack-card-card.tsx +71 -0
- package/src/components/stack-card/stack-card-empty.tsx +30 -0
- package/src/components/stack-card/stack-card-slot.tsx +90 -0
- package/src/components/stack-card/stack-card-stamp.tsx +76 -0
- package/src/components/stack-card/stack-card.context.tsx +117 -0
- package/src/components/stack-card/stack-card.tsx +557 -0
- package/src/components/stack-card/stack-card.types.ts +26 -0
- package/src/components/stack-card/stack-card.variants.test.ts +355 -0
- package/src/components/stack-card/stack-card.variants.ts +360 -0
- package/src/components/steps/AGENTS.md +153 -0
- package/src/components/steps/index.ts +45 -0
- package/src/components/steps/steps-connector.tsx +31 -0
- package/src/components/steps/steps-description.tsx +26 -0
- package/src/components/steps/steps-indicator.tsx +106 -0
- package/src/components/steps/steps-item.tsx +225 -0
- package/src/components/steps/steps-panel.tsx +41 -0
- package/src/components/steps/steps-title.tsx +32 -0
- package/src/components/steps/steps-track.tsx +59 -0
- package/src/components/steps/steps.context.tsx +123 -0
- package/src/components/steps/steps.tsx +166 -0
- package/src/components/steps/steps.variants.test.ts +388 -0
- package/src/components/steps/steps.variants.ts +468 -0
- package/src/components/surface/AGENTS.md +82 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +56 -0
- package/src/components/surface/surface.tsx +87 -0
- package/src/components/surface/surface.variants.test.ts +227 -0
- package/src/components/surface/surface.variants.ts +165 -0
- package/src/components/swipe/AGENTS.md +140 -0
- package/src/components/swipe/index.ts +31 -0
- package/src/components/swipe/swipe-action.tsx +99 -0
- package/src/components/swipe/swipe-end.tsx +14 -0
- package/src/components/swipe/swipe-group.tsx +55 -0
- package/src/components/swipe/swipe-panel.tsx +55 -0
- package/src/components/swipe/swipe-start.tsx +15 -0
- package/src/components/swipe/swipe.context.tsx +135 -0
- package/src/components/swipe/swipe.tsx +393 -0
- package/src/components/swipe/swipe.types.ts +7 -0
- package/src/components/swipe/swipe.variants.test.ts +337 -0
- package/src/components/swipe/swipe.variants.ts +290 -0
- package/src/components/switch/switch.variants.ts +2 -2
- package/src/components/tabs/AGENTS.md +4 -3
- package/src/components/tabs/tabs.variants.test.ts +1 -1
- package/src/components/tabs/tabs.variants.ts +4 -6
- package/src/components/text/AGENTS.md +8 -2
- package/src/components/text/text.tsx +7 -0
- package/src/components/text/text.variants.test.ts +24 -1
- package/src/components/text/text.variants.ts +5 -0
- package/src/components/textarea/AGENTS.md +89 -0
- package/src/components/textarea/index.ts +16 -0
- package/src/components/textarea/textarea.tsx +157 -0
- package/src/components/textarea/textarea.variants.test.ts +219 -0
- package/src/components/textarea/textarea.variants.ts +171 -0
- package/src/components/toast/AGENTS.md +140 -0
- package/src/components/toast/index.ts +74 -0
- package/src/components/toast/toast-action.tsx +59 -0
- package/src/components/toast/toast-api.ts +32 -0
- package/src/components/toast/toast-close.tsx +44 -0
- package/src/components/toast/toast-content.tsx +11 -0
- package/src/components/toast/toast-description.tsx +11 -0
- package/src/components/toast/toast-indicator.tsx +34 -0
- package/src/components/toast/toast-item.tsx +308 -0
- package/src/components/toast/toast-title.tsx +13 -0
- package/src/components/toast/toast-viewport.tsx +135 -0
- package/src/components/toast/toast.context.tsx +69 -0
- package/src/components/toast/toast.store.test.ts +259 -0
- package/src/components/toast/toast.store.ts +338 -0
- package/src/components/toast/toast.timer.test.ts +64 -0
- package/src/components/toast/toast.timer.ts +51 -0
- package/src/components/toast/toast.tsx +132 -0
- package/src/components/toast/toast.variants.test.ts +393 -0
- package/src/components/toast/toast.variants.ts +304 -0
- package/src/components/toggle-button/AGENTS.md +106 -0
- package/src/components/toggle-button/index.ts +30 -0
- package/src/components/toggle-button/toggle-button-group.tsx +156 -0
- package/src/components/toggle-button/toggle-button-label.tsx +19 -0
- package/src/components/toggle-button/toggle-button.context.tsx +130 -0
- package/src/components/toggle-button/toggle-button.tsx +172 -0
- package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
- package/src/components/toggle-button/toggle-button.variants.ts +160 -0
- package/src/components/tooltip/AGENTS.md +133 -0
- package/src/components/tooltip/index.ts +28 -0
- package/src/components/tooltip/tooltip-arrow.tsx +40 -0
- package/src/components/tooltip/tooltip-content.tsx +179 -0
- package/src/components/tooltip/tooltip-description.tsx +19 -0
- package/src/components/tooltip/tooltip-text.tsx +21 -0
- package/src/components/tooltip/tooltip-title.tsx +20 -0
- package/src/components/tooltip/tooltip-trigger.tsx +103 -0
- package/src/components/tooltip/tooltip.context.tsx +68 -0
- package/src/components/tooltip/tooltip.tsx +213 -0
- package/src/components/tooltip/tooltip.variants.test.ts +231 -0
- package/src/components/tooltip/tooltip.variants.ts +177 -0
- package/src/hooks/use-calm-motion.tsx +31 -0
- package/src/lib/calm-motion.test.ts +17 -0
- package/src/lib/calm-motion.ts +23 -0
- package/src/lib/cn.parity.test.ts +62 -0
- package/src/lib/cn.test.ts +14 -0
- package/src/lib/cn.ts +11 -6
- package/src/no-gorhom.test.ts +54 -0
- package/src/styles/geometry.test.ts +1 -0
- package/src/styles/theme.css +4 -0
- package/src/styles/tokens.css +44 -0
- package/src/styles/tokens.test.ts +56 -0
- package/src/styles/tokens.ts +29 -1
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { aliasedTokens } from "../../styles/theme-tokens.test";
|
|
3
|
+
import {
|
|
4
|
+
isSlideArmed,
|
|
5
|
+
resolveSlideHandleWidth,
|
|
6
|
+
resolveSlideLabelGutter,
|
|
7
|
+
resolveSlideRelease,
|
|
8
|
+
resolveSlideThreshold,
|
|
9
|
+
resolveSlideTrailOpacity,
|
|
10
|
+
resolveSlideTrailWidth,
|
|
11
|
+
resolveSlideTravel,
|
|
12
|
+
SLIDE_BUTTON_ARM_SLACK,
|
|
13
|
+
SLIDE_BUTTON_DEFAULT_THRESHOLD,
|
|
14
|
+
SLIDE_BUTTON_FAR_END_SLOP,
|
|
15
|
+
SLIDE_BUTTON_GLYPH_TOKEN,
|
|
16
|
+
SLIDE_BUTTON_HANDLE_RATIO,
|
|
17
|
+
SLIDE_BUTTON_INSET,
|
|
18
|
+
SLIDE_BUTTON_LOOKAHEAD,
|
|
19
|
+
SLIDE_BUTTON_SIZES,
|
|
20
|
+
SLIDE_BUTTON_SPRING,
|
|
21
|
+
SLIDE_BUTTON_TRAIL_FADE,
|
|
22
|
+
SLIDE_BUTTON_VARIANTS,
|
|
23
|
+
slideButtonVariants,
|
|
24
|
+
} from "./slide-button.variants";
|
|
25
|
+
|
|
26
|
+
const ALIASED = aliasedTokens();
|
|
27
|
+
|
|
28
|
+
/** Every colour utility a class string names, with any opacity modifier dropped. */
|
|
29
|
+
function colourTokens(classes: string): string[] {
|
|
30
|
+
return classes
|
|
31
|
+
.split(/\s+/)
|
|
32
|
+
.map((utility) => utility.match(/^(?:bg|text|border)-([a-z-]+?)(?:\/\d+)?$/)?.[1])
|
|
33
|
+
.filter((name): name is string => name !== undefined)
|
|
34
|
+
.filter((name) => name !== "center" && !name.startsWith("button-"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("resolveSlideTrailOpacity", () => {
|
|
38
|
+
test("no trail shows while the handle rests", () => {
|
|
39
|
+
expect(resolveSlideTrailOpacity(0)).toBe(0);
|
|
40
|
+
expect(resolveSlideTrailOpacity(-3)).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("it fades in over the first few points, then holds", () => {
|
|
44
|
+
expect(SLIDE_BUTTON_TRAIL_FADE).toBeGreaterThan(0);
|
|
45
|
+
expect(resolveSlideTrailOpacity(SLIDE_BUTTON_TRAIL_FADE / 2)).toBeCloseTo(0.5);
|
|
46
|
+
expect(resolveSlideTrailOpacity(SLIDE_BUTTON_TRAIL_FADE)).toBe(1);
|
|
47
|
+
expect(resolveSlideTrailOpacity(200)).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("resolveSlideTrailWidth", () => {
|
|
52
|
+
test("runs from the rail's start edge to the handle's middle", () => {
|
|
53
|
+
expect(resolveSlideTrailWidth({ handleWidth: 58, inset: 4, offset: 0 })).toBe(33);
|
|
54
|
+
expect(resolveSlideTrailWidth({ handleWidth: 58, inset: 4, offset: 100 })).toBe(133);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("resolveSlideLabelGutter", () => {
|
|
59
|
+
test("keeps the label clear of the resting handle on both sides", () => {
|
|
60
|
+
expect(resolveSlideLabelGutter({ handleWidth: 58, inset: 4 })).toBe(66);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("is the inset alone before the handle is measured", () => {
|
|
64
|
+
expect(resolveSlideLabelGutter({ handleWidth: 0, inset: 4 })).toBe(8);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("resolveSlideThreshold", () => {
|
|
69
|
+
test("defaults to 0.9", () => {
|
|
70
|
+
expect(resolveSlideThreshold()).toBe(0.9);
|
|
71
|
+
expect(SLIDE_BUTTON_DEFAULT_THRESHOLD).toBe(0.9);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("NaN falls back to the default rather than poisoning every comparison", () => {
|
|
75
|
+
expect(resolveSlideThreshold(Number.NaN)).toBe(0.9);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("clamps into [0.1, 1]", () => {
|
|
79
|
+
expect(resolveSlideThreshold(0)).toBe(0.1);
|
|
80
|
+
expect(resolveSlideThreshold(-3)).toBe(0.1);
|
|
81
|
+
expect(resolveSlideThreshold(1.5)).toBe(1);
|
|
82
|
+
expect(resolveSlideThreshold(Number.POSITIVE_INFINITY)).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("passes a value in range through untouched", () => {
|
|
86
|
+
expect(resolveSlideThreshold(0.5)).toBe(0.5);
|
|
87
|
+
expect(resolveSlideThreshold(1)).toBe(1);
|
|
88
|
+
expect(resolveSlideThreshold(0.1)).toBe(0.1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("resolveSlideTravel", () => {
|
|
93
|
+
test("is the rail less the handle and an inset at each end", () => {
|
|
94
|
+
expect(resolveSlideTravel({ handleWidth: 58, inset: 4, railWidth: 300 })).toBe(234);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("is zero before layout and never negative", () => {
|
|
98
|
+
expect(resolveSlideTravel({ handleWidth: 0, inset: 4, railWidth: 0 })).toBe(0);
|
|
99
|
+
expect(resolveSlideTravel({ handleWidth: 80, inset: 4, railWidth: 60 })).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("resolveSlideHandleWidth", () => {
|
|
104
|
+
test("is about 1.6 times the handle's height, which is the rail less an inset top and bottom", () => {
|
|
105
|
+
expect(SLIDE_BUTTON_HANDLE_RATIO).toBe(1.6);
|
|
106
|
+
expect(resolveSlideHandleWidth({ inset: 4, railHeight: 44 })).toBe(Math.round(36 * 1.6));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("is zero before layout", () => {
|
|
110
|
+
expect(resolveSlideHandleWidth({ inset: 4, railHeight: 0 })).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("isSlideArmed", () => {
|
|
115
|
+
test("arms at the threshold and disarms below it", () => {
|
|
116
|
+
expect(isSlideArmed({ offset: 180, threshold: 0.9, travel: 200 })).toBe(true);
|
|
117
|
+
expect(isSlideArmed({ offset: 179, threshold: 0.9, travel: 200 })).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("with threshold 1 arms only at the far end", () => {
|
|
121
|
+
expect(isSlideArmed({ offset: 199.6, threshold: 1, travel: 200 })).toBe(true);
|
|
122
|
+
expect(isSlideArmed({ offset: 199, threshold: 1, travel: 200 })).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("never arms before layout", () => {
|
|
126
|
+
expect(isSlideArmed({ offset: 0, threshold: 0.1, travel: 0 })).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("resolveSlideRelease", () => {
|
|
131
|
+
const base = { lookahead: SLIDE_BUTTON_LOOKAHEAD, threshold: 0.9, travel: 200 } as const;
|
|
132
|
+
|
|
133
|
+
test("a release past the threshold completes", () => {
|
|
134
|
+
expect(resolveSlideRelease({ ...base, offset: 185, velocity: 0 })).toBe("complete");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a short release returns", () => {
|
|
138
|
+
expect(resolveSlideRelease({ ...base, offset: 60, velocity: 0 })).toBe("return");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a committed flick from about 70% completes", () => {
|
|
142
|
+
expect(resolveSlideRelease({ ...base, offset: 140, velocity: 1000 })).toBe("complete");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("a lazy flick from halfway does not complete", () => {
|
|
146
|
+
expect(resolveSlideRelease({ ...base, offset: 100, velocity: 300 })).toBe("return");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("even a hard flick from halfway does not complete — the hand has to get close first", () => {
|
|
150
|
+
expect(resolveSlideRelease({ ...base, offset: 100, velocity: 4000 })).toBe("return");
|
|
151
|
+
expect(SLIDE_BUTTON_ARM_SLACK).toBe(0.25);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("letting go past the threshold while pulling back returns", () => {
|
|
155
|
+
expect(resolveSlideRelease({ ...base, offset: 185, velocity: -1500 })).toBe("return");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("threshold 1 has no velocity shortcut and demands the far end", () => {
|
|
159
|
+
const strict = { ...base, threshold: 1 };
|
|
160
|
+
expect(resolveSlideRelease({ ...strict, offset: 150, velocity: 5000 })).toBe("return");
|
|
161
|
+
expect(resolveSlideRelease({ ...strict, offset: 199, velocity: 5000 })).toBe("return");
|
|
162
|
+
expect(resolveSlideRelease({ ...strict, offset: 200 - SLIDE_BUTTON_FAR_END_SLOP, velocity: 0 })).toBe("complete");
|
|
163
|
+
expect(resolveSlideRelease({ ...strict, offset: 200, velocity: 0 })).toBe("complete");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("never completes before layout", () => {
|
|
167
|
+
expect(resolveSlideRelease({ ...base, offset: 0, travel: 0, velocity: 9000 })).toBe("return");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("a low threshold completes from a short drag", () => {
|
|
171
|
+
expect(resolveSlideRelease({ ...base, offset: 110, threshold: 0.5, velocity: 0 })).toBe("complete");
|
|
172
|
+
expect(resolveSlideRelease({ ...base, offset: 90, threshold: 0.5, velocity: 0 })).toBe("return");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("motion constants", () => {
|
|
177
|
+
test("the handle is inset 4pt inside the rail", () => {
|
|
178
|
+
expect(SLIDE_BUTTON_INSET).toBe(4);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("the release spring overshoots only a little", () => {
|
|
182
|
+
const ratio =
|
|
183
|
+
SLIDE_BUTTON_SPRING.damping / (2 * Math.sqrt(SLIDE_BUTTON_SPRING.stiffness * SLIDE_BUTTON_SPRING.mass));
|
|
184
|
+
expect(ratio).toBeGreaterThan(0.7);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("slideButtonVariants", () => {
|
|
189
|
+
test("TODO(category lead): compare against progressButtonVariants at merge — each size's rail is a button-height capsule", () => {
|
|
190
|
+
for (const size of SLIDE_BUTTON_SIZES) {
|
|
191
|
+
const root = slideButtonVariants({ size }).root().split(/\s+/);
|
|
192
|
+
expect(root).toContain(`h-button-${size}`);
|
|
193
|
+
expect(root).toContain(`rounded-button-${size}`);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("the label takes the button's type step at each size", () => {
|
|
198
|
+
for (const size of SLIDE_BUTTON_SIZES) {
|
|
199
|
+
expect(slideButtonVariants({ size }).label().split(/\s+/)).toContain(`text-button-${size}`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("the label frame fills the rail and centres its text", () => {
|
|
204
|
+
const frame = slideButtonVariants().labelFrame().split(/\s+/);
|
|
205
|
+
expect(frame).toEqual(expect.arrayContaining(["absolute", "inset-0", "justify-center"]));
|
|
206
|
+
expect(slideButtonVariants().label()).toContain("text-center");
|
|
207
|
+
expect(slideButtonVariants().label()).not.toContain("absolute");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the label over the trail takes the trail's own foreground", () => {
|
|
211
|
+
expect(slideButtonVariants({ variant: "secondary" }).labelOnTrail()).toContain("text-secondary-foreground");
|
|
212
|
+
expect(slideButtonVariants({ variant: "destructive" }).labelOnTrail()).toContain("text-destructive-foreground");
|
|
213
|
+
expect(slideButtonVariants({ variant: "success" }).labelOnTrail()).toContain("text-success-foreground");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("the trail clip is pinned to the start edge and clips", () => {
|
|
217
|
+
const clip = slideButtonVariants().labelClip().split(/\s+/);
|
|
218
|
+
expect(clip).toEqual(expect.arrayContaining(["absolute", "start-0", "top-0", "bottom-0", "overflow-hidden"]));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("the glyph takes the icon step that matches the size", () => {
|
|
222
|
+
expect(slideButtonVariants({ size: "sm" }).thumbGlyph()).toContain("size-icon-sm");
|
|
223
|
+
expect(slideButtonVariants({ size: "md" }).thumbGlyph()).toContain("size-icon-md");
|
|
224
|
+
expect(slideButtonVariants({ size: "lg" }).thumbGlyph()).toContain("size-icon-lg");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("there is no primary variant", () => {
|
|
228
|
+
expect(SLIDE_BUTTON_VARIANTS).toEqual(["secondary", "destructive", "success"]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("secondary sits on bg-secondary and trails in a faint foreground", () => {
|
|
232
|
+
const slots = slideButtonVariants({ variant: "secondary" });
|
|
233
|
+
expect(slots.root()).toContain("bg-secondary");
|
|
234
|
+
expect(slots.trail()).toContain("bg-foreground/15");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("a state variant sits on its soft fill and trails in its full colour", () => {
|
|
238
|
+
for (const variant of ["destructive", "success"] as const) {
|
|
239
|
+
const slots = slideButtonVariants({ variant });
|
|
240
|
+
expect(slots.root()).toContain(`bg-${variant}-soft`);
|
|
241
|
+
expect(slots.trail().split(/\s+/)).toContain(`bg-${variant}`);
|
|
242
|
+
expect(slots.label()).toContain(`text-${variant}-soft-foreground`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("the handle is neutral in every variant", () => {
|
|
247
|
+
for (const variant of SLIDE_BUTTON_VARIANTS) {
|
|
248
|
+
const thumb = slideButtonVariants({ variant }).thumb();
|
|
249
|
+
expect(thumb).toContain("bg-background");
|
|
250
|
+
expect(thumb).toContain("border-border");
|
|
251
|
+
}
|
|
252
|
+
expect(SLIDE_BUTTON_GLYPH_TOKEN).toBe("foreground");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("the label colour is on the label, never the rail", () => {
|
|
256
|
+
for (const variant of SLIDE_BUTTON_VARIANTS) {
|
|
257
|
+
expect(slideButtonVariants({ variant }).root()).not.toMatch(/\btext-/);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("disabled fades the whole control", () => {
|
|
262
|
+
expect(slideButtonVariants({ isDisabled: true }).root()).toContain("opacity-50");
|
|
263
|
+
expect(slideButtonVariants({ isDisabled: false }).root()).not.toContain("opacity-50");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("full width stretches the rail", () => {
|
|
267
|
+
expect(slideButtonVariants({ isFullWidth: true }).root()).toContain("w-full");
|
|
268
|
+
expect(slideButtonVariants({ isFullWidth: false }).root()).not.toContain("w-full");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("every colour named exists in the theme", () => {
|
|
272
|
+
for (const variant of SLIDE_BUTTON_VARIANTS) {
|
|
273
|
+
const slots = slideButtonVariants({ variant });
|
|
274
|
+
const classes = [
|
|
275
|
+
slots.root(),
|
|
276
|
+
slots.trail(),
|
|
277
|
+
slots.label(),
|
|
278
|
+
slots.labelOnTrail(),
|
|
279
|
+
slots.thumb(),
|
|
280
|
+
slots.thumbGlyph(),
|
|
281
|
+
].join(" ");
|
|
282
|
+
const tokens = colourTokens(classes);
|
|
283
|
+
expect(tokens.length).toBeGreaterThan(0);
|
|
284
|
+
for (const token of tokens) expect(ALIASED.has(`--color-${token}`)).toBe(true);
|
|
285
|
+
}
|
|
286
|
+
expect(ALIASED.has(`--color-${SLIDE_BUTTON_GLYPH_TOKEN}`)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What the action the slide confirms means.
|
|
6
|
+
*
|
|
7
|
+
* There is no `primary`. The handle is neutral, so a primary rail with a neutral
|
|
8
|
+
* handle draws the same picture as `secondary` in any theme whose primary sits
|
|
9
|
+
* close to its foreground — two names for one look. See AGENTS.md.
|
|
10
|
+
*/
|
|
11
|
+
export const SLIDE_BUTTON_VARIANTS = ["secondary", "destructive", "success"] as const;
|
|
12
|
+
|
|
13
|
+
/** The button's own label steps, so a slide button lines up with a button beside it. */
|
|
14
|
+
export const SLIDE_BUTTON_SIZES = ["sm", "md", "lg"] as const;
|
|
15
|
+
|
|
16
|
+
export type SlideButtonVariant = (typeof SLIDE_BUTTON_VARIANTS)[number];
|
|
17
|
+
export type SlideButtonSize = (typeof SLIDE_BUTTON_SIZES)[number];
|
|
18
|
+
|
|
19
|
+
export const SLIDE_BUTTON_DEFAULT_VARIANT: SlideButtonVariant = "secondary";
|
|
20
|
+
export const SLIDE_BUTTON_DEFAULT_SIZE: SlideButtonSize = "md";
|
|
21
|
+
|
|
22
|
+
/** How far along the travel a release has to reach to confirm. */
|
|
23
|
+
export const SLIDE_BUTTON_DEFAULT_THRESHOLD = 0.9;
|
|
24
|
+
|
|
25
|
+
/** The lowest threshold accepted. Below it a nudge would confirm, which is a tap with extra steps. */
|
|
26
|
+
export const SLIDE_BUTTON_MIN_THRESHOLD = 0.1;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* How far the handle sits inside the rail on every side, in points.
|
|
30
|
+
*
|
|
31
|
+
* Written into the `thumb` slot as `start-1 top-1 bottom-1` and subtracted at
|
|
32
|
+
* both ends by {@link resolveSlideTravel}; a test pins the number.
|
|
33
|
+
*/
|
|
34
|
+
export const SLIDE_BUTTON_INSET = 4;
|
|
35
|
+
|
|
36
|
+
/** The handle's width over its height — a stadium, wider than a switch's knob. */
|
|
37
|
+
export const SLIDE_BUTTON_HANDLE_RATIO = 1.6;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How far ahead a release looks, in seconds of its own velocity.
|
|
41
|
+
*
|
|
42
|
+
* Small on purpose: enough that a finger already committed to the end does not
|
|
43
|
+
* have to drag the last few points, not enough that a flick does the work.
|
|
44
|
+
*/
|
|
45
|
+
export const SLIDE_BUTTON_LOOKAHEAD = 0.08;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* How far short of the threshold the *hand* may be when a flick carries it over.
|
|
49
|
+
*
|
|
50
|
+
* Without it the look-ahead alone would let a hard flick from halfway confirm,
|
|
51
|
+
* which is exactly the habit-driven swipe this control exists to refuse.
|
|
52
|
+
*/
|
|
53
|
+
export const SLIDE_BUTTON_ARM_SLACK = 0.25;
|
|
54
|
+
|
|
55
|
+
/** How close to the far end counts as there, in points, when the threshold is 1. */
|
|
56
|
+
export const SLIDE_BUTTON_FAR_END_SLOP = 0.5;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The spring a release settles on, either way.
|
|
60
|
+
*
|
|
61
|
+
* Damped a little under critical: the handle travelling home after a short
|
|
62
|
+
* release should land, not bounce, but a hint of give tells the hand it was
|
|
63
|
+
* heard. A test keeps the damping ratio above 0.7.
|
|
64
|
+
*/
|
|
65
|
+
export const SLIDE_BUTTON_SPRING = { damping: 22, mass: 0.6, stiffness: 260 } as const;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* How far the handle travels, in points, before its trail is fully drawn.
|
|
69
|
+
*
|
|
70
|
+
* The trail ends under the handle's middle, so at rest it would peek out
|
|
71
|
+
* behind the handle's rear curve as a coloured sliver — a control that looks
|
|
72
|
+
* half-started before anyone touched it.
|
|
73
|
+
*/
|
|
74
|
+
export const SLIDE_BUTTON_TRAIL_FADE = 8;
|
|
75
|
+
|
|
76
|
+
/** How long the chevron takes to cross into a tick, and how long a reduced-motion move takes. */
|
|
77
|
+
export const SLIDE_BUTTON_GLYPH_MS = 180;
|
|
78
|
+
export const SLIDE_BUTTON_REDUCED_MOTION_MS = 180;
|
|
79
|
+
|
|
80
|
+
/** How long a confirmed handle rests at the end before `isAutoReset` takes it home. */
|
|
81
|
+
export const SLIDE_BUTTON_DEFAULT_AUTO_RESET_MS = 1000;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The gesture's activation window, in points.
|
|
85
|
+
*
|
|
86
|
+
* Horizontal movement past 8 takes the drag; vertical movement past 12 fails it
|
|
87
|
+
* first, so a scroll that begins on the rail stays a scroll.
|
|
88
|
+
*/
|
|
89
|
+
export const SLIDE_BUTTON_ACTIVE_OFFSET_X = 8;
|
|
90
|
+
export const SLIDE_BUTTON_FAIL_OFFSET_Y = 12;
|
|
91
|
+
|
|
92
|
+
/** The token the handle's glyph is drawn in. A colour value, read through `useThemeColor`. */
|
|
93
|
+
export const SLIDE_BUTTON_GLYPH_TOKEN = "foreground";
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Styling for every part of a slide button.
|
|
97
|
+
*
|
|
98
|
+
* The rail is the same box `Button` draws at each size — `h-button-*` and
|
|
99
|
+
* `rounded-button-*` — so a slide button stacks level with any button above or
|
|
100
|
+
* below it. The handle is neutral in every variant; only the rail, the trail and
|
|
101
|
+
* the label take the variant's colour. The label's colour sits on the label,
|
|
102
|
+
* never the rail (rule 1).
|
|
103
|
+
*
|
|
104
|
+
* Every positioned part uses logical insets (`start-*`), so under RTL the handle
|
|
105
|
+
* rests at the right with nothing else said.
|
|
106
|
+
*
|
|
107
|
+
* Free of React Native imports so it stays unit-testable. See AGENTS.md.
|
|
108
|
+
*/
|
|
109
|
+
export const slideButtonVariants = tv({
|
|
110
|
+
slots: {
|
|
111
|
+
root: "relative justify-center overflow-hidden",
|
|
112
|
+
trail: "absolute start-0 top-0 bottom-0",
|
|
113
|
+
labelFrame: "absolute inset-0 justify-center",
|
|
114
|
+
label: "text-center font-semibold",
|
|
115
|
+
labelClip: "absolute start-0 top-0 bottom-0 overflow-hidden",
|
|
116
|
+
labelOnTrailFrame: "absolute start-0 top-0 bottom-0 justify-center",
|
|
117
|
+
labelOnTrail: "text-center font-semibold",
|
|
118
|
+
thumb:
|
|
119
|
+
"absolute start-1 top-1 bottom-1 items-center justify-center rounded-full border border-border bg-background",
|
|
120
|
+
thumbGlyph: "",
|
|
121
|
+
},
|
|
122
|
+
variants: {
|
|
123
|
+
variant: {
|
|
124
|
+
secondary: {
|
|
125
|
+
root: "bg-secondary",
|
|
126
|
+
trail: "bg-foreground/15",
|
|
127
|
+
label: "text-secondary-foreground",
|
|
128
|
+
labelOnTrail: "text-secondary-foreground",
|
|
129
|
+
},
|
|
130
|
+
destructive: {
|
|
131
|
+
root: "bg-destructive-soft",
|
|
132
|
+
trail: "bg-destructive",
|
|
133
|
+
label: "text-destructive-soft-foreground",
|
|
134
|
+
labelOnTrail: "text-destructive-foreground",
|
|
135
|
+
},
|
|
136
|
+
success: {
|
|
137
|
+
root: "bg-success-soft",
|
|
138
|
+
trail: "bg-success",
|
|
139
|
+
label: "text-success-soft-foreground",
|
|
140
|
+
labelOnTrail: "text-success-foreground",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
size: {
|
|
144
|
+
sm: {
|
|
145
|
+
root: "h-button-sm rounded-button-sm",
|
|
146
|
+
label: "text-button-sm",
|
|
147
|
+
labelOnTrail: "text-button-sm",
|
|
148
|
+
thumbGlyph: "size-icon-sm",
|
|
149
|
+
},
|
|
150
|
+
md: {
|
|
151
|
+
root: "h-button-md rounded-button-md",
|
|
152
|
+
label: "text-button-md",
|
|
153
|
+
labelOnTrail: "text-button-md",
|
|
154
|
+
thumbGlyph: "size-icon-md",
|
|
155
|
+
},
|
|
156
|
+
lg: {
|
|
157
|
+
root: "h-button-lg rounded-button-lg",
|
|
158
|
+
label: "text-button-lg",
|
|
159
|
+
labelOnTrail: "text-button-lg",
|
|
160
|
+
thumbGlyph: "size-icon-lg",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
isFullWidth: {
|
|
164
|
+
true: { root: "w-full self-stretch" },
|
|
165
|
+
false: { root: "w-72" },
|
|
166
|
+
},
|
|
167
|
+
isDisabled: {
|
|
168
|
+
true: { root: "opacity-50" },
|
|
169
|
+
false: {},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
defaultVariants: {
|
|
173
|
+
variant: SLIDE_BUTTON_DEFAULT_VARIANT,
|
|
174
|
+
size: SLIDE_BUTTON_DEFAULT_SIZE,
|
|
175
|
+
isFullWidth: false,
|
|
176
|
+
isDisabled: false,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The threshold a slide button actually uses.
|
|
182
|
+
*
|
|
183
|
+
* Undefined and NaN take the default — NaN would otherwise fail every comparison
|
|
184
|
+
* and leave a control that can never confirm. Anything else is clamped into
|
|
185
|
+
* [0.1, 1].
|
|
186
|
+
*/
|
|
187
|
+
export function resolveSlideThreshold(threshold?: number): number {
|
|
188
|
+
if (threshold === undefined || Number.isNaN(threshold)) return SLIDE_BUTTON_DEFAULT_THRESHOLD;
|
|
189
|
+
return Math.min(1, Math.max(SLIDE_BUTTON_MIN_THRESHOLD, threshold));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* How far the handle can move, in points: the rail less the handle and an inset
|
|
194
|
+
* at each end. Zero before layout, never negative.
|
|
195
|
+
*
|
|
196
|
+
* Marked `"worklet"` and written flat — see `resolveSwitchRelease` for why a
|
|
197
|
+
* module-scope worklet calls nothing.
|
|
198
|
+
*/
|
|
199
|
+
export function resolveSlideTravel({
|
|
200
|
+
railWidth,
|
|
201
|
+
handleWidth,
|
|
202
|
+
inset,
|
|
203
|
+
}: {
|
|
204
|
+
railWidth: number;
|
|
205
|
+
handleWidth: number;
|
|
206
|
+
inset: number;
|
|
207
|
+
}): number {
|
|
208
|
+
"worklet";
|
|
209
|
+
const travel = railWidth - handleWidth - inset * 2;
|
|
210
|
+
return travel > 0 ? travel : 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The handle's width from the rail's measured height.
|
|
215
|
+
*
|
|
216
|
+
* Measured rather than tabulated, so a consumer who retunes `--spacing-button-*`
|
|
217
|
+
* keeps a handle in proportion without touching this file.
|
|
218
|
+
*/
|
|
219
|
+
export function resolveSlideHandleWidth({ railHeight, inset }: { railHeight: number; inset: number }): number {
|
|
220
|
+
"worklet";
|
|
221
|
+
const height = railHeight - inset * 2;
|
|
222
|
+
return height > 0 ? Math.round(height * SLIDE_BUTTON_HANDLE_RATIO) : 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Whether the handle is far enough along that letting go here would confirm —
|
|
227
|
+
* what the arming haptic reports.
|
|
228
|
+
*
|
|
229
|
+
* With a threshold of 1 that is the far end, less half a point of slop.
|
|
230
|
+
*/
|
|
231
|
+
export function isSlideArmed({
|
|
232
|
+
offset,
|
|
233
|
+
travel,
|
|
234
|
+
threshold,
|
|
235
|
+
}: {
|
|
236
|
+
offset: number;
|
|
237
|
+
travel: number;
|
|
238
|
+
threshold: number;
|
|
239
|
+
}): boolean {
|
|
240
|
+
"worklet";
|
|
241
|
+
if (travel <= 0) return false;
|
|
242
|
+
if (threshold >= 1) return offset >= travel - SLIDE_BUTTON_FAR_END_SLOP;
|
|
243
|
+
return offset / travel >= threshold;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* How visible the trail is at a given offset: nothing at rest, fully drawn after
|
|
248
|
+
* {@link SLIDE_BUTTON_TRAIL_FADE} points.
|
|
249
|
+
*/
|
|
250
|
+
export function resolveSlideTrailOpacity(offset: number): number {
|
|
251
|
+
"worklet";
|
|
252
|
+
if (offset <= 0) return 0;
|
|
253
|
+
return offset >= SLIDE_BUTTON_TRAIL_FADE ? 1 : offset / SLIDE_BUTTON_TRAIL_FADE;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* How wide the trail is: from the rail's start edge to the handle's middle.
|
|
258
|
+
*
|
|
259
|
+
* It ends *under* the handle rather than at its rear edge — a stadium's rear is
|
|
260
|
+
* a curve, and a trail stopping at the straight edge leaves a sliver of rail
|
|
261
|
+
* above and below it. The label's trail-coloured copy is clipped to the same
|
|
262
|
+
* width, so the two cannot disagree.
|
|
263
|
+
*/
|
|
264
|
+
export function resolveSlideTrailWidth({
|
|
265
|
+
offset,
|
|
266
|
+
handleWidth,
|
|
267
|
+
inset,
|
|
268
|
+
}: {
|
|
269
|
+
offset: number;
|
|
270
|
+
handleWidth: number;
|
|
271
|
+
inset: number;
|
|
272
|
+
}): number {
|
|
273
|
+
"worklet";
|
|
274
|
+
return inset + offset + handleWidth / 2;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The label's horizontal padding, in points, on **each** side.
|
|
279
|
+
*
|
|
280
|
+
* One handle and two insets wide, applied symmetrically: the label stays centred
|
|
281
|
+
* in the whole rail, and a long one truncates before it runs under the resting
|
|
282
|
+
* handle instead of after.
|
|
283
|
+
*/
|
|
284
|
+
export function resolveSlideLabelGutter({ handleWidth, inset }: { handleWidth: number; inset: number }): number {
|
|
285
|
+
"worklet";
|
|
286
|
+
return handleWidth + inset * 2;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export type SlideRelease = "complete" | "return";
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Where a release sends the handle.
|
|
293
|
+
*
|
|
294
|
+
* `velocity` is already in the travel direction (flipped under RTL). The release
|
|
295
|
+
* looks a little ahead — `offset + velocity * lookahead` — and confirms when that
|
|
296
|
+
* projection reaches the threshold **and** the hand itself got within
|
|
297
|
+
* {@link SLIDE_BUTTON_ARM_SLACK} of it. The second condition is what stops a hard
|
|
298
|
+
* flick from halfway confirming.
|
|
299
|
+
*
|
|
300
|
+
* A threshold of 1 has no shortcut at all: only a release at the far end confirms.
|
|
301
|
+
*/
|
|
302
|
+
export function resolveSlideRelease({
|
|
303
|
+
offset,
|
|
304
|
+
velocity,
|
|
305
|
+
travel,
|
|
306
|
+
threshold,
|
|
307
|
+
lookahead,
|
|
308
|
+
}: {
|
|
309
|
+
offset: number;
|
|
310
|
+
velocity: number;
|
|
311
|
+
travel: number;
|
|
312
|
+
threshold: number;
|
|
313
|
+
lookahead: number;
|
|
314
|
+
}): SlideRelease {
|
|
315
|
+
"worklet";
|
|
316
|
+
if (travel <= 0) return "return";
|
|
317
|
+
if (threshold >= 1) return offset >= travel - SLIDE_BUTTON_FAR_END_SLOP ? "complete" : "return";
|
|
318
|
+
|
|
319
|
+
const projected = (offset + velocity * lookahead) / travel;
|
|
320
|
+
const reached = offset / travel;
|
|
321
|
+
return projected >= threshold && reached >= threshold - SLIDE_BUTTON_ARM_SLACK ? "complete" : "return";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type SlideButtonVariantProps = VariantProps<typeof slideButtonVariants>;
|
|
@@ -15,6 +15,7 @@ export {
|
|
|
15
15
|
nearestThumbIndex,
|
|
16
16
|
progressOf,
|
|
17
17
|
resolveSliderAxes,
|
|
18
|
+
resolveThumbAccessibilityLabel,
|
|
18
19
|
SLIDER_COLORS,
|
|
19
20
|
SLIDER_DEFAULT_COLOR,
|
|
20
21
|
SLIDER_DEFAULT_ORIENTATION,
|
|
@@ -28,6 +29,7 @@ export {
|
|
|
28
29
|
SLIDER_SIZES,
|
|
29
30
|
SLIDER_STEP,
|
|
30
31
|
SLIDER_THUMB_ANIMATION,
|
|
32
|
+
SLIDER_THUMB_NAMES,
|
|
31
33
|
SLIDER_THUMB_SPRING,
|
|
32
34
|
type SliderAxes,
|
|
33
35
|
type SliderColor,
|