@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,468 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
import type { TextSize } from "../text/text.variants";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How an indicator is painted. `primary` rings the current step and leaves the
|
|
7
|
+
* steps ahead hollow; `secondary` fills every indicator, the current one solid
|
|
8
|
+
* and the rest soft.
|
|
9
|
+
*/
|
|
10
|
+
export const STEPS_VARIANTS = ["primary", "secondary"] as const;
|
|
11
|
+
|
|
12
|
+
export const STEPS_SIZES = ["sm", "md", "lg"] as const;
|
|
13
|
+
|
|
14
|
+
/** Which way the steps run. `horizontal` puts titles under the indicators; `vertical` beside them. */
|
|
15
|
+
export const STEPS_ORIENTATIONS = ["horizontal", "vertical"] as const;
|
|
16
|
+
|
|
17
|
+
/** Where a step stands relative to the value. See {@link resolveStepStatus}. */
|
|
18
|
+
export const STEP_STATUSES = ["completed", "current", "upcoming"] as const;
|
|
19
|
+
|
|
20
|
+
export type StepsVariant = (typeof STEPS_VARIANTS)[number];
|
|
21
|
+
export type StepsSize = (typeof STEPS_SIZES)[number];
|
|
22
|
+
export type StepsOrientation = (typeof STEPS_ORIENTATIONS)[number];
|
|
23
|
+
export type StepStatus = (typeof STEP_STATUSES)[number];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The axes a stepper falls back to when the call site names none.
|
|
27
|
+
*
|
|
28
|
+
* Named once because two places read them — `defaultVariants` below and the
|
|
29
|
+
* root's destructure, which settles them before `tv` is ever called and
|
|
30
|
+
* publishes them through context. A test pins the pair.
|
|
31
|
+
*/
|
|
32
|
+
export const STEPS_DEFAULT_VARIANT: StepsVariant = "primary";
|
|
33
|
+
export const STEPS_DEFAULT_SIZE: StepsSize = "md";
|
|
34
|
+
export const STEPS_DEFAULT_ORIENTATION: StepsOrientation = "horizontal";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The `Text` size step each stepper size hands `Steps.Title`.
|
|
38
|
+
*
|
|
39
|
+
* The title renders `Text.Label` and names this step rather than restating a
|
|
40
|
+
* type scale in a slot — the rule `Radio.Label` follows. The vertical layout's
|
|
41
|
+
* top padding is derived from the line height of this step, and a test holds
|
|
42
|
+
* the two together: move one without the other and the title's first line
|
|
43
|
+
* stops sitting level with its indicator.
|
|
44
|
+
*/
|
|
45
|
+
export const STEPS_TITLE_TEXT_SIZE: Record<StepsSize, TextSize> = { sm: "sm", md: "md", lg: "lg" };
|
|
46
|
+
|
|
47
|
+
/** The `Text` size step each stepper size hands `Steps.Description`. One below the title. */
|
|
48
|
+
export const STEPS_DESCRIPTION_TEXT_SIZE: Record<StepsSize, TextSize> = { sm: "xs", md: "sm", lg: "md" };
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Styling for every part of a stepper.
|
|
52
|
+
*
|
|
53
|
+
* One slotted `tv()` rather than a call per part, because the item, indicator,
|
|
54
|
+
* title and description files cannot import the root without closing a cycle
|
|
55
|
+
* (AGENTS.md rule 3) yet all of them read the same `orientation`, `size` and
|
|
56
|
+
* `status`.
|
|
57
|
+
*
|
|
58
|
+
* **The connectors are laid out, never measured.** A horizontal item is a
|
|
59
|
+
* column of equal `flex-1` width with its indicator centred in a track of
|
|
60
|
+
* `[half-line] [indicator] [half-line]`; the line between two steps is the
|
|
61
|
+
* right half of one and the left half of the next, meeting at the item
|
|
62
|
+
* boundary. A vertical trigger is a row whose track is `[indicator] [line]`, the
|
|
63
|
+
* line `flex-1` down the height the content gives it. Neither needs an
|
|
64
|
+
* `onLayout`, so a title that wraps to three lines moves nothing a frame late.
|
|
65
|
+
*
|
|
66
|
+
* **A vertical title is padded down to sit level with its indicator**, by half
|
|
67
|
+
* the difference between the indicator's edge and the title's line height —
|
|
68
|
+
* the one number here derived from another, and pinned by a test.
|
|
69
|
+
*
|
|
70
|
+
* **The fade lands on the indicator and the content, never the trigger.** The
|
|
71
|
+
* trigger is worn by `Pressable`'s own `Animated.View`, whose animated style writes
|
|
72
|
+
* `opacity` every frame and would silently overwrite an `opacity-50` class —
|
|
73
|
+
* the trap `radioVariants` documents.
|
|
74
|
+
*
|
|
75
|
+
* Every colour cell lives in `compoundVariants`, invalid last, because `tv`
|
|
76
|
+
* emits the compounds after the plain variants: a plain `isInvalid` branch
|
|
77
|
+
* would lose to the status cells.
|
|
78
|
+
*
|
|
79
|
+
* Free of React Native imports so it stays unit-testable. See AGENTS.md.
|
|
80
|
+
*/
|
|
81
|
+
export const stepsVariants = tv({
|
|
82
|
+
slots: {
|
|
83
|
+
/** The root's box. */
|
|
84
|
+
root: "w-full",
|
|
85
|
+
/** One step's box: the trigger, then its panel. Takes the caller's `className`. */
|
|
86
|
+
item: "",
|
|
87
|
+
/** The pressable — indicator, title and description. Deliberately free of opacity; see above. */
|
|
88
|
+
trigger: "",
|
|
89
|
+
/** The indicator and the connector halves around it. */
|
|
90
|
+
track: "",
|
|
91
|
+
/**
|
|
92
|
+
* One half of the line between two steps. Square-ended: two rounded
|
|
93
|
+
* halves meeting at the item boundary would pinch the line there.
|
|
94
|
+
*/
|
|
95
|
+
connector: "",
|
|
96
|
+
/** An invisible half that keeps an end indicator centred in its column. */
|
|
97
|
+
spacer: "flex-1",
|
|
98
|
+
/** The circle. */
|
|
99
|
+
indicator: "items-center justify-center rounded-full border-2",
|
|
100
|
+
/** The step number inside the circle — a `Text`, so it owns its colour (rule 1). */
|
|
101
|
+
indicatorLabel: "font-semibold",
|
|
102
|
+
/** The check, alert or spinner inside the circle. */
|
|
103
|
+
glyph: "",
|
|
104
|
+
/** The title, description and anything else composed into the step. */
|
|
105
|
+
content: "min-w-0 gap-0.5",
|
|
106
|
+
/** Handed to a `Text.Label`. */
|
|
107
|
+
title: "",
|
|
108
|
+
/** Handed to a `Text.Caption`. */
|
|
109
|
+
description: "",
|
|
110
|
+
/** `Steps.Panel`'s row: a rail carrying the line on, and the panel beside it. */
|
|
111
|
+
panelRow: "",
|
|
112
|
+
/** The column under the indicator that carries a vertical line past a panel. */
|
|
113
|
+
rail: "items-center",
|
|
114
|
+
/** `Steps.Panel` itself — outside the tap target. */
|
|
115
|
+
panel: "min-w-0",
|
|
116
|
+
},
|
|
117
|
+
variants: {
|
|
118
|
+
orientation: {
|
|
119
|
+
horizontal: {
|
|
120
|
+
root: "flex-row items-start",
|
|
121
|
+
item: "flex-1",
|
|
122
|
+
trigger: "items-center",
|
|
123
|
+
track: "flex-row items-center self-stretch",
|
|
124
|
+
connector: "h-0.5 flex-1",
|
|
125
|
+
content: "items-center px-1",
|
|
126
|
+
panel: "px-1 pt-2",
|
|
127
|
+
title: "text-center",
|
|
128
|
+
description: "text-center",
|
|
129
|
+
},
|
|
130
|
+
vertical: {
|
|
131
|
+
root: "flex-col",
|
|
132
|
+
trigger: "flex-row",
|
|
133
|
+
track: "items-center",
|
|
134
|
+
panelRow: "flex-row",
|
|
135
|
+
panel: "flex-1",
|
|
136
|
+
connector: "min-h-4 w-0.5 flex-1",
|
|
137
|
+
content: "flex-1",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
size: {
|
|
141
|
+
sm: {
|
|
142
|
+
trigger: "gap-2",
|
|
143
|
+
panelRow: "gap-2",
|
|
144
|
+
rail: "w-6",
|
|
145
|
+
track: "gap-1",
|
|
146
|
+
indicator: "size-6",
|
|
147
|
+
indicatorLabel: "text-xs",
|
|
148
|
+
glyph: "size-icon-sm",
|
|
149
|
+
},
|
|
150
|
+
md: {
|
|
151
|
+
trigger: "gap-2.5",
|
|
152
|
+
panelRow: "gap-2.5",
|
|
153
|
+
rail: "w-8",
|
|
154
|
+
track: "gap-1.5",
|
|
155
|
+
indicator: "size-8",
|
|
156
|
+
indicatorLabel: "text-sm",
|
|
157
|
+
glyph: "size-icon-lg",
|
|
158
|
+
},
|
|
159
|
+
lg: {
|
|
160
|
+
trigger: "gap-3",
|
|
161
|
+
panelRow: "gap-3",
|
|
162
|
+
rail: "w-10",
|
|
163
|
+
track: "gap-2",
|
|
164
|
+
indicator: "size-10",
|
|
165
|
+
indicatorLabel: "text-base",
|
|
166
|
+
glyph: "size-icon-xl",
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
// The empty branches are load-bearing typing, not placeholders — `tv`
|
|
170
|
+
// derives the prop type from the declared keys. See button.variants.ts.
|
|
171
|
+
variant: { primary: {}, secondary: {} },
|
|
172
|
+
status: {
|
|
173
|
+
completed: { title: "text-foreground" },
|
|
174
|
+
current: { title: "text-foreground" },
|
|
175
|
+
upcoming: { title: "text-muted-foreground" },
|
|
176
|
+
},
|
|
177
|
+
isInvalid: { true: {}, false: {} },
|
|
178
|
+
isConnectorComplete: { true: { connector: "bg-primary" }, false: { connector: "bg-border" } },
|
|
179
|
+
isDisabled: { true: { indicator: "opacity-50", content: "opacity-50" }, false: {} },
|
|
180
|
+
isLast: { true: {}, false: {} },
|
|
181
|
+
hasPanel: { true: {}, false: {} },
|
|
182
|
+
},
|
|
183
|
+
compoundVariants: [
|
|
184
|
+
// A vertical step's content: level with the indicator at the top, and
|
|
185
|
+
// room below for the line to run down to the next step.
|
|
186
|
+
{ orientation: "vertical", size: "sm", class: { content: "pt-0.5" } },
|
|
187
|
+
{ orientation: "vertical", size: "md", class: { content: "pt-1" } },
|
|
188
|
+
{ orientation: "vertical", size: "lg", class: { content: "pt-1.5" } },
|
|
189
|
+
{ orientation: "vertical", isLast: false, size: "sm", class: { content: "pb-4" } },
|
|
190
|
+
{ orientation: "vertical", isLast: false, size: "md", class: { content: "pb-6" } },
|
|
191
|
+
{ orientation: "vertical", isLast: false, size: "lg", class: { content: "pb-8" } },
|
|
192
|
+
{ orientation: "vertical", isLast: true, class: { content: "pb-0" } },
|
|
193
|
+
// The line stops short of the next indicator by the same gap it leaves
|
|
194
|
+
// below its own, so it floats between the two rather than touching one.
|
|
195
|
+
{ orientation: "vertical", size: "sm", class: { track: "pb-1" } },
|
|
196
|
+
{ orientation: "vertical", size: "md", class: { track: "pb-1.5" } },
|
|
197
|
+
{ orientation: "vertical", size: "lg", class: { track: "pb-2" } },
|
|
198
|
+
// With a panel both gaps move down a row: the trigger's line runs to its
|
|
199
|
+
// bottom edge and on through the rail, and the space before the next step
|
|
200
|
+
// opens below the panel instead of below the title.
|
|
201
|
+
{ orientation: "vertical", hasPanel: true, class: { content: "pb-0", track: "pb-0" } },
|
|
202
|
+
{ orientation: "vertical", hasPanel: true, isLast: false, size: "sm", class: { panel: "pb-4", rail: "pb-1" } },
|
|
203
|
+
{ orientation: "vertical", hasPanel: true, isLast: false, size: "md", class: { panel: "pb-6", rail: "pb-1.5" } },
|
|
204
|
+
{ orientation: "vertical", hasPanel: true, isLast: false, size: "lg", class: { panel: "pb-8", rail: "pb-2" } },
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
variant: "primary",
|
|
208
|
+
status: "completed",
|
|
209
|
+
class: { indicator: "border-primary bg-primary", indicatorLabel: "text-primary-foreground" },
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
variant: "primary",
|
|
213
|
+
status: "current",
|
|
214
|
+
class: { indicator: "border-primary bg-transparent", indicatorLabel: "text-foreground" },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
variant: "primary",
|
|
218
|
+
status: "upcoming",
|
|
219
|
+
class: { indicator: "border-border bg-transparent", indicatorLabel: "text-muted-foreground" },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
variant: "secondary",
|
|
223
|
+
status: "completed",
|
|
224
|
+
class: { indicator: "border-secondary bg-secondary", indicatorLabel: "text-secondary-foreground" },
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
variant: "secondary",
|
|
228
|
+
status: "current",
|
|
229
|
+
class: { indicator: "border-primary bg-primary", indicatorLabel: "text-primary-foreground" },
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
variant: "secondary",
|
|
233
|
+
status: "upcoming",
|
|
234
|
+
class: { indicator: "border-muted bg-muted", indicatorLabel: "text-muted-foreground" },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
isInvalid: true,
|
|
238
|
+
class: {
|
|
239
|
+
indicator: "border-destructive bg-destructive",
|
|
240
|
+
indicatorLabel: "text-destructive-foreground",
|
|
241
|
+
title: "text-destructive",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
defaultVariants: {
|
|
246
|
+
orientation: STEPS_DEFAULT_ORIENTATION,
|
|
247
|
+
size: STEPS_DEFAULT_SIZE,
|
|
248
|
+
variant: STEPS_DEFAULT_VARIANT,
|
|
249
|
+
status: "upcoming",
|
|
250
|
+
isInvalid: false,
|
|
251
|
+
isConnectorComplete: false,
|
|
252
|
+
isDisabled: false,
|
|
253
|
+
isLast: false,
|
|
254
|
+
hasPanel: false,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
export type StepsVariantProps = VariantProps<typeof stepsVariants>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The theme token an indicator's contents are drawn in.
|
|
262
|
+
*
|
|
263
|
+
* The number is a `Text` and takes its colour as a class from the
|
|
264
|
+
* `indicatorLabel` slot; the check, the alert and the spinner are SVG and take
|
|
265
|
+
* theirs as a token through `IconDefaultsProvider`. Two mechanisms for one
|
|
266
|
+
* decision, so a test asserts every cell of this map is the class the slot
|
|
267
|
+
* emits — a drift would be a white number beside a black check.
|
|
268
|
+
*/
|
|
269
|
+
export function resolveIndicatorForeground({
|
|
270
|
+
variant,
|
|
271
|
+
status,
|
|
272
|
+
isInvalid,
|
|
273
|
+
}: {
|
|
274
|
+
variant: StepsVariant;
|
|
275
|
+
status: StepStatus;
|
|
276
|
+
isInvalid: boolean;
|
|
277
|
+
}): string {
|
|
278
|
+
if (isInvalid) return "destructive-foreground";
|
|
279
|
+
if (status === "upcoming") return "muted-foreground";
|
|
280
|
+
if (variant === "primary") return status === "completed" ? "primary-foreground" : "foreground";
|
|
281
|
+
return status === "completed" ? "secondary-foreground" : "primary-foreground";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Where a step stands, from its index, the stepper's value and its own override.
|
|
286
|
+
*
|
|
287
|
+
* Before the value is `completed`, at it is `current`, after it `upcoming`.
|
|
288
|
+
* `completed: true` wins everywhere — a finished flow marks its last step done
|
|
289
|
+
* while still sitting on it. `completed: false` un-completes a step the value
|
|
290
|
+
* has passed (a skipped optional step) but never demotes the current one, which
|
|
291
|
+
* would leave a stepper with nowhere the user is.
|
|
292
|
+
*
|
|
293
|
+
* Pure, so the whole matrix is reachable from `bun test`.
|
|
294
|
+
*/
|
|
295
|
+
export function resolveStepStatus({
|
|
296
|
+
step,
|
|
297
|
+
value,
|
|
298
|
+
completed,
|
|
299
|
+
}: {
|
|
300
|
+
step: number;
|
|
301
|
+
value: number;
|
|
302
|
+
completed?: boolean;
|
|
303
|
+
}): StepStatus {
|
|
304
|
+
if (completed === true) return "completed";
|
|
305
|
+
if (step === value) return "current";
|
|
306
|
+
if (completed === false) return "upcoming";
|
|
307
|
+
return step < value ? "completed" : "upcoming";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Whether the line leaving `step` is filled.
|
|
312
|
+
*
|
|
313
|
+
* Read off the value alone, never off either step's status: the line is drawn
|
|
314
|
+
* as two halves in two different items, and each half can see only its own
|
|
315
|
+
* step's overrides. The value is the one thing both ends agree on.
|
|
316
|
+
*/
|
|
317
|
+
export function isConnectorComplete(step: number, value: number): boolean {
|
|
318
|
+
return step < value;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** What one side of an indicator's track holds. */
|
|
322
|
+
export type StepConnectorSlot = "line" | "spacer" | "none";
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Which connector halves a step draws.
|
|
326
|
+
*
|
|
327
|
+
* Horizontal steps always draw both sides so every indicator stays centred in
|
|
328
|
+
* its equal-width column — the outer ends become transparent spacers rather
|
|
329
|
+
* than disappearing. Vertical steps draw one line down from the indicator, and
|
|
330
|
+
* none from the last.
|
|
331
|
+
*
|
|
332
|
+
* This is how the connectors are "drawn for you": the root counts its items,
|
|
333
|
+
* so no caller places a separator, and none can be left dangling after the
|
|
334
|
+
* last step.
|
|
335
|
+
*/
|
|
336
|
+
export function resolveStepConnectors({
|
|
337
|
+
step,
|
|
338
|
+
count,
|
|
339
|
+
orientation,
|
|
340
|
+
}: {
|
|
341
|
+
step: number;
|
|
342
|
+
count: number;
|
|
343
|
+
orientation: StepsOrientation;
|
|
344
|
+
}): { leading: StepConnectorSlot; trailing: StepConnectorSlot } {
|
|
345
|
+
const isFirst = step <= 0;
|
|
346
|
+
const isLast = step >= count - 1;
|
|
347
|
+
if (orientation === "vertical") return { leading: "none", trailing: isLast ? "none" : "line" };
|
|
348
|
+
return { leading: isFirst ? "spacer" : "line", trailing: isLast ? "spacer" : "line" };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Whether the stepper takes presses at all.
|
|
353
|
+
*
|
|
354
|
+
* A controlled `value` with no `onValueChange` is a progress display: nothing a
|
|
355
|
+
* press could do would stick, so its steps render as plain views and are not
|
|
356
|
+
* announced as buttons. `isReadOnly` states it outright either way.
|
|
357
|
+
*/
|
|
358
|
+
export function resolveStepsReadOnly({
|
|
359
|
+
isControlled,
|
|
360
|
+
hasOnValueChange,
|
|
361
|
+
isReadOnly,
|
|
362
|
+
}: {
|
|
363
|
+
isControlled: boolean;
|
|
364
|
+
hasOnValueChange: boolean;
|
|
365
|
+
isReadOnly?: boolean;
|
|
366
|
+
}): boolean {
|
|
367
|
+
return isReadOnly ?? (isControlled && !hasOnValueChange);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* How one step responds to touch.
|
|
372
|
+
*
|
|
373
|
+
* `static` renders a plain view — the stepper is read-only. `pressable` moves the
|
|
374
|
+
* value there. `blocked` is still a pressable, announced disabled: the step is
|
|
375
|
+
* disabled, loading, or ahead of the value in a linear flow, where the only way
|
|
376
|
+
* forward is the flow's own Continue button.
|
|
377
|
+
*/
|
|
378
|
+
export type StepInteraction = "static" | "pressable" | "blocked";
|
|
379
|
+
|
|
380
|
+
export function resolveStepInteraction({
|
|
381
|
+
isReadOnly,
|
|
382
|
+
isLinear,
|
|
383
|
+
isDisabled,
|
|
384
|
+
isLoading,
|
|
385
|
+
step,
|
|
386
|
+
value,
|
|
387
|
+
}: {
|
|
388
|
+
isReadOnly: boolean;
|
|
389
|
+
isLinear: boolean;
|
|
390
|
+
isDisabled: boolean;
|
|
391
|
+
isLoading: boolean;
|
|
392
|
+
step: number;
|
|
393
|
+
value: number;
|
|
394
|
+
}): StepInteraction {
|
|
395
|
+
if (isReadOnly) return "static";
|
|
396
|
+
if (isDisabled || isLoading) return "blocked";
|
|
397
|
+
if (isLinear && step > value) return "blocked";
|
|
398
|
+
return "pressable";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Whether a press should write a new value. Re-pressing the current step is not
|
|
403
|
+
* a change, so the caller is never re-notified with the value it already holds.
|
|
404
|
+
*/
|
|
405
|
+
export function shouldEmitStep(current: number, next: number): boolean {
|
|
406
|
+
return current !== next;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const STATUS_WORD: Record<StepStatus, string> = { completed: "completed", current: "current", upcoming: "upcoming" };
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* What VoiceOver and TalkBack read after a step's title.
|
|
413
|
+
*
|
|
414
|
+
* Handed to `accessibilityValue.text`, so the title stays the accessible name
|
|
415
|
+
* and this follows it: "Payment, Step 2 of 3, completed". One-based, because
|
|
416
|
+
* that is what the indicator draws. A count of zero means the root found no
|
|
417
|
+
* items to count — they sit behind a wrapper — and "of 0" would be a lie.
|
|
418
|
+
*/
|
|
419
|
+
export function resolveStepAccessibilityValue({
|
|
420
|
+
step,
|
|
421
|
+
count,
|
|
422
|
+
status,
|
|
423
|
+
isInvalid = false,
|
|
424
|
+
isLoading = false,
|
|
425
|
+
}: {
|
|
426
|
+
step: number;
|
|
427
|
+
count: number;
|
|
428
|
+
status: StepStatus;
|
|
429
|
+
isInvalid?: boolean;
|
|
430
|
+
isLoading?: boolean;
|
|
431
|
+
}): string {
|
|
432
|
+
const position = count > 0 ? `Step ${step + 1} of ${count}` : `Step ${step + 1}`;
|
|
433
|
+
const parts = [position, STATUS_WORD[status]];
|
|
434
|
+
if (isInvalid) parts.push("has an error");
|
|
435
|
+
if (isLoading) parts.push("loading");
|
|
436
|
+
return parts.join(", ");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** What the indicator draws inside its circle. */
|
|
440
|
+
export type StepIndicatorGlyph =
|
|
441
|
+
| { kind: "spinner" }
|
|
442
|
+
| { kind: "alert" }
|
|
443
|
+
| { kind: "check" }
|
|
444
|
+
| { kind: "number"; label: string };
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* What an indicator draws: a spinner while loading, an alert while invalid, a
|
|
448
|
+
* check once completed, and otherwise the step's one-based number.
|
|
449
|
+
*
|
|
450
|
+
* Loading outranks invalid because it is the more recent fact — a step that
|
|
451
|
+
* failed and is being retried is retrying.
|
|
452
|
+
*/
|
|
453
|
+
export function resolveIndicatorGlyph({
|
|
454
|
+
step,
|
|
455
|
+
status,
|
|
456
|
+
isInvalid,
|
|
457
|
+
isLoading,
|
|
458
|
+
}: {
|
|
459
|
+
step: number;
|
|
460
|
+
status: StepStatus;
|
|
461
|
+
isInvalid: boolean;
|
|
462
|
+
isLoading: boolean;
|
|
463
|
+
}): StepIndicatorGlyph {
|
|
464
|
+
if (isLoading) return { kind: "spinner" };
|
|
465
|
+
if (isInvalid) return { kind: "alert" };
|
|
466
|
+
if (status === "completed") return { kind: "check" };
|
|
467
|
+
return { kind: "number", label: String(step + 1) };
|
|
468
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Surface
|
|
2
|
+
|
|
3
|
+
A rounded container on one of the theme's surface fills — the plane a card, an
|
|
4
|
+
alert or a settings panel is drawn on. A single styled `View` with a context, so
|
|
5
|
+
components built on it (`Card`, `Alert`) can read the plane they sit on.
|
|
6
|
+
|
|
7
|
+
`import { Surface } from "@delacour/react-native-ui/surface";`
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `index.ts` | → `@delacour/react-native-ui/surface` |
|
|
14
|
+
| `surface.tsx` | `Surface` |
|
|
15
|
+
| `surface.context.tsx` | `SurfaceProvider`, `useSurface()`, `useSurfaceContext()` |
|
|
16
|
+
| `surface.variants.ts` | The `tv()`, `SURFACE_FOREGROUND_TOKENS`, and the two pure resolvers — no RN imports |
|
|
17
|
+
| `surface.variants.test.ts` | |
|
|
18
|
+
|
|
19
|
+
## Design
|
|
20
|
+
|
|
21
|
+
- **The API is `variant`, `padding`, `className` and `children`, and it stays
|
|
22
|
+
that small.** `Card` and `Alert` are built on this; every prop added here is
|
|
23
|
+
one they inherit and have to document. Anything else a caller needs — a
|
|
24
|
+
margin, a tinted border, a fixed height — is a class, merged last through the
|
|
25
|
+
`tv()`.
|
|
26
|
+
- **Variants**: `default`, `secondary`, `tertiary`, `transparent`.
|
|
27
|
+
**Padding**: `none`, `sm`, `md`, `lg` — `p-0`, `p-3`, `p-4`, `p-6`. `md`
|
|
28
|
+
rather than `default` for the middle step, so one step name means "the
|
|
29
|
+
default step" across the library.
|
|
30
|
+
- **The variants are a ladder, not a palette.** `default` is `bg-card` with a
|
|
31
|
+
`border-border` hairline; `secondary` and `tertiary` are the fills beneath it;
|
|
32
|
+
`transparent` keeps the padding and corner and paints nothing. Only `default`
|
|
33
|
+
colours its border: in light `--card` sits a percent above `--background`, and
|
|
34
|
+
without the hairline a card on a page is white on near-white. Every variant
|
|
35
|
+
still reserves the same one-point `border`, so changing variant never moves
|
|
36
|
+
the content.
|
|
37
|
+
- **A surface that names no variant steps from the one it sits in.**
|
|
38
|
+
`resolveSurfaceVariant` makes a top-level surface `default`, and a nested one
|
|
39
|
+
`default → secondary → tertiary → secondary`. `tertiary` is *quieter* than
|
|
40
|
+
`secondary` — half a step back toward the page, see `theme.css` — so a strict
|
|
41
|
+
ascent would run out of fills at the fourth level; what the resolver
|
|
42
|
+
guarantees, and the tests assert, is that a surface never resolves to the fill
|
|
43
|
+
it sits on. An explicit `variant` always wins.
|
|
44
|
+
- **A transparent surface passes its parent's plane through.**
|
|
45
|
+
`resolveSurfacePlane` publishes the plane a surface's children sit on, and for
|
|
46
|
+
`transparent` that is whatever it sits on itself. Without it, a surface inside
|
|
47
|
+
a transparent wrapper inside a card would restart at `default` — the card's
|
|
48
|
+
own fill — and disappear into it.
|
|
49
|
+
- **The corner is `rounded-lg`, with `border-continuous`.** `rounded-lg` is the
|
|
50
|
+
step `--radius` names, which is the card corner — see the corner-scale note in
|
|
51
|
+
[the package AGENTS.md](../../../AGENTS.md). `border-continuous` is Uniwind's
|
|
52
|
+
utility for iOS's `borderCurve: "continuous"`, the squircle rather than a
|
|
53
|
+
circular arc; Android ignores it. It is registered in `TW_MERGE_CONFIG` as a
|
|
54
|
+
class group of its own — unregistered, tailwind-merge read it as a border
|
|
55
|
+
*colour* and dropped it against `border-border`.
|
|
56
|
+
- **`padding="none"` clips, and nothing else does.** It is the step for content
|
|
57
|
+
bled to the edge — an image, a chart — and that content has to take the
|
|
58
|
+
corner with it. Clipping at every padding would crop anything a child draws
|
|
59
|
+
past its own box.
|
|
60
|
+
- **No shadow.** Nothing in this package casts one — React Native's shadow
|
|
61
|
+
props disagree between platforms — so depth here is carried by fill, and the
|
|
62
|
+
tests assert no variant emits one. A caller who wants one writes `shadow-md`
|
|
63
|
+
and gets the theme's `--shadow-md`.
|
|
64
|
+
- **No text treatment.** The root carries no `text-*` (rule 1), and the surface
|
|
65
|
+
publishes no `TextClassProvider` either: `Text`'s presets set their own
|
|
66
|
+
colour, so the cascade would reach only a bare `Text` — and a provider marks
|
|
67
|
+
every `Text` beneath it as nested, which strips `Text.Code` of its chip.
|
|
68
|
+
`SURFACE_FOREGROUND_TOKENS` maps each fill to its `X-foreground` token for a
|
|
69
|
+
caller that wants it, as a class or through `useThemeColor`.
|
|
70
|
+
- **No role.** A surface is layout, not a landmark; it adds nothing a screen
|
|
71
|
+
reader could announce. `ViewProps` pass through, so a caller that does mean
|
|
72
|
+
one — `accessibilityRole="summary"` — sets it.
|
|
73
|
+
|
|
74
|
+
- **`material` is what the surface is made of, separate from which plane it fills.** `flat` is the
|
|
75
|
+
default and draws nothing extra. `etched` adds `shadow-etched dark:shadow-etched-dark`: a one-pixel
|
|
76
|
+
highlight set into the edge (a darkening on the bottom in light, a lightening on the top in dark)
|
|
77
|
+
and a very soft drop. `tray` is the muted frame that holds panels — `bg-muted/70`, `p-1`,
|
|
78
|
+
`rounded-2xl`, whatever `padding` says. A tray paints its own fill, so it resets the plane to
|
|
79
|
+
`null` (`resolveSurfacePlane`) and a surface inside it resolves to the card fill, not the next rung
|
|
80
|
+
down; `SurfaceContext.material` is how that panel knows to take `rounded-xl`, one step under the
|
|
81
|
+
tray's corner. A single `shadow-*` utility sets one `box-shadow`, which is why light and dark are
|
|
82
|
+
two tokens joined by `dark:` and not one composed class. Opt-in: nothing in the default look uses it.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { Surface, type SurfaceProps } from "./surface";
|
|
2
|
+
export { type SurfaceContextValue, SurfaceProvider, useSurface, useSurfaceContext } from "./surface.context";
|
|
3
|
+
export {
|
|
4
|
+
resolveSurfacePlane,
|
|
5
|
+
resolveSurfaceVariant,
|
|
6
|
+
SURFACE_FILLED_VARIANTS,
|
|
7
|
+
SURFACE_FOREGROUND_TOKENS,
|
|
8
|
+
SURFACE_PADDINGS,
|
|
9
|
+
SURFACE_VARIANTS,
|
|
10
|
+
type SurfacePadding,
|
|
11
|
+
type SurfacePlane,
|
|
12
|
+
type SurfaceVariant,
|
|
13
|
+
type SurfaceVariantProps,
|
|
14
|
+
surfaceVariants,
|
|
15
|
+
} from "./surface.variants";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { SurfaceMaterial, SurfacePlane, SurfaceVariant } from "./surface.variants";
|
|
3
|
+
|
|
4
|
+
export type SurfaceContextValue = {
|
|
5
|
+
/** The fill the enclosing surface resolved to — explicit, or stepped from its own parent. */
|
|
6
|
+
variant: SurfaceVariant;
|
|
7
|
+
/**
|
|
8
|
+
* The fill content inside the enclosing surface sits on. The same as
|
|
9
|
+
* `variant` unless that is `transparent`, which paints nothing and passes the
|
|
10
|
+
* plane beneath it through — `null` when there is none.
|
|
11
|
+
*/
|
|
12
|
+
plane: SurfacePlane | null;
|
|
13
|
+
/** What the surface is made of. A `tray` tells the panels inside it to take the inner corner. */
|
|
14
|
+
material: SurfaceMaterial;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SurfaceContext = createContext<SurfaceContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Supplies the enclosing surface's fill to its subtree.
|
|
21
|
+
*
|
|
22
|
+
* Lives in its own module, importing nothing but `surface.variants`, so a
|
|
23
|
+
* component built on a surface can read it without importing `../surface` and
|
|
24
|
+
* closing a cycle through `surface.tsx`. See AGENTS.md rule 3.
|
|
25
|
+
*/
|
|
26
|
+
export function SurfaceProvider({
|
|
27
|
+
value,
|
|
28
|
+
children,
|
|
29
|
+
}: {
|
|
30
|
+
value: SurfaceContextValue;
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}): ReactElement {
|
|
33
|
+
return <SurfaceContext value={value}>{children}</SurfaceContext>;
|
|
34
|
+
}
|
|
35
|
+
SurfaceProvider.displayName = "DelacourUI.Surface.Provider";
|
|
36
|
+
|
|
37
|
+
/** The enclosing surface's context, or null outside a `<Surface>`. */
|
|
38
|
+
export function useSurfaceContext(): SurfaceContextValue | null {
|
|
39
|
+
return use(SurfaceContext);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads the enclosing surface's fill.
|
|
44
|
+
*
|
|
45
|
+
* Lets a custom child match the plane it sits on — pick an icon colour from
|
|
46
|
+
* `SURFACE_FOREGROUND_TOKENS[plane]`, or a divider that reads against it —
|
|
47
|
+
* without the surface passing props down. Throws outside a `<Surface>`; use
|
|
48
|
+
* {@link useSurfaceContext} where the enclosing surface is optional.
|
|
49
|
+
*/
|
|
50
|
+
export function useSurface(): SurfaceContextValue {
|
|
51
|
+
const context = useSurfaceContext();
|
|
52
|
+
if (!context) {
|
|
53
|
+
throw new Error("useSurface must be called inside a <Surface>.");
|
|
54
|
+
}
|
|
55
|
+
return context;
|
|
56
|
+
}
|