@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
|
@@ -76,6 +76,10 @@ export type ButtonGroupContextValue = {
|
|
|
76
76
|
isDisabled?: boolean;
|
|
77
77
|
/** Press treatment for members. Unset falls back to `fade` — see `resolveButtonFeedback`. */
|
|
78
78
|
feedback?: PressableFeedback;
|
|
79
|
+
/** Members join into one shape. False, each keeps its own corner and the run takes a gap. */
|
|
80
|
+
isAttached: boolean;
|
|
81
|
+
/** The group spans its parent. Horizontal members then split it — see `isStretched`. */
|
|
82
|
+
isFullWidth: boolean;
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
export type ButtonGroupItemContextValue = ButtonGroupContextValue & {
|
|
@@ -83,6 +87,12 @@ export type ButtonGroupItemContextValue = ButtonGroupContextValue & {
|
|
|
83
87
|
position: ButtonGroupPosition;
|
|
84
88
|
/** Overlap the member before it, so the shared edge is drawn once. */
|
|
85
89
|
isSeamed: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Take an equal share of a full-width horizontal run — `flex-1 basis-0`.
|
|
92
|
+
* A custom member that should split the run reads this; one that should
|
|
93
|
+
* keep its content width ignores it, as `Button.Group.Text` does.
|
|
94
|
+
*/
|
|
95
|
+
isStretched: boolean;
|
|
86
96
|
};
|
|
87
97
|
|
|
88
98
|
const ButtonGroupContext = createContext<ButtonGroupContextValue | null>(null);
|
|
@@ -7,6 +7,7 @@ import { type ButtonContextValue, ButtonProvider, useButtonGroupItemContext } fr
|
|
|
7
7
|
import {
|
|
8
8
|
BUTTON_FOREGROUND_TOKEN,
|
|
9
9
|
type ButtonLayout,
|
|
10
|
+
type ButtonMaterial,
|
|
10
11
|
type ButtonSize,
|
|
11
12
|
type ButtonSpinnerPlacement,
|
|
12
13
|
type ButtonVariant,
|
|
@@ -23,6 +24,8 @@ import { ButtonStartContent } from "./button-start-content";
|
|
|
23
24
|
|
|
24
25
|
export type ButtonProps = Omit<PressableProps, "busy" | "children" | "disabled" | "pressedOpacity" | "pressedScale"> & {
|
|
25
26
|
variant?: ButtonVariant;
|
|
27
|
+
/** `flat` by default; `etched` sets a highlight into the fill's edge. */
|
|
28
|
+
material?: ButtonMaterial;
|
|
26
29
|
/** Height, label step, icon step, corner and padding on one axis. `icon-*` is a square. */
|
|
27
30
|
size?: ButtonSize;
|
|
28
31
|
isDisabled?: boolean;
|
|
@@ -37,6 +40,7 @@ export type ButtonProps = Omit<PressableProps, "busy" | "children" | "disabled"
|
|
|
37
40
|
|
|
38
41
|
function ButtonRoot({
|
|
39
42
|
variant,
|
|
43
|
+
material = "flat",
|
|
40
44
|
size,
|
|
41
45
|
isDisabled,
|
|
42
46
|
isLoading = false,
|
|
@@ -74,6 +78,8 @@ function ButtonRoot({
|
|
|
74
78
|
isDisabled: resolvedIsDisabled,
|
|
75
79
|
isLoading,
|
|
76
80
|
isSeamed: item?.isSeamed ?? false,
|
|
81
|
+
isStretched: item?.isStretched ?? false,
|
|
82
|
+
material,
|
|
77
83
|
orientation: item?.orientation ?? "horizontal",
|
|
78
84
|
size: resolvedSize,
|
|
79
85
|
variant: resolvedVariant,
|
|
@@ -96,7 +102,7 @@ function ButtonRoot({
|
|
|
96
102
|
busy={isLoading}
|
|
97
103
|
className={slots.root({ className })}
|
|
98
104
|
disabled={resolvedIsDisabled}
|
|
99
|
-
feedback={resolveButtonFeedback(feedback, item?.feedback, item !== null)}
|
|
105
|
+
feedback={resolveButtonFeedback(feedback, item?.feedback, item !== null, item?.isAttached)}
|
|
100
106
|
{...props}
|
|
101
107
|
>
|
|
102
108
|
<IconDefaultsProvider value={iconDefaults}>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
2
3
|
import { BUTTON_RADIUS_TOKENS, ICON_SIZE_TOKENS } from "../../styles/tokens";
|
|
3
4
|
import {
|
|
4
5
|
BUTTON_FOREGROUND_TOKEN,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
BUTTON_GROUP_SEPARATOR_ORIENTATION,
|
|
8
9
|
BUTTON_ICON_SIZES,
|
|
9
10
|
BUTTON_LABEL_SIZES,
|
|
11
|
+
BUTTON_MATERIALS,
|
|
10
12
|
BUTTON_SIZES,
|
|
11
13
|
BUTTON_SPINNER_PLACEMENTS,
|
|
12
14
|
BUTTON_VARIANTS,
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
resolveButtonFeedback,
|
|
17
19
|
resolveButtonLayout,
|
|
18
20
|
resolveGroupedButtonSize,
|
|
21
|
+
resolveGroupMemberStretch,
|
|
19
22
|
resolveGroupPositions,
|
|
20
23
|
resolveGroupSeams,
|
|
21
24
|
resolveSpinnerSwapIndex,
|
|
@@ -809,3 +812,190 @@ describe("resolveGroupedButtonSize", () => {
|
|
|
809
812
|
}
|
|
810
813
|
});
|
|
811
814
|
});
|
|
815
|
+
|
|
816
|
+
describe("the etched button material", () => {
|
|
817
|
+
test("flat is the default and adds no shadow", () => {
|
|
818
|
+
for (const variant of BUTTON_VARIANTS) {
|
|
819
|
+
expect(buttonVariants({ variant }).root()).toBe(buttonVariants({ variant, material: "flat" }).root());
|
|
820
|
+
expect(buttonVariants({ variant }).root()).not.toMatch(/shadow/);
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
test("etched primary carries the white inset highlight", () => {
|
|
825
|
+
const cls = buttonVariants({ material: "etched", variant: "primary" }).root();
|
|
826
|
+
expect(cls).toContain("shadow-etched-primary");
|
|
827
|
+
expect(cls).not.toContain("shadow-etched ");
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
test("etched filled and outlined buttons carry the light and dark edge", () => {
|
|
831
|
+
for (const variant of ["secondary", "tertiary", "outline"] as const) {
|
|
832
|
+
const cls = buttonVariants({ material: "etched", variant }).root();
|
|
833
|
+
expect(cls).toContain("shadow-etched");
|
|
834
|
+
expect(cls).toContain("dark:shadow-etched-dark");
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// A ghost has no surface to lift, and a destructive fill is a signal colour
|
|
839
|
+
// whose highlight would read as a second state.
|
|
840
|
+
test("ghost and destructive variants stay flat", () => {
|
|
841
|
+
for (const variant of ["ghost", "destructive", "destructive-soft"] as const) {
|
|
842
|
+
expect(buttonVariants({ material: "etched", variant }).root()).not.toMatch(/shadow/);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
test("lists every material", () => {
|
|
847
|
+
expect(BUTTON_MATERIALS).toEqual(["flat", "etched"]);
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
describe("a detached group", () => {
|
|
852
|
+
test("places every member on its own", () => {
|
|
853
|
+
// `only` is the lone member's position and draws the lone button's corner,
|
|
854
|
+
// so a spaced run is a row of whole buttons rather than a joined shape with
|
|
855
|
+
// gaps punched in it.
|
|
856
|
+
expect(resolveGroupPositions([true, true, true], { isAttached: false })).toEqual(["only", "only", "only"]);
|
|
857
|
+
expect(resolveGroupPositions([true, false, true], { isAttached: false })).toEqual(["only", null, "only"]);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
test("still skips a separator", () => {
|
|
861
|
+
expect(resolveGroupPositions([false, true, false], { isAttached: false })).toEqual([null, "only", null]);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("joins by default", () => {
|
|
865
|
+
expect(resolveGroupPositions([true, true], { isAttached: true })).toEqual(["first", "last"]);
|
|
866
|
+
expect(resolveGroupPositions([true, true], {})).toEqual(["first", "last"]);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test("overlaps nothing", () => {
|
|
870
|
+
// There is no shared edge to draw once, and a point of overlap would eat
|
|
871
|
+
// into the gap instead.
|
|
872
|
+
for (const shape of SHAPES) {
|
|
873
|
+
expect(resolveGroupSeams(shape, { isAttached: false }).every((seamed) => !seamed)).toBe(true);
|
|
874
|
+
}
|
|
875
|
+
expect(resolveGroupSeams([true, true, true], { isAttached: true })).toEqual([false, true, true]);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("draws each member's own corner", () => {
|
|
879
|
+
for (const size of BUTTON_SIZES) {
|
|
880
|
+
const step = size.startsWith("icon-") ? size.slice("icon-".length) : size;
|
|
881
|
+
const cls = buttonVariants({ groupPosition: "only", size }).root();
|
|
882
|
+
expect(cls).toContain(`rounded-button-${step}`);
|
|
883
|
+
expect(cls).not.toMatch(/\brounded-[sebt]-/);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("spaces its members with a gap on either axis", () => {
|
|
888
|
+
for (const orientation of BUTTON_GROUP_ORIENTATIONS) {
|
|
889
|
+
expect(buttonVariants({ isAttached: false, orientation }).group()).toMatch(/\bgap-2\b/);
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
test("an attached group still holds no gap", () => {
|
|
894
|
+
for (const orientation of BUTTON_GROUP_ORIENTATIONS) {
|
|
895
|
+
expect(buttonVariants({ isAttached: true, orientation }).group()).not.toMatch(/\bgap-/);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
describe("resolveButtonFeedback in a detached group", () => {
|
|
901
|
+
test("falls back to the lone button's scale", () => {
|
|
902
|
+
// Tearing a seam is impossible with no seam, so the group rung is dropped.
|
|
903
|
+
expect(resolveButtonFeedback(undefined, undefined, true, false)).toBe("scale");
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
test("still fades when attached", () => {
|
|
907
|
+
expect(resolveButtonFeedback(undefined, undefined, true, true)).toBe("fade");
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test("defaults to attached, which is what a group is", () => {
|
|
911
|
+
expect(resolveButtonFeedback(undefined, undefined, true)).toBe("fade");
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("honours an explicit `none` either way", () => {
|
|
915
|
+
for (const isAttached of [true, false]) {
|
|
916
|
+
expect(resolveButtonFeedback("none", undefined, true, isAttached)).toBe("none");
|
|
917
|
+
expect(resolveButtonFeedback(undefined, "none", true, isAttached)).toBe("none");
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test("still lets the group or the member name a treatment", () => {
|
|
922
|
+
expect(resolveButtonFeedback(undefined, "fade", true, false)).toBe("fade");
|
|
923
|
+
expect(resolveButtonFeedback("scale-fade", "fade", true, false)).toBe("scale-fade");
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
describe("a full-width group", () => {
|
|
928
|
+
test("spans its parent instead of hugging its content", () => {
|
|
929
|
+
const cls = buttonVariants({ isFullWidth: true, orientation: "horizontal" }).group();
|
|
930
|
+
expect(cls).toMatch(/\bw-full\b/);
|
|
931
|
+
expect(cls).toMatch(/\bself-stretch\b/);
|
|
932
|
+
expect(cls).not.toMatch(/\bself-start\b/);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test("fills the width when vertical too", () => {
|
|
936
|
+
const cls = buttonVariants({ isFullWidth: true, orientation: "vertical" }).group();
|
|
937
|
+
expect(cls).toMatch(/\bw-full\b/);
|
|
938
|
+
expect(cls).toMatch(/\bself-stretch\b/);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test("hugs its content by default", () => {
|
|
942
|
+
const cls = buttonVariants({ orientation: "horizontal" }).group();
|
|
943
|
+
expect(cls).toMatch(/\bself-start\b/);
|
|
944
|
+
expect(cls).not.toMatch(/\bw-full\b/);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
test("keeps a gap when it is also detached", () => {
|
|
948
|
+
const cls = buttonVariants({ isAttached: false, isFullWidth: true }).group();
|
|
949
|
+
expect(cls).toMatch(/\bgap-2\b/);
|
|
950
|
+
expect(cls).toMatch(/\bw-full\b/);
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
describe("a stretched member", () => {
|
|
955
|
+
test("splits the run equally rather than by content", () => {
|
|
956
|
+
// `basis-0` is the half that matters: with `flex-1` alone Yoga shares out
|
|
957
|
+
// only the space left over, and the widths still follow the labels.
|
|
958
|
+
const cls = buttonVariants({ isStretched: true }).root();
|
|
959
|
+
expect(cls).toMatch(/\bflex-1\b/);
|
|
960
|
+
expect(cls).toMatch(/\bbasis-0\b/);
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
test("does not stretch by default", () => {
|
|
964
|
+
const cls = buttonVariants().root();
|
|
965
|
+
expect(cls).not.toMatch(/\bflex-1\b/);
|
|
966
|
+
expect(cls).not.toMatch(/\bbasis-0\b/);
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
test("yields to a caller's flex", () => {
|
|
970
|
+
const slot = buttonVariants({ isStretched: true }).root({ className: "flex-none" });
|
|
971
|
+
expect(slot).toMatch(/\bflex-none\b/);
|
|
972
|
+
expect(slot).not.toMatch(/\bflex-1\b/);
|
|
973
|
+
const merged = cn(buttonVariants({ isStretched: true }).root(), "flex-none");
|
|
974
|
+
expect(merged).toMatch(/\bflex-none\b/);
|
|
975
|
+
expect(merged).not.toMatch(/\bflex-1\b/);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test("keeps its own corner and height", () => {
|
|
979
|
+
const cls = buttonVariants({ groupPosition: "first", isStretched: true, size: "md" }).root();
|
|
980
|
+
expect(cls).toContain("h-button-md");
|
|
981
|
+
expect(cls).toContain("rounded-s-button-md");
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
describe("resolveGroupMemberStretch", () => {
|
|
986
|
+
test("stretches members of a full-width horizontal run", () => {
|
|
987
|
+
expect(resolveGroupMemberStretch({ isFullWidth: true, orientation: "horizontal" })).toBe(true);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
test("leaves a vertical run to the cross axis it already stretches on", () => {
|
|
991
|
+
// `basis-0` on the main axis of a column with no definite height would
|
|
992
|
+
// collapse every member to nothing.
|
|
993
|
+
expect(resolveGroupMemberStretch({ isFullWidth: true, orientation: "vertical" })).toBe(false);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
test("stretches nothing by default", () => {
|
|
997
|
+
for (const orientation of BUTTON_GROUP_ORIENTATIONS) {
|
|
998
|
+
expect(resolveGroupMemberStretch({ isFullWidth: false, orientation })).toBe(false);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
@@ -13,6 +13,14 @@ export const BUTTON_VARIANTS = [
|
|
|
13
13
|
] as const;
|
|
14
14
|
|
|
15
15
|
/** Sizes that hold a label. Horizontal padding; the width comes from the content. */
|
|
16
|
+
/**
|
|
17
|
+
* `flat` is the library default. `etched` sets the highlight into the fill's
|
|
18
|
+
* edge — a white inset top edge on `primary`, the light/dark edge on the quiet
|
|
19
|
+
* fills. Ghost and destructive buttons stay flat: a ghost has no surface to
|
|
20
|
+
* lift, and a signal colour does not take a second highlight.
|
|
21
|
+
*/
|
|
22
|
+
export const BUTTON_MATERIALS = ["flat", "etched"] as const;
|
|
23
|
+
|
|
16
24
|
export const BUTTON_LABEL_SIZES = ["sm", "md", "lg"] as const;
|
|
17
25
|
|
|
18
26
|
/** Sizes with a square footprint, for a button whose only content is an icon. */
|
|
@@ -33,6 +41,7 @@ export const BUTTON_GROUP_ORIENTATIONS = ["horizontal", "vertical"] as const;
|
|
|
33
41
|
|
|
34
42
|
export const BUTTON_GROUP_POSITIONS = ["first", "middle", "last", "only"] as const;
|
|
35
43
|
|
|
44
|
+
export type ButtonMaterial = (typeof BUTTON_MATERIALS)[number];
|
|
36
45
|
export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
|
|
37
46
|
export type ButtonLabelSize = (typeof BUTTON_LABEL_SIZES)[number];
|
|
38
47
|
export type ButtonIconSize = (typeof BUTTON_ICON_SIZES)[number];
|
|
@@ -41,6 +50,11 @@ export type ButtonSpinnerPlacement = (typeof BUTTON_SPINNER_PLACEMENTS)[number];
|
|
|
41
50
|
export type ButtonGroupOrientation = (typeof BUTTON_GROUP_ORIENTATIONS)[number];
|
|
42
51
|
export type ButtonGroupPosition = (typeof BUTTON_GROUP_POSITIONS)[number];
|
|
43
52
|
|
|
53
|
+
/** How a group shapes its run. Omitted, a group joins — that is what a group is. */
|
|
54
|
+
export type ButtonGroupShapeOptions = {
|
|
55
|
+
isAttached?: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
44
58
|
/** A member's place in its group, or `none` for a button standing on its own. */
|
|
45
59
|
export type ButtonGroupSlotPosition = ButtonGroupPosition | "none";
|
|
46
60
|
|
|
@@ -135,8 +149,9 @@ export const buttonVariants = tv({
|
|
|
135
149
|
/**
|
|
136
150
|
* A `Button.Group`'s box.
|
|
137
151
|
*
|
|
138
|
-
* Holds no `gap` and no `overflow-hidden
|
|
139
|
-
* load-bearing. A gap is the seam
|
|
152
|
+
* Holds no `gap` while attached and no `overflow-hidden` ever, and both
|
|
153
|
+
* absences are load-bearing. A gap is the seam a joined run exists to
|
|
154
|
+
* close — only `isAttached: false` adds one, where there is no seam. A clip
|
|
140
155
|
* would square off the very corners the position compounds just rounded,
|
|
141
156
|
* and would have to restate the group's own corner to avoid it — while the
|
|
142
157
|
* members already square themselves, which is the whole point.
|
|
@@ -192,10 +207,26 @@ export const buttonVariants = tv({
|
|
|
192
207
|
horizontal: { group: "flex-row self-start" },
|
|
193
208
|
vertical: { group: "flex-col" },
|
|
194
209
|
},
|
|
210
|
+
// Declared after `orientation` on purpose: `tv` merges in declaration
|
|
211
|
+
// order, so `self-stretch` here replaces the horizontal run's `self-start`
|
|
212
|
+
// rather than losing to it.
|
|
213
|
+
/** A `Button.Group` that spans its parent. The members split it — see `isStretched`. */
|
|
214
|
+
isFullWidth: { true: { group: "w-full self-stretch" }, false: {} },
|
|
215
|
+
/** A `Button.Group` whose members join into one shape. Detached, they stand apart by a gap. */
|
|
216
|
+
isAttached: { true: {}, false: { group: "gap-2" } },
|
|
217
|
+
/**
|
|
218
|
+
* A member of a full-width horizontal run, taking an equal share of it.
|
|
219
|
+
*
|
|
220
|
+
* `basis-0` is the half that matters. With `flex-1` alone Yoga shares out
|
|
221
|
+
* only the space left over after each member's content width, so the
|
|
222
|
+
* widths still follow the labels; from a zero basis every share is equal.
|
|
223
|
+
*/
|
|
224
|
+
isStretched: { true: { root: "flex-1 basis-0" }, false: {} },
|
|
195
225
|
// `middle` is the one position whose corner needs neither the size nor the
|
|
196
226
|
// orientation, so it lands here rather than in six compound cells that
|
|
197
227
|
// would all say the same word.
|
|
198
228
|
groupPosition: { none: {}, first: {}, middle: { root: "rounded-none" }, last: {}, only: {} },
|
|
229
|
+
material: { flat: {}, etched: {} },
|
|
199
230
|
// The empty `false` branches are load-bearing typing, not placeholders.
|
|
200
231
|
// `tv` derives the prop type from the declared keys, so a map with only
|
|
201
232
|
// `true` types the prop as `true` rather than `boolean` and rejects
|
|
@@ -208,6 +239,12 @@ export const buttonVariants = tv({
|
|
|
208
239
|
isDimmedWhileLoading: { true: {}, false: {} },
|
|
209
240
|
},
|
|
210
241
|
compoundVariants: [
|
|
242
|
+
{ material: "etched", variant: "primary", class: { root: "shadow-etched-primary" } },
|
|
243
|
+
{
|
|
244
|
+
material: "etched",
|
|
245
|
+
variant: ["secondary", "tertiary", "outline"],
|
|
246
|
+
class: { root: "shadow-etched dark:shadow-etched-dark" },
|
|
247
|
+
},
|
|
211
248
|
// Loading is not a disabled state. The button keeps full contrast — the
|
|
212
249
|
// spinner already says the press landed — unless the caller opts in.
|
|
213
250
|
{ isLoading: true, isDimmedWhileLoading: true, class: { root: "opacity-50" } },
|
|
@@ -304,6 +341,9 @@ export const buttonVariants = tv({
|
|
|
304
341
|
orientation: "horizontal",
|
|
305
342
|
groupPosition: "none",
|
|
306
343
|
isSeamed: false,
|
|
344
|
+
isFullWidth: false,
|
|
345
|
+
isAttached: true,
|
|
346
|
+
isStretched: false,
|
|
307
347
|
isDisabled: false,
|
|
308
348
|
isLoading: false,
|
|
309
349
|
isDimmedWhileLoading: false,
|
|
@@ -413,9 +453,18 @@ export function resolveGroupedButtonSize(own: ButtonSize | undefined, group: But
|
|
|
413
453
|
* trade {@link resolveSpinnerSwapIndex} already makes. React Native has no
|
|
414
454
|
* sibling selector, so this is the only place a member's corner can be decided.
|
|
415
455
|
*
|
|
456
|
+
* A detached run (`isAttached: false`) places every member `only` — the lone
|
|
457
|
+
* position, which draws the lone button's corner — rather than minting a fourth
|
|
458
|
+
* sentinel: a spaced member *is* a button on its own that happens to share props.
|
|
459
|
+
*
|
|
416
460
|
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
417
461
|
*/
|
|
418
|
-
export function resolveGroupPositions(
|
|
462
|
+
export function resolveGroupPositions(
|
|
463
|
+
isMember: readonly boolean[],
|
|
464
|
+
{ isAttached = true }: ButtonGroupShapeOptions = {}
|
|
465
|
+
): (ButtonGroupPosition | null)[] {
|
|
466
|
+
if (!isAttached) return isMember.map((member) => (member ? "only" : null));
|
|
467
|
+
|
|
419
468
|
const count = isMember.reduce((total, member) => (member ? total + 1 : total), 0);
|
|
420
469
|
let seen = 0;
|
|
421
470
|
|
|
@@ -445,9 +494,16 @@ export function resolveGroupPositions(isMember: readonly boolean[]): (ButtonGrou
|
|
|
445
494
|
* `isMember[-1]` is `undefined`, so the first child falls out as `false` without
|
|
446
495
|
* a case of its own.
|
|
447
496
|
*
|
|
497
|
+
* A detached run has no shared edge, so nothing overlaps: a point of overlap
|
|
498
|
+
* there would only eat into the gap.
|
|
499
|
+
*
|
|
448
500
|
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
449
501
|
*/
|
|
450
|
-
export function resolveGroupSeams(
|
|
502
|
+
export function resolveGroupSeams(
|
|
503
|
+
isMember: readonly boolean[],
|
|
504
|
+
{ isAttached = true }: ButtonGroupShapeOptions = {}
|
|
505
|
+
): boolean[] {
|
|
506
|
+
if (!isAttached) return isMember.map(() => false);
|
|
451
507
|
return isMember.map((member, index) => member && isMember[index - 1] === true);
|
|
452
508
|
}
|
|
453
509
|
|
|
@@ -463,6 +519,10 @@ export function resolveGroupSeams(isMember: readonly boolean[]): boolean[] {
|
|
|
463
519
|
* while its neighbours hold still, so the seam the group exists to close tears
|
|
464
520
|
* open for the length of the press. `fade` moves no geometry.
|
|
465
521
|
*
|
|
522
|
+
* Only while the group is *attached*. A detached run has no seam to tear, so
|
|
523
|
+
* its members fall back to the lone button's `scale`. `isAttached` defaults to
|
|
524
|
+
* `true`, because joining is what a group does when it says nothing.
|
|
525
|
+
*
|
|
466
526
|
* A caller still gets `scale` by asking for it, on one button or on a whole
|
|
467
527
|
* group. `pressedScale` beats both, because `resolvePressedState` lets an
|
|
468
528
|
* explicit value win on the axis it names — see `pressable.variants.ts`.
|
|
@@ -472,9 +532,30 @@ export function resolveGroupSeams(isMember: readonly boolean[]): boolean[] {
|
|
|
472
532
|
export function resolveButtonFeedback(
|
|
473
533
|
own: PressableFeedback | undefined,
|
|
474
534
|
group: PressableFeedback | undefined,
|
|
475
|
-
isGrouped: boolean
|
|
535
|
+
isGrouped: boolean,
|
|
536
|
+
isAttached = true
|
|
476
537
|
): PressableFeedback {
|
|
477
|
-
return own ?? group ?? (isGrouped ? BUTTON_GROUP_FEEDBACK : BUTTON_FEEDBACK);
|
|
538
|
+
return own ?? group ?? (isGrouped && isAttached ? BUTTON_GROUP_FEEDBACK : BUTTON_FEEDBACK);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Whether a group's members take an equal share of its width.
|
|
543
|
+
*
|
|
544
|
+
* Horizontal only. A vertical run already stretches its members on the cross
|
|
545
|
+
* axis, and `basis-0` on the main axis of a column with no definite height
|
|
546
|
+
* would collapse every member to nothing — so `isFullWidth` on a vertical group
|
|
547
|
+
* widens the group and does nothing further.
|
|
548
|
+
*
|
|
549
|
+
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
550
|
+
*/
|
|
551
|
+
export function resolveGroupMemberStretch({
|
|
552
|
+
isFullWidth,
|
|
553
|
+
orientation,
|
|
554
|
+
}: {
|
|
555
|
+
isFullWidth: boolean;
|
|
556
|
+
orientation: ButtonGroupOrientation;
|
|
557
|
+
}): boolean {
|
|
558
|
+
return isFullWidth && orientation === "horizontal";
|
|
478
559
|
}
|
|
479
560
|
|
|
480
561
|
export type ButtonVariantProps = VariantProps<typeof buttonVariants>;
|
|
@@ -28,6 +28,7 @@ export {
|
|
|
28
28
|
BUTTON_VARIANTS,
|
|
29
29
|
type ButtonGroupOrientation,
|
|
30
30
|
type ButtonGroupPosition,
|
|
31
|
+
type ButtonGroupShapeOptions,
|
|
31
32
|
type ButtonGroupSlotPosition,
|
|
32
33
|
type ButtonIconSize,
|
|
33
34
|
type ButtonLabelSize,
|
|
@@ -41,6 +42,7 @@ export {
|
|
|
41
42
|
resolveButtonLayout,
|
|
42
43
|
resolveButtonSizeStep,
|
|
43
44
|
resolveGroupedButtonSize,
|
|
45
|
+
resolveGroupMemberStretch,
|
|
44
46
|
resolveGroupPositions,
|
|
45
47
|
resolveGroupSeams,
|
|
46
48
|
} from "./button.variants";
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
An always-visible month grid for picking one day, several days or a range. Compound root plus
|
|
4
|
+
`Calendar.Header`, `Calendar.Nav`, `Calendar.Caption`, `Calendar.Weekdays`, `Calendar.Grid`,
|
|
5
|
+
`Calendar.Day` and `Calendar.Picker`. Used inline on a screen, and as the body of the date pickers'
|
|
6
|
+
sheets.
|
|
7
|
+
|
|
8
|
+
`import { Calendar } from "@delacour/react-native-ui/calendar";`
|
|
9
|
+
|
|
10
|
+
## Files
|
|
11
|
+
|
|
12
|
+
| File | What it holds |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `index.ts` | → `@delacour/react-native-ui/calendar`; also re-exports every date helper and type |
|
|
15
|
+
| `calendar.tsx` | Root: selection and month state, bounds, the context value, the `Object.assign` surface |
|
|
16
|
+
| `calendar-header.tsx` | `Calendar.Header` — prev arrow, caption, next arrow by default |
|
|
17
|
+
| `calendar-nav.tsx` | `Calendar.Nav` — a ghost `Button` that disables itself at a bound |
|
|
18
|
+
| `calendar-caption.tsx` | `Calendar.Caption` — label, or the button that cycles days → months → years |
|
|
19
|
+
| `calendar-weekdays.tsx` | `Calendar.Weekdays` — short names shown, full names spoken |
|
|
20
|
+
| `calendar-grid.tsx` | `Calendar.Grid` — six weeks, the paging pan and slide, hosts the picker |
|
|
21
|
+
| `calendar-day.tsx` | `Calendar.Day` — one cell: band, circle, number or custom content |
|
|
22
|
+
| `calendar-picker.tsx` | `Calendar.Picker` — the months and years jump views |
|
|
23
|
+
| `calendar.context.tsx` | `CalendarContext`, `useCalendar()`, `useCalendarContext()`, `useCalendarPart()` |
|
|
24
|
+
| `calendar.types.ts` | `CalendarSelection` (the mode union), `CalendarProps`, `CalendarCaptionLayout` |
|
|
25
|
+
| `calendar.date.ts` | **Shared leaf.** `CalendarDate` maths, matchers, grid, selection rules, `Intl` formatters. No React Native |
|
|
26
|
+
| `calendar.date.test.ts` | |
|
|
27
|
+
| `calendar.variants.ts` | Slotted `tv()`, the day-state, range-role, bounds, axes and page-direction resolvers |
|
|
28
|
+
| `calendar.variants.test.ts` | |
|
|
29
|
+
|
|
30
|
+
## The date leaf
|
|
31
|
+
|
|
32
|
+
- **A day is `{ year, month, day }` and nothing else.** No `Date`, no time zone, no UTC
|
|
33
|
+
round-trip — a `Date` built at midnight in Auckland is the previous day in UTC, and that is the
|
|
34
|
+
bug every calendar built on `Date` ships once. `toDate`/`fromDate` cross over by **local**
|
|
35
|
+
fields, at the edge only.
|
|
36
|
+
- **`weekdayOf` is Sakamoto's method**, not `new Date().getDay()`, so the leaf never touches the
|
|
37
|
+
device clock or zone. A test checks it against the platform for every day of a leap year.
|
|
38
|
+
- **`calendar.date.ts` is the only module another folder may import from here.** `date-picker`
|
|
39
|
+
and `date-time-picker` reach `../calendar/calendar.date` — never `../calendar`, which would pull
|
|
40
|
+
the whole compound in (package rule 3).
|
|
41
|
+
- **`resolveWeekStart` prefers `Intl.Locale#getWeekInfo`/`weekInfo`** and falls back to a region
|
|
42
|
+
table, because Hermes support for either is not guaranteed. Unknown is Monday (ISO 8601).
|
|
43
|
+
- **Formatters never throw.** An unknown locale falls back to `en`; a formatter that throws inside
|
|
44
|
+
render takes the screen down for a typo.
|
|
45
|
+
- **`parseDate` is strict** — `YYYY-MM-DD` and a real day, or `null`. A lenient parser turns
|
|
46
|
+
`2026-02-31` into 3 March, which is a different booking.
|
|
47
|
+
|
|
48
|
+
## Selection
|
|
49
|
+
|
|
50
|
+
- **`mode` is the discriminant** of `CalendarSelection`, so `selected` and `onSelect` are typed for
|
|
51
|
+
the mode named. Internally one `CalendarSelectionState` union flows through
|
|
52
|
+
`useControllableState`, and `emitSelection` narrows back to the caller's shape.
|
|
53
|
+
- **Single:** a tap on the selected day clears it. **Multiple:** toggles; always sorted, deduped.
|
|
54
|
+
- **Range** (`applyRangeTap`): empty → start; start + any day → complete, in either direction,
|
|
55
|
+
with the earlier day as `start` (`normaliseRange`); complete + tap → a new start. Backwards
|
|
56
|
+
completes rather than moving the start, because tapping check-out first is as common as
|
|
57
|
+
check-in first and a tap that silently discards the first one reads as a bug. A range that would contain a disabled day
|
|
58
|
+
(`rangeContainsDisabled`) restarts at the tapped day instead of completing — a stay that spans a
|
|
59
|
+
closed night is not bookable, and silently accepting it is worse than making the user tap again.
|
|
60
|
+
- **Disabled vs read-only.** `isDisabled` makes everything inert and fades the root. `isReadOnly`
|
|
61
|
+
ignores day taps and leaves paging working — looking at another month changes nothing.
|
|
62
|
+
|
|
63
|
+
## Layout
|
|
64
|
+
|
|
65
|
+
- **Always six weeks** (`CALENDAR_WEEKS`). A month that fits in four or five rows still draws six,
|
|
66
|
+
so height and column positions never shift while paging. An outside day with
|
|
67
|
+
`showOutsideDays={false}` is an empty cell of the same height, never a missing one.
|
|
68
|
+
- **The whole cell is the target**, not the circle. A column on a phone is wider than the circle
|
|
69
|
+
is tall, and the gap belongs to whichever day it sits beside.
|
|
70
|
+
- **The cell is on the input scale** (`h-input-*`, `size-input-*`, `text-input-*`): sm 36, md 44,
|
|
71
|
+
lg 52. `md` and `lg` meet 44pt.
|
|
72
|
+
- **The jump view sits over the grid** (`absolute inset-0`) while the weeks are hidden with
|
|
73
|
+
`opacity-0`, not unmounted, so opening it never changes the calendar's height. The weekday row
|
|
74
|
+
hides the same way.
|
|
75
|
+
|
|
76
|
+
## Drawing
|
|
77
|
+
|
|
78
|
+
- **A day is three layers:** `band` (absolute, edge to edge, behind), `dayBase` (the circle) and
|
|
79
|
+
the label. The band spans the full cell so adjacent days join into one strip.
|
|
80
|
+
- **The band ends at the centre of each end cell, and the circle is the cap.** Start runs from its
|
|
81
|
+
cell's centre to the right edge, end from the left edge to its centre, middle spans the cell; none
|
|
82
|
+
is rounded. A cell is wider than its circle, so the first version — a band rounded across the
|
|
83
|
+
whole end cell — showed a crescent of band beside the circle at both ends. A lone `only` day
|
|
84
|
+
draws no band. At a week's edge the strip simply stops and resumes on the next row.
|
|
85
|
+
- **Today is a ring (`border-ring`) and `text-primary`, never a fill**, so it cannot be mistaken
|
|
86
|
+
for a selection.
|
|
87
|
+
- **Variant:** `primary` fills `bg-primary`, band `bg-primary/15`; `secondary` fills
|
|
88
|
+
`bg-primary/25` under `text-foreground`, band `bg-primary/10`. The primary band is a tint of the selection colour, not
|
|
89
|
+
`bg-accent`: light `--accent` is 0.97 on a 0.985 page, so the band all but vanished in light
|
|
90
|
+
while reading fine in dark. A tint of `primary` reads in both themes and joins the end caps
|
|
91
|
+
as one shape. `secondary` is tinted from `primary` for the same reason — `bg-secondary`
|
|
92
|
+
and `bg-muted` are 0.97 on the same 0.985 page, and the recorded preview showed a soft
|
|
93
|
+
selection with no visible circle at all. `isInvalid` overrides both with `bg-destructive` / `bg-destructive-soft`.
|
|
94
|
+
- **Colour is on the text slots only** (rule 1): `dayLabel`, `weekdayLabel`, `captionText`,
|
|
95
|
+
`pickerItemLabel`. A test holds every `View` slot to carrying no `text-*` colour.
|
|
96
|
+
- **Custom day content** gets `labelClassName` in its render props, so a price under the number
|
|
97
|
+
takes the selected colour with it rather than reimplementing the tone table.
|
|
98
|
+
|
|
99
|
+
## Paging
|
|
100
|
+
|
|
101
|
+
- **A Gesture Handler pan on `Calendar.Grid`**, `activeOffsetX` ±12 and `failOffsetY` ±12: it
|
|
102
|
+
claims a sideways drag and gives up a vertical one, so the calendar lives inside a `ScrollView`
|
|
103
|
+
or a sheet without stealing its scroll.
|
|
104
|
+
- **`resolvePageDirection` is pure and tested.** A quarter of the width, or a release faster than
|
|
105
|
+
500 pt/s, pages; a flick against the drag settles back; it never pages past a bound or on an
|
|
106
|
+
unmeasured grid.
|
|
107
|
+
- **Slide out, swap, slide in.** The old month slides off, the month changes on the JS thread, and
|
|
108
|
+
the new one slides in from the opposite side — the same entrance the arrows give. The grid
|
|
109
|
+
clips (`overflow-hidden`) so the swap frame is never seen.
|
|
110
|
+
- **Calm motion skips both slides.** The page change is behaviour and always happens.
|
|
111
|
+
- **A drag toward a bound resists** (20% travel) and settles back with a `light` haptic — the edge
|
|
112
|
+
is felt rather than the month silently refusing to turn.
|
|
113
|
+
- **Tapping an outside day** (with `selectOutsideDays`) selects it and pages to its month.
|
|
114
|
+
|
|
115
|
+
## Accessibility
|
|
116
|
+
|
|
117
|
+
- Each day is a button labelled with the full date (`formatDate(…, { style: "full" })`), hinted
|
|
118
|
+
"Today" / "Range start" / "Range end", with `selected` and `disabled` state.
|
|
119
|
+
- The grid is `role="grid"`, named by an enclosing `Field.Label` or else the month.
|
|
120
|
+
- **Paging for screen readers lives on the caption**, as increment/decrement actions. An
|
|
121
|
+
adjustable grid would fold its child buttons into itself on both platforms and make the days
|
|
122
|
+
unreachable.
|
|
123
|
+
- A month change is announced with `AccessibilityInfo.announceForAccessibility` — on change only,
|
|
124
|
+
never on mount, because the caption already says it.
|
|
125
|
+
- Today is re-read on `AppState` → `active`, so a calendar left open overnight marks the right day.
|
|
126
|
+
|
|
127
|
+
- **Parts derive their `testID` from the root's** (`resolvePartTestID`): `testID="booking"`
|
|
128
|
+
names the days `booking-day-2026-10-05`, the arrows `booking-prev`/`booking-next`, the caption
|
|
129
|
+
`booking-caption` and the jump-view items `booking-month-10`/`booking-year-2026`. The default
|
|
130
|
+
anatomy renders forty-odd parts no caller writes, and an end-to-end test or a recorded preview
|
|
131
|
+
flow has to reach one particular day all the same. No root `testID`, no part ids.
|
|
132
|
+
|
|
133
|
+
## Field
|
|
134
|
+
|
|
135
|
+
`resolveCalendarAxes` is the nearest-wins chain: the calendar's own `isDisabled` / `isInvalid` /
|
|
136
|
+
`isReadOnly` / `size` / `variant`, else the `Field`'s. The field's label names the grid. It does
|
|
137
|
+
not `registerPress` — there is no single focus target for a row tap to reach.
|
|
138
|
+
|
|
139
|
+
## Not here
|
|
140
|
+
|
|
141
|
+
Non-Gregorian calendars, side-by-side months, week numbers, drag-to-select ranges, time. The
|
|
142
|
+
anchored presentation belongs to `DatePicker`.
|