@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,314 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { declaredTokens } from "../../styles/theme-tokens.test";
|
|
3
|
+
import { buttonVariants } from "../button/button.variants";
|
|
4
|
+
import {
|
|
5
|
+
PROGRESS_BUTTON_DEFAULT_AUTO_RESET_MS,
|
|
6
|
+
PROGRESS_BUTTON_DEFAULT_HINT,
|
|
7
|
+
PROGRESS_BUTTON_DEFAULT_HOLD_MS,
|
|
8
|
+
PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN,
|
|
9
|
+
PROGRESS_BUTTON_FILL_TOKEN,
|
|
10
|
+
PROGRESS_BUTTON_LABEL_TOKEN,
|
|
11
|
+
PROGRESS_BUTTON_MIN_HOLD_MS,
|
|
12
|
+
PROGRESS_BUTTON_REDUCED_MOTION_STEPS,
|
|
13
|
+
PROGRESS_BUTTON_SHAPES,
|
|
14
|
+
PROGRESS_BUTTON_SIZES,
|
|
15
|
+
PROGRESS_BUTTON_VARIANTS,
|
|
16
|
+
progressButtonVariants,
|
|
17
|
+
resolveAutoResetDelay,
|
|
18
|
+
resolveHoldDuration,
|
|
19
|
+
resolveProgressButtonAccessibilityState,
|
|
20
|
+
resolveRemainingDuration,
|
|
21
|
+
resolveSteppedProgress,
|
|
22
|
+
} from "./progress-button.variants";
|
|
23
|
+
|
|
24
|
+
const LIGHT = declaredTokens("light");
|
|
25
|
+
const DARK = declaredTokens("dark");
|
|
26
|
+
|
|
27
|
+
/** Every theme token a class string paints with, with any `/alpha` suffix dropped. */
|
|
28
|
+
function colorTokens(cls: string): string[] {
|
|
29
|
+
const tokens: string[] = [];
|
|
30
|
+
for (const [, utility, token] of cls.matchAll(/\b(bg|text)-([a-z][\w-]*)(?:\/\d+)?\b/g)) {
|
|
31
|
+
if (utility === "text" && token.startsWith("button-")) continue;
|
|
32
|
+
if (utility === "text" && (token === "center" || token === "left" || token === "right")) continue;
|
|
33
|
+
tokens.push(token);
|
|
34
|
+
}
|
|
35
|
+
return tokens;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SLOT_NAMES = ["root", "content", "fill", "fillContent", "label", "fillLabel", "done", "icon"] as const;
|
|
39
|
+
|
|
40
|
+
/** Every colour token any slot names, across the whole variant × size × shape matrix. */
|
|
41
|
+
function everySlotToken(): string[] {
|
|
42
|
+
const tokens = new Set<string>();
|
|
43
|
+
const combos = PROGRESS_BUTTON_VARIANTS.flatMap((variant) =>
|
|
44
|
+
PROGRESS_BUTTON_SIZES.flatMap((size) => PROGRESS_BUTTON_SHAPES.map((shape) => ({ shape, size, variant })))
|
|
45
|
+
);
|
|
46
|
+
for (const combo of combos) {
|
|
47
|
+
const slots = progressButtonVariants(combo);
|
|
48
|
+
for (const name of SLOT_NAMES) {
|
|
49
|
+
for (const token of colorTokens(slots[name]() ?? "")) tokens.add(token);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...tokens];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("resolveHoldDuration", () => {
|
|
56
|
+
test("defaults to two seconds", () => {
|
|
57
|
+
expect(PROGRESS_BUTTON_DEFAULT_HOLD_MS).toBe(2000);
|
|
58
|
+
expect(resolveHoldDuration()).toBe(2000);
|
|
59
|
+
expect(resolveHoldDuration(undefined)).toBe(2000);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("passes a sensible value through", () => {
|
|
63
|
+
expect(resolveHoldDuration(1500)).toBe(1500);
|
|
64
|
+
expect(resolveHoldDuration(200)).toBe(200);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("floors a hold too short to be deliberate", () => {
|
|
68
|
+
expect(PROGRESS_BUTTON_MIN_HOLD_MS).toBe(200);
|
|
69
|
+
expect(resolveHoldDuration(50)).toBe(200);
|
|
70
|
+
expect(resolveHoldDuration(0)).toBe(200);
|
|
71
|
+
expect(resolveHoldDuration(-1000)).toBe(200);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a non-finite value gives the default", () => {
|
|
75
|
+
expect(resolveHoldDuration(Number.NaN)).toBe(2000);
|
|
76
|
+
expect(resolveHoldDuration(Number.POSITIVE_INFINITY)).toBe(2000);
|
|
77
|
+
expect(resolveHoldDuration(Number.NEGATIVE_INFINITY)).toBe(2000);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("resolveRemainingDuration", () => {
|
|
82
|
+
test("forward from empty is the whole hold", () => {
|
|
83
|
+
expect(resolveRemainingDuration({ direction: "forward", holdDuration: 2000, progress: 0 })).toBe(2000);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("forward resumes from where the fill is", () => {
|
|
87
|
+
expect(resolveRemainingDuration({ direction: "forward", holdDuration: 2000, progress: 0.25 })).toBe(1500);
|
|
88
|
+
expect(resolveRemainingDuration({ direction: "forward", holdDuration: 2000, progress: 1 })).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("reverse plays back at the same rate", () => {
|
|
92
|
+
expect(resolveRemainingDuration({ direction: "reverse", holdDuration: 2000, progress: 0.9 })).toBeCloseTo(1800);
|
|
93
|
+
expect(resolveRemainingDuration({ direction: "reverse", holdDuration: 2000, progress: 0 })).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("forward and reverse at one point sum to the hold", () => {
|
|
97
|
+
for (const progress of [0, 0.1, 0.5, 0.73, 1]) {
|
|
98
|
+
const forward = resolveRemainingDuration({ direction: "forward", holdDuration: 1200, progress });
|
|
99
|
+
const reverse = resolveRemainingDuration({ direction: "reverse", holdDuration: 1200, progress });
|
|
100
|
+
expect(forward + reverse).toBeCloseTo(1200);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("clamps progress outside 0..1 and treats a non-finite one as empty", () => {
|
|
105
|
+
expect(resolveRemainingDuration({ direction: "forward", holdDuration: 2000, progress: -0.5 })).toBe(2000);
|
|
106
|
+
expect(resolveRemainingDuration({ direction: "forward", holdDuration: 2000, progress: 1.5 })).toBe(0);
|
|
107
|
+
expect(resolveRemainingDuration({ direction: "reverse", holdDuration: 2000, progress: Number.NaN })).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("resolveSteppedProgress", () => {
|
|
112
|
+
test("steps in fifths by default", () => {
|
|
113
|
+
expect(PROGRESS_BUTTON_REDUCED_MOTION_STEPS).toBe(5);
|
|
114
|
+
expect(resolveSteppedProgress(0)).toBe(0);
|
|
115
|
+
expect(resolveSteppedProgress(0.19)).toBe(0);
|
|
116
|
+
expect(resolveSteppedProgress(0.2)).toBe(0.2);
|
|
117
|
+
expect(resolveSteppedProgress(0.39)).toBe(0.2);
|
|
118
|
+
expect(resolveSteppedProgress(0.5)).toBe(0.4);
|
|
119
|
+
expect(resolveSteppedProgress(0.99)).toBe(0.8);
|
|
120
|
+
expect(resolveSteppedProgress(1)).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("takes another step count", () => {
|
|
124
|
+
expect(resolveSteppedProgress(0.6, 2)).toBe(0.5);
|
|
125
|
+
expect(resolveSteppedProgress(0.3, 4)).toBe(0.25);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("clamps, and never shows more than the hold has earned", () => {
|
|
129
|
+
expect(resolveSteppedProgress(-1)).toBe(0);
|
|
130
|
+
expect(resolveSteppedProgress(2)).toBe(1);
|
|
131
|
+
expect(resolveSteppedProgress(Number.NaN)).toBe(0);
|
|
132
|
+
for (let p = 0; p <= 1; p += 0.01) {
|
|
133
|
+
expect(resolveSteppedProgress(p)).toBeLessThanOrEqual(p + 1e-9);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a step count below one is treated as one", () => {
|
|
138
|
+
expect(resolveSteppedProgress(0.5, 0)).toBe(0);
|
|
139
|
+
expect(resolveSteppedProgress(1, 0)).toBe(1);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("resolveAutoResetDelay", () => {
|
|
144
|
+
test("defaults to a second", () => {
|
|
145
|
+
expect(PROGRESS_BUTTON_DEFAULT_AUTO_RESET_MS).toBe(1000);
|
|
146
|
+
expect(resolveAutoResetDelay()).toBe(1000);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("passes a value through and floors a negative one at zero", () => {
|
|
150
|
+
expect(resolveAutoResetDelay(2500)).toBe(2500);
|
|
151
|
+
expect(resolveAutoResetDelay(0)).toBe(0);
|
|
152
|
+
expect(resolveAutoResetDelay(-5)).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a non-finite value gives the default", () => {
|
|
156
|
+
expect(resolveAutoResetDelay(Number.NaN)).toBe(1000);
|
|
157
|
+
expect(resolveAutoResetDelay(Number.POSITIVE_INFINITY)).toBe(1000);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("resolveProgressButtonAccessibilityState", () => {
|
|
162
|
+
test("announces disabled and completed as checked", () => {
|
|
163
|
+
expect(resolveProgressButtonAccessibilityState({ isCompleted: false, isDisabled: false })).toEqual({
|
|
164
|
+
checked: false,
|
|
165
|
+
disabled: false,
|
|
166
|
+
});
|
|
167
|
+
expect(resolveProgressButtonAccessibilityState({ isCompleted: true, isDisabled: true })).toEqual({
|
|
168
|
+
checked: true,
|
|
169
|
+
disabled: true,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("the default hint says the button has to be held", () => {
|
|
174
|
+
expect(PROGRESS_BUTTON_DEFAULT_HINT.toLowerCase()).toContain("hold");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("progressButtonVariants — axes", () => {
|
|
179
|
+
test("exposes the variants, sizes and shapes the API names", () => {
|
|
180
|
+
expect([...PROGRESS_BUTTON_VARIANTS]).toEqual(["primary", "secondary", "destructive", "success"]);
|
|
181
|
+
expect([...PROGRESS_BUTTON_SIZES]).toEqual(["sm", "md", "lg"]);
|
|
182
|
+
expect([...PROGRESS_BUTTON_SHAPES]).toEqual(["pill", "rounded"]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("defaults to primary, md, pill", () => {
|
|
186
|
+
const root = progressButtonVariants().root();
|
|
187
|
+
expect(root).toContain("h-button-md");
|
|
188
|
+
expect(root).toContain("rounded-button-md");
|
|
189
|
+
expect(progressButtonVariants().fill()).toContain("bg-primary");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("every variant rests on the same surface", () => {
|
|
193
|
+
for (const variant of PROGRESS_BUTTON_VARIANTS) {
|
|
194
|
+
const root = progressButtonVariants({ variant }).root();
|
|
195
|
+
expect(root).toContain("bg-secondary");
|
|
196
|
+
expect(colorTokens(root)).toEqual(["secondary"]);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("the variant colour is carried by the fill and the labels", () => {
|
|
201
|
+
for (const variant of PROGRESS_BUTTON_VARIANTS) {
|
|
202
|
+
const slots = progressButtonVariants({ variant });
|
|
203
|
+
expect(slots.fill()).toContain(`bg-${PROGRESS_BUTTON_FILL_TOKEN[variant]}`);
|
|
204
|
+
expect(slots.label()).toContain(`text-${PROGRESS_BUTTON_LABEL_TOKEN[variant]}`);
|
|
205
|
+
expect(slots.fillLabel()).toContain(`text-${PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN[variant]}`);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("secondary fills with the foreground and draws its inner label on the background", () => {
|
|
210
|
+
const slots = progressButtonVariants({ variant: "secondary" });
|
|
211
|
+
expect(slots.fill()).toContain("bg-foreground");
|
|
212
|
+
expect(slots.label()).toContain("text-secondary-foreground");
|
|
213
|
+
expect(slots.fillLabel()).toContain("text-background");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("the two label copies differ only in colour", () => {
|
|
217
|
+
for (const variant of PROGRESS_BUTTON_VARIANTS) {
|
|
218
|
+
for (const size of PROGRESS_BUTTON_SIZES) {
|
|
219
|
+
const slots = progressButtonVariants({ size, variant });
|
|
220
|
+
const strip = (cls: string) =>
|
|
221
|
+
cls
|
|
222
|
+
.split(/\s+/)
|
|
223
|
+
.filter((c) => colorTokens(c).length === 0)
|
|
224
|
+
.sort();
|
|
225
|
+
expect(strip(slots.label())).toEqual(strip(slots.fillLabel()));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("the fill is clipped and anchored to the leading edge", () => {
|
|
231
|
+
const fill = progressButtonVariants().fill();
|
|
232
|
+
for (const cls of ["absolute", "overflow-hidden", "inset-y-0", "start-0"]) {
|
|
233
|
+
expect(fill).toContain(cls);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("progressButtonVariants — the button's box", () => {
|
|
239
|
+
test("height, padding, label step and icon step come off the button's own size tokens", () => {
|
|
240
|
+
for (const size of PROGRESS_BUTTON_SIZES) {
|
|
241
|
+
const ours = progressButtonVariants({ size });
|
|
242
|
+
const button = buttonVariants({ size });
|
|
243
|
+
const buttonRoot = button.root().split(/\s+/);
|
|
244
|
+
const height = buttonRoot.find((c) => c.startsWith("h-button-"));
|
|
245
|
+
const padding = buttonRoot.find((c) => c.startsWith("px-"));
|
|
246
|
+
expect(height).toBeDefined();
|
|
247
|
+
expect(padding).toBeDefined();
|
|
248
|
+
expect(ours.root()).toContain(height as string);
|
|
249
|
+
expect(ours.root()).toContain(padding as string);
|
|
250
|
+
expect(ours.fillContent()).toContain(padding as string);
|
|
251
|
+
expect(ours.label()).toContain(`text-button-${size}`);
|
|
252
|
+
expect(ours.icon()).toContain(`size-icon-${size}`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("a pill takes the button's corner, rounded takes the card's", () => {
|
|
257
|
+
for (const size of PROGRESS_BUTTON_SIZES) {
|
|
258
|
+
expect(progressButtonVariants({ shape: "pill", size }).root()).toContain(`rounded-button-${size}`);
|
|
259
|
+
const rounded = progressButtonVariants({ shape: "rounded", size }).root();
|
|
260
|
+
expect(rounded).toContain("rounded-lg");
|
|
261
|
+
expect(rounded).not.toContain("rounded-button-");
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("full width stretches", () => {
|
|
266
|
+
const root = progressButtonVariants({ isFullWidth: true }).root();
|
|
267
|
+
expect(root).toContain("w-full");
|
|
268
|
+
expect(root).toContain("self-stretch");
|
|
269
|
+
expect(progressButtonVariants({ isFullWidth: false }).root()).not.toContain("w-full");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("disabled fades", () => {
|
|
273
|
+
expect(progressButtonVariants({ isDisabled: true }).root()).toContain("opacity-50");
|
|
274
|
+
expect(progressButtonVariants({ isDisabled: false }).root()).not.toContain("opacity-50");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("a caller's className merges over the root's own", () => {
|
|
278
|
+
const root = progressButtonVariants().root({ className: "rounded-none" });
|
|
279
|
+
expect(root).toContain("rounded-none");
|
|
280
|
+
expect(root).not.toContain("rounded-button-md");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("progressButtonVariants — theme tokens", () => {
|
|
285
|
+
test("the theme reader found both variants, and the slots name some tokens", () => {
|
|
286
|
+
expect(LIGHT.size).toBeGreaterThan(0);
|
|
287
|
+
expect(DARK.size).toBeGreaterThan(0);
|
|
288
|
+
expect(everySlotToken().length).toBeGreaterThan(4);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("every token named in every slot, at every axis, exists in both themes", () => {
|
|
292
|
+
const missing = everySlotToken().filter((token) => !(LIGHT.has(token) && DARK.has(token)));
|
|
293
|
+
expect(missing).toEqual([]);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("every token in the colour tables exists in both themes", () => {
|
|
297
|
+
for (const table of [
|
|
298
|
+
PROGRESS_BUTTON_FILL_TOKEN,
|
|
299
|
+
PROGRESS_BUTTON_LABEL_TOKEN,
|
|
300
|
+
PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN,
|
|
301
|
+
]) {
|
|
302
|
+
for (const variant of PROGRESS_BUTTON_VARIANTS) {
|
|
303
|
+
expect(LIGHT.has(table[variant])).toBe(true);
|
|
304
|
+
expect(DARK.has(table[variant])).toBe(true);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("the fill and its label never share a token", () => {
|
|
310
|
+
for (const variant of PROGRESS_BUTTON_VARIANTS) {
|
|
311
|
+
expect(PROGRESS_BUTTON_FILL_TOKEN[variant]).not.toBe(PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN[variant]);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
|
|
4
|
+
export const PROGRESS_BUTTON_VARIANTS = ["primary", "secondary", "destructive", "success"] as const;
|
|
5
|
+
|
|
6
|
+
/** The button's label sizes. A hold needs a label, so there is no square `icon-*` step. */
|
|
7
|
+
export const PROGRESS_BUTTON_SIZES = ["sm", "md", "lg"] as const;
|
|
8
|
+
|
|
9
|
+
/** `pill` takes the button's capsule corner; `rounded` takes the card's `rounded-lg`. */
|
|
10
|
+
export const PROGRESS_BUTTON_SHAPES = ["pill", "rounded"] as const;
|
|
11
|
+
|
|
12
|
+
export type ProgressButtonVariant = (typeof PROGRESS_BUTTON_VARIANTS)[number];
|
|
13
|
+
export type ProgressButtonSize = (typeof PROGRESS_BUTTON_SIZES)[number];
|
|
14
|
+
export type ProgressButtonShape = (typeof PROGRESS_BUTTON_SHAPES)[number];
|
|
15
|
+
|
|
16
|
+
/** How long a hold takes when nothing says otherwise. */
|
|
17
|
+
export const PROGRESS_BUTTON_DEFAULT_HOLD_MS = 2000;
|
|
18
|
+
|
|
19
|
+
/** The shortest hold accepted. Anything quicker is a long tap, not a decision. */
|
|
20
|
+
export const PROGRESS_BUTTON_MIN_HOLD_MS = 200;
|
|
21
|
+
|
|
22
|
+
/** How long a completed button waits before `isAutoReset` rewinds it. */
|
|
23
|
+
export const PROGRESS_BUTTON_DEFAULT_AUTO_RESET_MS = 1000;
|
|
24
|
+
|
|
25
|
+
/** How long a reset, or a completion set from outside, takes to travel the whole width. */
|
|
26
|
+
export const PROGRESS_BUTTON_TRAVEL_MS = 400;
|
|
27
|
+
|
|
28
|
+
/** How long the labels and the done mark take to cross-fade. */
|
|
29
|
+
export const PROGRESS_BUTTON_CROSSFADE_MS = 200;
|
|
30
|
+
|
|
31
|
+
/** How far, in points, a resting finger may drift before the hold lets go. */
|
|
32
|
+
export const PROGRESS_BUTTON_MAX_DRIFT = 16;
|
|
33
|
+
|
|
34
|
+
/** How many discrete steps the fill takes under reduced motion. */
|
|
35
|
+
export const PROGRESS_BUTTON_REDUCED_MOTION_STEPS = 5;
|
|
36
|
+
|
|
37
|
+
/** What VoiceOver and TalkBack read after the label when no `accessibilityHint` is given. */
|
|
38
|
+
export const PROGRESS_BUTTON_DEFAULT_HINT = "Press and hold to confirm";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Theme token each variant fills with.
|
|
42
|
+
*
|
|
43
|
+
* `secondary` fills with the foreground: its resting surface already is
|
|
44
|
+
* `bg-secondary`, and a fill the same colour as the surface under it would not
|
|
45
|
+
* be seen at all.
|
|
46
|
+
*/
|
|
47
|
+
export const PROGRESS_BUTTON_FILL_TOKEN: Record<ProgressButtonVariant, string> = {
|
|
48
|
+
primary: "primary",
|
|
49
|
+
secondary: "foreground",
|
|
50
|
+
destructive: "destructive",
|
|
51
|
+
success: "success",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Theme token the resting label, and a composed icon on the surface, are drawn in. */
|
|
55
|
+
export const PROGRESS_BUTTON_LABEL_TOKEN: Record<ProgressButtonVariant, string> = {
|
|
56
|
+
primary: "primary",
|
|
57
|
+
secondary: "secondary-foreground",
|
|
58
|
+
destructive: "destructive",
|
|
59
|
+
success: "success",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Theme token the label copy inside the fill, its icons and the done mark are drawn in.
|
|
64
|
+
*
|
|
65
|
+
* `secondary` draws on `background`, the token `foreground` is the foreground
|
|
66
|
+
* *of* — `secondary-foreground` on a `foreground` fill would be the same colour
|
|
67
|
+
* twice.
|
|
68
|
+
*/
|
|
69
|
+
export const PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN: Record<ProgressButtonVariant, string> = {
|
|
70
|
+
primary: "primary-foreground",
|
|
71
|
+
secondary: "background",
|
|
72
|
+
destructive: "destructive-foreground",
|
|
73
|
+
success: "success-foreground",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Styling for every part of a progress button.
|
|
78
|
+
*
|
|
79
|
+
* The box is a button's box: `h-button-*`, the button's `px-*` and, as a pill,
|
|
80
|
+
* `rounded-button-*` — the same tokens `buttonVariants` reads, never new ones,
|
|
81
|
+
* and a test pins them equal. Every variant rests on one surface,
|
|
82
|
+
* `bg-secondary`, and carries its colour in the label and the fill.
|
|
83
|
+
*
|
|
84
|
+
* The label is drawn twice. `label` sits on the surface; `fillLabel` sits inside
|
|
85
|
+
* the clipped fill, laid out in `fillContent` at the button's measured width so
|
|
86
|
+
* both copies wrap the same and the wipe's edge cuts through a glyph rather
|
|
87
|
+
* than between two different layouts. The two differ only in colour — a test
|
|
88
|
+
* asserts it.
|
|
89
|
+
*
|
|
90
|
+
* Free of React Native imports so it stays unit-testable. See AGENTS.md.
|
|
91
|
+
*/
|
|
92
|
+
export const progressButtonVariants = tv({
|
|
93
|
+
slots: {
|
|
94
|
+
root: "relative flex-row items-center justify-center overflow-hidden bg-secondary",
|
|
95
|
+
/** The surface layer: the resting label and anything composed beside it. */
|
|
96
|
+
content: "flex-row items-center justify-center gap-2",
|
|
97
|
+
fill: "absolute inset-y-0 start-0 overflow-hidden",
|
|
98
|
+
/** The fill's inner copy, laid out at the root's measured width. */
|
|
99
|
+
fillContent: "absolute inset-y-0 start-0 flex-row items-center justify-center gap-2",
|
|
100
|
+
label: "text-center font-semibold",
|
|
101
|
+
fillLabel: "text-center font-semibold",
|
|
102
|
+
/** Where the done mark sits: the whole box, on the fill. */
|
|
103
|
+
done: "absolute inset-y-0 start-0 items-center justify-center",
|
|
104
|
+
/** Edge length a composed `Icon`, and the default tick, inherit. */
|
|
105
|
+
icon: "",
|
|
106
|
+
},
|
|
107
|
+
variants: {
|
|
108
|
+
variant: {
|
|
109
|
+
primary: { fill: "bg-primary", label: "text-primary", fillLabel: "text-primary-foreground" },
|
|
110
|
+
secondary: {
|
|
111
|
+
fill: "bg-foreground",
|
|
112
|
+
label: "text-secondary-foreground",
|
|
113
|
+
fillLabel: "text-background",
|
|
114
|
+
},
|
|
115
|
+
destructive: {
|
|
116
|
+
fill: "bg-destructive",
|
|
117
|
+
label: "text-destructive",
|
|
118
|
+
fillLabel: "text-destructive-foreground",
|
|
119
|
+
},
|
|
120
|
+
success: { fill: "bg-success", label: "text-success", fillLabel: "text-success-foreground" },
|
|
121
|
+
},
|
|
122
|
+
// Written out rather than built: Tailwind scans source text, and a class
|
|
123
|
+
// assembled at runtime is never compiled.
|
|
124
|
+
size: {
|
|
125
|
+
sm: {
|
|
126
|
+
root: "h-button-sm px-3",
|
|
127
|
+
content: "gap-1.5",
|
|
128
|
+
fillContent: "gap-1.5 px-3",
|
|
129
|
+
label: "text-button-sm",
|
|
130
|
+
fillLabel: "text-button-sm",
|
|
131
|
+
icon: "size-icon-sm",
|
|
132
|
+
},
|
|
133
|
+
md: {
|
|
134
|
+
root: "h-button-md px-4",
|
|
135
|
+
fillContent: "px-4",
|
|
136
|
+
label: "text-button-md",
|
|
137
|
+
fillLabel: "text-button-md",
|
|
138
|
+
icon: "size-icon-md",
|
|
139
|
+
},
|
|
140
|
+
lg: {
|
|
141
|
+
root: "h-button-lg px-5",
|
|
142
|
+
fillContent: "px-5",
|
|
143
|
+
label: "text-button-lg",
|
|
144
|
+
fillLabel: "text-button-lg",
|
|
145
|
+
icon: "size-icon-lg",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
// The pill's corner depends on the size, so it is a compound cell below.
|
|
149
|
+
shape: { pill: {}, rounded: { root: "rounded-lg" } },
|
|
150
|
+
isFullWidth: { true: { root: "w-full self-stretch" }, false: {} },
|
|
151
|
+
isDisabled: { true: { root: "opacity-50" }, false: {} },
|
|
152
|
+
},
|
|
153
|
+
compoundVariants: [
|
|
154
|
+
{ shape: "pill", size: "sm", class: { root: "rounded-button-sm" } },
|
|
155
|
+
{ shape: "pill", size: "md", class: { root: "rounded-button-md" } },
|
|
156
|
+
{ shape: "pill", size: "lg", class: { root: "rounded-button-lg" } },
|
|
157
|
+
],
|
|
158
|
+
defaultVariants: {
|
|
159
|
+
variant: "primary",
|
|
160
|
+
size: "md",
|
|
161
|
+
shape: "pill",
|
|
162
|
+
isFullWidth: false,
|
|
163
|
+
isDisabled: false,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export type ProgressButtonVariantProps = VariantProps<typeof progressButtonVariants>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* How long a full hold takes, in milliseconds.
|
|
171
|
+
*
|
|
172
|
+
* Defaults to {@link PROGRESS_BUTTON_DEFAULT_HOLD_MS} and is floored at
|
|
173
|
+
* {@link PROGRESS_BUTTON_MIN_HOLD_MS}. A non-finite value — `NaN` from a
|
|
174
|
+
* division somewhere upstream — takes the default rather than a hold that
|
|
175
|
+
* completes instantly or never.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveHoldDuration(ms?: number): number {
|
|
178
|
+
if (ms === undefined || !Number.isFinite(ms)) return PROGRESS_BUTTON_DEFAULT_HOLD_MS;
|
|
179
|
+
return Math.max(PROGRESS_BUTTON_MIN_HOLD_MS, ms);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** How long a completed button waits before rewinding itself. Non-negative. */
|
|
183
|
+
export function resolveAutoResetDelay(ms?: number): number {
|
|
184
|
+
if (ms === undefined || !Number.isFinite(ms)) return PROGRESS_BUTTON_DEFAULT_AUTO_RESET_MS;
|
|
185
|
+
return Math.max(0, ms);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* How long the fill takes to travel from `progress` to its end, at the hold's rate.
|
|
190
|
+
*
|
|
191
|
+
* `forward` is what is left to fill — `holdDuration × (1 − p)` — so a second
|
|
192
|
+
* press resumes rather than restarting. `reverse` is what is filled —
|
|
193
|
+
* `holdDuration × p` — so a release plays the fill back at the same speed it
|
|
194
|
+
* went in, and the two at one point always sum to the whole hold.
|
|
195
|
+
*
|
|
196
|
+
* The worklet that drives the fill restates this inline, because a worklet body
|
|
197
|
+
* must be self-contained; this copy is the one the tests pin.
|
|
198
|
+
*/
|
|
199
|
+
export function resolveRemainingDuration({
|
|
200
|
+
holdDuration,
|
|
201
|
+
progress,
|
|
202
|
+
direction,
|
|
203
|
+
}: {
|
|
204
|
+
holdDuration: number;
|
|
205
|
+
progress: number;
|
|
206
|
+
direction: "forward" | "reverse";
|
|
207
|
+
}): number {
|
|
208
|
+
const p = Number.isFinite(progress) ? Math.min(1, Math.max(0, progress)) : 0;
|
|
209
|
+
return holdDuration * (direction === "forward" ? 1 - p : p);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The fill shown under reduced motion: `progress` rounded down to a step.
|
|
214
|
+
*
|
|
215
|
+
* Down, never to nearest, so the fill never shows more than the hold has
|
|
216
|
+
* earned — a full bar appears only at 1. The animated style restates this
|
|
217
|
+
* inline (worklet bodies must be self-contained); this copy is the tested one.
|
|
218
|
+
*/
|
|
219
|
+
export function resolveSteppedProgress(progress: number, steps: number = PROGRESS_BUTTON_REDUCED_MOTION_STEPS): number {
|
|
220
|
+
const p = Number.isFinite(progress) ? Math.min(1, Math.max(0, progress)) : 0;
|
|
221
|
+
if (p >= 1) return 1;
|
|
222
|
+
if (!(steps >= 1)) return 0;
|
|
223
|
+
const count = Math.floor(steps);
|
|
224
|
+
return Math.floor(p * count) / count;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** What a screen reader is told about the button's state. Completed reads as checked. */
|
|
228
|
+
export function resolveProgressButtonAccessibilityState({
|
|
229
|
+
isDisabled,
|
|
230
|
+
isCompleted,
|
|
231
|
+
}: {
|
|
232
|
+
isDisabled: boolean;
|
|
233
|
+
isCompleted: boolean;
|
|
234
|
+
}): { disabled: boolean; checked: boolean } {
|
|
235
|
+
return { checked: isCompleted, disabled: isDisabled };
|
|
236
|
+
}
|
|
@@ -15,14 +15,14 @@ everything — a root layout, an `App.tsx`.
|
|
|
15
15
|
## Design
|
|
16
16
|
|
|
17
17
|
- **Five layers, outermost first**: `GestureHandlerRootView` →
|
|
18
|
-
`SafeAreaProvider` → `KeyboardProvider` → `<KeyboardStateSync />` beside
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
`SafeAreaProvider` → `KeyboardProvider` → `<KeyboardStateSync />` beside
|
|
19
|
+
`CalmMotionProvider`, which wraps the children. The order is not stylistic. The gesture root has to be an ancestor
|
|
20
|
+
native view of every handler a `Pressable` creates, and its absence is
|
|
21
|
+
*silent* — no error, no warning, presses simply stop landing.
|
|
22
|
+
`KeyboardStateSync` has to be a CHILD of `KeyboardProvider`, because it calls
|
|
23
|
+
`useKeyboardContext()`, and it stays a SIBLING of the children rather than
|
|
24
|
+
wrapping them: the repair is global and has to outlive any layer that can
|
|
25
|
+
remount.
|
|
26
26
|
- **`initialMetrics` defaults to `initialWindowMetrics`, and that is
|
|
27
27
|
load-bearing.** `SafeAreaProvider` renders `null` — not unstyled children,
|
|
28
28
|
*nothing* — until its native view reports the first `onInsetsChange`, so
|
|
@@ -44,25 +44,38 @@ everything — a root layout, an `App.tsx`.
|
|
|
44
44
|
failure mode, which is the least debuggable outcome in the package. And a prop
|
|
45
45
|
surface that names the layers changes shape every time a layer is added:
|
|
46
46
|
`children`, `style` and `initialMetrics` say nothing about what is inside, so a
|
|
47
|
-
future
|
|
48
|
-
|
|
47
|
+
future portal host or toast host is an edit to one file rather than a
|
|
48
|
+
breaking change. An app that genuinely needs a different stack composes
|
|
49
49
|
the providers by hand — they are all public from their own packages, and this
|
|
50
50
|
is a convenience, not a gate.
|
|
51
51
|
- **A new layer goes innermost.** Anything that draws above the app — a
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
portal host, a toast host — has to sit inside every provider it reads, so it
|
|
53
|
+
wraps `{children}` and nothing else moves. A layer that brings a new native
|
|
54
|
+
peer is a peer-dependency decision first.
|
|
55
|
+
- **`BottomSheetProvider` is the layer this component does not mount, and the
|
|
56
|
+
reason is the peer decision above.** The sheet's engine,
|
|
57
|
+
`@delacour/react-native-bottom-sheet`, is an **optional** peer of this
|
|
58
|
+
library, the way `@delacour/react-native-charts` is: an app with no sheet
|
|
59
|
+
never resolves it, and its native peer `react-native-teleport` never has to
|
|
60
|
+
be in that app's build. An import here would make every app resolve both,
|
|
61
|
+
sheet or no sheet. So the provider is exported beside `BottomSheet`, from the
|
|
62
|
+
sheet's own subpath, and the app mounts it itself, once,
|
|
63
|
+
inside `DelacourProvider` and around its navigator — innermost, exactly where
|
|
64
|
+
the rule above puts it, because it reads the gesture root for its pans, the
|
|
65
|
+
safe area for its insets and the keyboard values a sheet's footer rides.
|
|
66
|
+
`apps/playground/src/app/_layout.tsx` is the reference mount. Without it a
|
|
67
|
+
`BottomSheet.Portal` still renders, where it is written, as an inline sheet.
|
|
68
|
+
The previous sheet library was a required peer while this component mounted
|
|
69
|
+
its modal provider; nothing imports it any more.
|
|
70
|
+
- **`isMotionCalm` is a behaviour prop, not a layer-named one.** It feeds
|
|
71
|
+
`CalmMotionProvider` (`hooks/use-calm-motion.tsx`), which `useCalmMotion()`
|
|
72
|
+
reads beside the OS reduce-motion setting. It exists for E2E builds: a runner
|
|
73
|
+
that waits for the screen to settle before each gesture (Argent waits up to
|
|
74
|
+
3 s) pays the whole wait on a screen holding a never-ending loop. Only
|
|
75
|
+
decorative loops ask — `Skeleton` today; `Spinner` and an indeterminate
|
|
76
|
+
`Progress` are the behaviour and keep moving. The context defaults to `false`,
|
|
77
|
+
so a component used without this provider (a `delacour add` copy in an app
|
|
78
|
+
with its own stack) still honours reduce motion.
|
|
66
79
|
- **Deliberately not idempotent.** It does not detect an enclosing copy of
|
|
67
80
|
itself. Nesting `GestureHandlerRootView` costs a `View`; nesting
|
|
68
81
|
`SafeAreaProvider` seeds from the parent's insets and costs a native view;
|
|
@@ -83,7 +96,7 @@ everything — a root layout, an `App.tsx`.
|
|
|
83
96
|
per-subpath and survives intact: `/button` still pulls nothing
|
|
84
97
|
keyboard-related.
|
|
85
98
|
- **Nothing here for `bun test`, and no `provider.variants.ts` to give it
|
|
86
|
-
some.** The component is
|
|
99
|
+
some.** The component is five nested elements and one default parameter;
|
|
87
100
|
extracting a `resolveInitialMetrics()` would be a unit test of `??`. The rule
|
|
88
101
|
that pure decisions live in a `*.variants.ts` has no decision here to
|
|
89
102
|
relocate.
|