@delacour/react-native-ui 0.1.0-alpha.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +48 -4
- 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 +5 -0
- package/src/components/badge/badge.tsx +6 -1
- 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 +45 -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 +2 -1
- package/src/components/button/button.variants.test.ts +154 -0
- package/src/components/button/button.variants.ts +71 -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 +88 -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 +94 -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/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 +72 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +54 -0
- package/src/components/surface/surface.tsx +70 -0
- package/src/components/surface/surface.variants.test.ts +180 -0
- package/src/components/surface/surface.variants.ts +133 -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/textarea/AGENTS.md +86 -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.test.ts +14 -0
- package/src/lib/cn.ts +2 -2
- 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 +11 -0
- package/src/styles/tokens.test.ts +22 -0
- package/src/styles/tokens.ts +20 -0
- 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,426 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
import {
|
|
4
|
+
type CalendarCell,
|
|
5
|
+
type CalendarDate,
|
|
6
|
+
type CalendarDateRange,
|
|
7
|
+
type CalendarSelectionState,
|
|
8
|
+
compareDates,
|
|
9
|
+
isBetween,
|
|
10
|
+
isSameDay,
|
|
11
|
+
startOfMonth,
|
|
12
|
+
} from "./calendar.date";
|
|
13
|
+
|
|
14
|
+
export const CALENDAR_VARIANTS = ["primary", "secondary"] as const;
|
|
15
|
+
export const CALENDAR_SIZES = ["sm", "md", "lg"] as const;
|
|
16
|
+
|
|
17
|
+
/** How a day is painted: unselected, a filled selection, or the soft band between a range's ends. */
|
|
18
|
+
export const CALENDAR_DAY_TONES = ["plain", "selected", "band"] as const;
|
|
19
|
+
|
|
20
|
+
/** Where a day sits in a range. `none` is outside it — `tv` cannot key a variant on `null`. */
|
|
21
|
+
export const CALENDAR_RANGE_ROLES = ["none", "start", "middle", "end", "only"] as const;
|
|
22
|
+
|
|
23
|
+
export type CalendarVariant = (typeof CALENDAR_VARIANTS)[number];
|
|
24
|
+
export type CalendarSize = (typeof CALENDAR_SIZES)[number];
|
|
25
|
+
export type CalendarDayTone = (typeof CALENDAR_DAY_TONES)[number];
|
|
26
|
+
export type CalendarRangeRole = Exclude<(typeof CALENDAR_RANGE_ROLES)[number], "none">;
|
|
27
|
+
|
|
28
|
+
/** The view the calendar's body shows: the month grid, or one of the two jump views. */
|
|
29
|
+
export type CalendarView = "days" | "months" | "years";
|
|
30
|
+
|
|
31
|
+
export const CALENDAR_DEFAULT_VARIANT: CalendarVariant = "primary";
|
|
32
|
+
export const CALENDAR_DEFAULT_SIZE: CalendarSize = "md";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* How far a swipe must travel, as a fraction of the grid's width, to page.
|
|
36
|
+
*
|
|
37
|
+
* A quarter rather than a half because a thumb sweeping across a phone rarely covers half the
|
|
38
|
+
* screen, and a fast flick pages regardless — see {@link CALENDAR_PAGE_VELOCITY}.
|
|
39
|
+
*/
|
|
40
|
+
export const CALENDAR_PAGE_THRESHOLD = 0.25;
|
|
41
|
+
|
|
42
|
+
/** A release faster than this, in points per second, pages however short the swipe was. */
|
|
43
|
+
export const CALENDAR_PAGE_VELOCITY = 500;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Styling for every part of a calendar.
|
|
47
|
+
*
|
|
48
|
+
* One slotted `tv()` because eight part files read the same `size` and `variant`, and none of them
|
|
49
|
+
* can import the root without closing a cycle (AGENTS.md rule 3).
|
|
50
|
+
*
|
|
51
|
+
* **A day is three layers.** `cell` is the flex-1 column share and the touch target's height;
|
|
52
|
+
* `band` is drawn behind, edge to edge, for a day inside a range; `dayBase` is the circle that
|
|
53
|
+
* fills for a selection and rings for today, and `dayLabel` is the number. The band spans the
|
|
54
|
+
* whole cell so neighbouring days join into one continuous strip — a gap between columns would
|
|
55
|
+
* break it into beads.
|
|
56
|
+
*
|
|
57
|
+
* **The band ends under the circle, never beside it.** Start runs from its cell's centre to the
|
|
58
|
+
* right edge, end from the left edge to its centre, and every day between spans the whole cell
|
|
59
|
+
* square — including at a week's edge, where the strip simply stops and picks up again on the
|
|
60
|
+
* next row. A cell is wider than its circle, so a band rounded across the whole end cell poked a
|
|
61
|
+
* crescent out beside the circle; stopping at the centre lets the circle itself be the cap. A
|
|
62
|
+
* one-day range draws no band at all.
|
|
63
|
+
*
|
|
64
|
+
* **The cell steps on the input scale** — `h-input-*` and `text-input-*` — so a calendar inline
|
|
65
|
+
* beside a field shares its density, and a retune of the field retunes the calendar.
|
|
66
|
+
*
|
|
67
|
+
* **Colour is on the text slots, never their parent** (rule 1): `dayLabel`, `weekdayLabel`,
|
|
68
|
+
* `captionText` and `pickerItemLabel` carry it; every `View` slot carries a fill at most.
|
|
69
|
+
*
|
|
70
|
+
* Free of React Native imports so it stays unit-testable. See AGENTS.md.
|
|
71
|
+
*/
|
|
72
|
+
export const calendarVariants = tv({
|
|
73
|
+
slots: {
|
|
74
|
+
/** The column holding the header, the weekday row and the grid. */
|
|
75
|
+
root: "w-full gap-1",
|
|
76
|
+
/** Previous arrow, caption, next arrow. */
|
|
77
|
+
header: "flex-row items-center justify-between gap-1",
|
|
78
|
+
/** The caption's touch target — a capsule when it opens the jump view. */
|
|
79
|
+
caption: "min-h-11 flex-row items-center justify-center gap-1 rounded-full px-3",
|
|
80
|
+
/** The month and year. */
|
|
81
|
+
captionText: "font-sans font-semibold text-foreground",
|
|
82
|
+
/** The row of weekday names. */
|
|
83
|
+
weekdays: "flex-row",
|
|
84
|
+
/** One weekday name's column. */
|
|
85
|
+
weekday: "flex-1 items-center justify-center py-1",
|
|
86
|
+
/** A weekday name. */
|
|
87
|
+
weekdayLabel: "font-medium font-sans text-muted-foreground text-xs",
|
|
88
|
+
/** The six weeks, clipped so a page sliding in or out never draws past the edge. */
|
|
89
|
+
grid: "overflow-hidden",
|
|
90
|
+
/** One week. */
|
|
91
|
+
week: "flex-row",
|
|
92
|
+
/** One day's column share, and the height of its touch target. */
|
|
93
|
+
cell: "flex-1 items-center justify-center",
|
|
94
|
+
/** The strip behind a day inside a range. */
|
|
95
|
+
band: "absolute inset-y-0",
|
|
96
|
+
/** The circle a selection fills and today rings. */
|
|
97
|
+
dayBase: "items-center justify-center rounded-full border border-transparent",
|
|
98
|
+
/** The day's number. */
|
|
99
|
+
dayLabel: "font-sans tabular-nums",
|
|
100
|
+
/** The months or years jump view. */
|
|
101
|
+
pickerGrid: "flex-row flex-wrap",
|
|
102
|
+
/** One month or year. */
|
|
103
|
+
pickerItem: "basis-1/3 items-center justify-center rounded-full",
|
|
104
|
+
/** A month or year's name. */
|
|
105
|
+
pickerItemLabel: "font-sans",
|
|
106
|
+
},
|
|
107
|
+
variants: {
|
|
108
|
+
variant: {
|
|
109
|
+
primary: {},
|
|
110
|
+
secondary: {},
|
|
111
|
+
},
|
|
112
|
+
size: {
|
|
113
|
+
sm: {
|
|
114
|
+
captionText: "text-input-sm",
|
|
115
|
+
cell: "h-input-sm",
|
|
116
|
+
dayBase: "size-input-sm",
|
|
117
|
+
dayLabel: "text-input-sm",
|
|
118
|
+
pickerItem: "h-input-sm",
|
|
119
|
+
pickerItemLabel: "text-input-sm",
|
|
120
|
+
},
|
|
121
|
+
md: {
|
|
122
|
+
captionText: "text-input-md",
|
|
123
|
+
cell: "h-input-md",
|
|
124
|
+
dayBase: "size-input-md",
|
|
125
|
+
dayLabel: "text-input-md",
|
|
126
|
+
pickerItem: "h-input-md",
|
|
127
|
+
pickerItemLabel: "text-input-md",
|
|
128
|
+
},
|
|
129
|
+
lg: {
|
|
130
|
+
captionText: "text-input-lg",
|
|
131
|
+
cell: "h-input-lg",
|
|
132
|
+
dayBase: "size-input-lg",
|
|
133
|
+
dayLabel: "text-input-lg",
|
|
134
|
+
pickerItem: "h-input-lg",
|
|
135
|
+
pickerItemLabel: "text-input-lg",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
tone: {
|
|
139
|
+
plain: { dayLabel: "text-foreground", pickerItemLabel: "text-foreground" },
|
|
140
|
+
selected: { dayLabel: "font-semibold", pickerItemLabel: "font-semibold" },
|
|
141
|
+
band: { dayBase: "bg-transparent" },
|
|
142
|
+
},
|
|
143
|
+
rangeRole: {
|
|
144
|
+
none: { band: "hidden" },
|
|
145
|
+
start: { band: "left-1/2 right-0" },
|
|
146
|
+
middle: { band: "inset-x-0" },
|
|
147
|
+
end: { band: "left-0 right-1/2" },
|
|
148
|
+
only: { band: "hidden" },
|
|
149
|
+
},
|
|
150
|
+
isToday: { true: {}, false: {} },
|
|
151
|
+
isOutside: { true: {}, false: {} },
|
|
152
|
+
isInvalid: { true: {}, false: {} },
|
|
153
|
+
isDisabled: {
|
|
154
|
+
true: { root: "opacity-50", dayBase: "opacity-40" },
|
|
155
|
+
false: {},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
compoundVariants: [
|
|
159
|
+
{ variant: "primary", tone: "selected", class: { dayBase: "bg-primary", dayLabel: "text-primary-foreground" } },
|
|
160
|
+
{
|
|
161
|
+
variant: "primary",
|
|
162
|
+
tone: "selected",
|
|
163
|
+
class: { pickerItem: "bg-primary", pickerItemLabel: "text-primary-foreground" },
|
|
164
|
+
},
|
|
165
|
+
{ variant: "primary", class: { band: "bg-primary/15" } },
|
|
166
|
+
{ variant: "primary", tone: "band", class: { dayLabel: "text-foreground" } },
|
|
167
|
+
{
|
|
168
|
+
variant: "secondary",
|
|
169
|
+
tone: "selected",
|
|
170
|
+
class: {
|
|
171
|
+
dayBase: "bg-primary/25",
|
|
172
|
+
dayLabel: "text-foreground",
|
|
173
|
+
pickerItem: "bg-primary/25",
|
|
174
|
+
pickerItemLabel: "text-foreground",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{ variant: "secondary", class: { band: "bg-primary/10" } },
|
|
178
|
+
{ variant: "secondary", tone: "band", class: { dayLabel: "text-foreground" } },
|
|
179
|
+
{
|
|
180
|
+
isInvalid: true,
|
|
181
|
+
tone: "selected",
|
|
182
|
+
class: {
|
|
183
|
+
dayBase: "bg-destructive",
|
|
184
|
+
dayLabel: "text-destructive-foreground",
|
|
185
|
+
pickerItem: "bg-destructive",
|
|
186
|
+
pickerItemLabel: "text-destructive-foreground",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{ isInvalid: true, class: { band: "bg-destructive-soft" } },
|
|
190
|
+
{ isInvalid: true, tone: "band", class: { dayLabel: "text-destructive-soft-foreground" } },
|
|
191
|
+
{ isToday: true, tone: "plain", class: { dayBase: "border-ring", dayLabel: "font-semibold text-primary" } },
|
|
192
|
+
{ isOutside: true, tone: "plain", class: { dayLabel: "text-muted-foreground" } },
|
|
193
|
+
],
|
|
194
|
+
defaultVariants: {
|
|
195
|
+
variant: CALENDAR_DEFAULT_VARIANT,
|
|
196
|
+
size: CALENDAR_DEFAULT_SIZE,
|
|
197
|
+
tone: "plain",
|
|
198
|
+
rangeRole: "none",
|
|
199
|
+
isToday: false,
|
|
200
|
+
isOutside: false,
|
|
201
|
+
isInvalid: false,
|
|
202
|
+
isDisabled: false,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export type CalendarVariantProps = VariantProps<typeof calendarVariants>;
|
|
207
|
+
|
|
208
|
+
// ── Resolvers ───────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Where `date` sits in `range`, or `null` outside it.
|
|
212
|
+
*
|
|
213
|
+
* A started range's one day is `only`, the same as a complete one-day range: both are a single
|
|
214
|
+
* filled circle with nothing joining it to a neighbour.
|
|
215
|
+
*/
|
|
216
|
+
export function resolveRangeRole(range: CalendarDateRange, date: CalendarDate): CalendarRangeRole | null {
|
|
217
|
+
const { start, end } = range;
|
|
218
|
+
if (!start) return null;
|
|
219
|
+
if (!end || isSameDay(start, end)) return isSameDay(start, date) ? "only" : null;
|
|
220
|
+
if (isSameDay(start, date)) return "start";
|
|
221
|
+
if (isSameDay(end, date)) return "end";
|
|
222
|
+
return isBetween(date, start, end) ? "middle" : null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** How a day is painted, from whether it is selected and where it sits in a range. */
|
|
226
|
+
export function resolveDayTone({
|
|
227
|
+
isSelected,
|
|
228
|
+
rangeRole,
|
|
229
|
+
}: {
|
|
230
|
+
isSelected: boolean;
|
|
231
|
+
rangeRole: CalendarRangeRole | null;
|
|
232
|
+
}): CalendarDayTone {
|
|
233
|
+
if (rangeRole === "middle") return "band";
|
|
234
|
+
return isSelected || rangeRole !== null ? "selected" : "plain";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Whether a day is part of the selection, in whichever mode it is. */
|
|
238
|
+
export function isDateSelected(selection: CalendarSelectionState, date: CalendarDate): boolean {
|
|
239
|
+
switch (selection.mode) {
|
|
240
|
+
case "single":
|
|
241
|
+
return isSameDay(selection.value, date);
|
|
242
|
+
case "multiple":
|
|
243
|
+
return selection.value.some((item) => isSameDay(item, date));
|
|
244
|
+
case "range":
|
|
245
|
+
return resolveRangeRole(selection.value, date) !== null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Everything a `Calendar.Day` needs to know to draw itself and decide whether it takes a tap. */
|
|
250
|
+
export type CalendarDayState = {
|
|
251
|
+
isSelected: boolean;
|
|
252
|
+
rangeRole: CalendarRangeRole | null;
|
|
253
|
+
tone: CalendarDayTone;
|
|
254
|
+
isToday: boolean;
|
|
255
|
+
isOutside: boolean;
|
|
256
|
+
/** Dimmed and announced as disabled: out of bounds, or matched. */
|
|
257
|
+
isDisabled: boolean;
|
|
258
|
+
/** Drawn as an empty cell — an outside day with `showOutsideDays` off. */
|
|
259
|
+
isHidden: boolean;
|
|
260
|
+
/** Takes a tap. */
|
|
261
|
+
isPressable: boolean;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* One day's state, from its cell and the calendar around it.
|
|
266
|
+
*
|
|
267
|
+
* **An outside day shows no selection unless outside days are selectable.** It is a preview of
|
|
268
|
+
* the next month, and a range drawn into it would read as a second copy of the range's end.
|
|
269
|
+
* **`isReadOnly` stops the tap but not the paint**, while a disabled day or a disabled calendar
|
|
270
|
+
* stops both — a read-only calendar is showing an answer, not offering a dead control.
|
|
271
|
+
*/
|
|
272
|
+
export function resolveDayState({
|
|
273
|
+
cell,
|
|
274
|
+
selection,
|
|
275
|
+
today,
|
|
276
|
+
isDisabled,
|
|
277
|
+
showOutsideDays,
|
|
278
|
+
selectOutsideDays,
|
|
279
|
+
isCalendarDisabled,
|
|
280
|
+
isReadOnly,
|
|
281
|
+
}: {
|
|
282
|
+
cell: CalendarCell;
|
|
283
|
+
selection: CalendarSelectionState;
|
|
284
|
+
today: CalendarDate;
|
|
285
|
+
isDisabled: (date: CalendarDate) => boolean;
|
|
286
|
+
showOutsideDays: boolean;
|
|
287
|
+
selectOutsideDays: boolean;
|
|
288
|
+
isCalendarDisabled: boolean;
|
|
289
|
+
isReadOnly: boolean;
|
|
290
|
+
}): CalendarDayState {
|
|
291
|
+
const { date, isOutside } = cell;
|
|
292
|
+
const isHidden = isOutside && !showOutsideDays;
|
|
293
|
+
const showsSelection = !isOutside || selectOutsideDays;
|
|
294
|
+
const isSelected = showsSelection && isDateSelected(selection, date);
|
|
295
|
+
const rangeRole = showsSelection && selection.mode === "range" ? resolveRangeRole(selection.value, date) : null;
|
|
296
|
+
const dayDisabled = isDisabled(date);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
isSelected,
|
|
300
|
+
rangeRole,
|
|
301
|
+
tone: resolveDayTone({ isSelected, rangeRole }),
|
|
302
|
+
isToday: isSameDay(date, today),
|
|
303
|
+
isOutside,
|
|
304
|
+
isDisabled: dayDisabled,
|
|
305
|
+
isHidden,
|
|
306
|
+
isPressable: !isHidden && !dayDisabled && !isCalendarDisabled && !isReadOnly && (!isOutside || selectOutsideDays),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The first and last months the arrows and the swipe can reach.
|
|
312
|
+
*
|
|
313
|
+
* `startMonth`/`endMonth` when the caller names them, otherwise the months `minDate`/`maxDate`
|
|
314
|
+
* fall in — paging to a month in which nothing can be picked is a dead end. Either may be `null`,
|
|
315
|
+
* which is unbounded.
|
|
316
|
+
*/
|
|
317
|
+
export function resolveMonthBounds({
|
|
318
|
+
startMonth,
|
|
319
|
+
endMonth,
|
|
320
|
+
minDate,
|
|
321
|
+
maxDate,
|
|
322
|
+
}: {
|
|
323
|
+
startMonth?: CalendarDate | null;
|
|
324
|
+
endMonth?: CalendarDate | null;
|
|
325
|
+
minDate?: CalendarDate | null;
|
|
326
|
+
maxDate?: CalendarDate | null;
|
|
327
|
+
}): { first: CalendarDate | null; last: CalendarDate | null } {
|
|
328
|
+
const first = startMonth ?? minDate;
|
|
329
|
+
const last = endMonth ?? maxDate;
|
|
330
|
+
return { first: first ? startOfMonth(first) : null, last: last ? startOfMonth(last) : null };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Whether `month` lies inside the bounds {@link resolveMonthBounds} returns, by month alone. */
|
|
334
|
+
export function isMonthInBounds(
|
|
335
|
+
month: CalendarDate,
|
|
336
|
+
{ first, last }: { first: CalendarDate | null; last: CalendarDate | null }
|
|
337
|
+
): boolean {
|
|
338
|
+
const start = startOfMonth(month);
|
|
339
|
+
if (first && compareDates(start, first) < 0) return false;
|
|
340
|
+
if (last && compareDates(start, last) > 0) return false;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Which way a released swipe pages: `1` forward, `-1` back, `0` settle where it was.
|
|
346
|
+
*
|
|
347
|
+
* A swipe pages when it travelled past {@link CALENDAR_PAGE_THRESHOLD} of the width, or was
|
|
348
|
+
* released faster than {@link CALENDAR_PAGE_VELOCITY} — unless the flick goes against the drag,
|
|
349
|
+
* which is someone changing their mind. It never pages past a bound, and an unmeasured grid
|
|
350
|
+
* never pages at all.
|
|
351
|
+
*/
|
|
352
|
+
export function resolvePageDirection({
|
|
353
|
+
translationX,
|
|
354
|
+
velocityX,
|
|
355
|
+
width,
|
|
356
|
+
canGoPrev,
|
|
357
|
+
canGoNext,
|
|
358
|
+
}: {
|
|
359
|
+
translationX: number;
|
|
360
|
+
velocityX: number;
|
|
361
|
+
width: number;
|
|
362
|
+
canGoPrev: boolean;
|
|
363
|
+
canGoNext: boolean;
|
|
364
|
+
}): -1 | 0 | 1 {
|
|
365
|
+
"worklet";
|
|
366
|
+
if (width <= 0 || translationX === 0) return 0;
|
|
367
|
+
const direction = translationX < 0 ? 1 : -1;
|
|
368
|
+
const against =
|
|
369
|
+
(direction === 1 && velocityX > CALENDAR_PAGE_VELOCITY) ||
|
|
370
|
+
(direction === -1 && velocityX < -CALENDAR_PAGE_VELOCITY);
|
|
371
|
+
if (against) return 0;
|
|
372
|
+
const far = Math.abs(translationX) >= width * CALENDAR_PAGE_THRESHOLD;
|
|
373
|
+
const fast = Math.abs(velocityX) >= CALENDAR_PAGE_VELOCITY;
|
|
374
|
+
if (!far && !fast) return 0;
|
|
375
|
+
if (direction === 1 && !canGoNext) return 0;
|
|
376
|
+
if (direction === -1 && !canGoPrev) return 0;
|
|
377
|
+
return direction;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** What a calendar was given at its own call site. */
|
|
381
|
+
export type CalendarOwnAxes = {
|
|
382
|
+
variant?: CalendarVariant;
|
|
383
|
+
size?: CalendarSize;
|
|
384
|
+
isDisabled?: boolean;
|
|
385
|
+
isInvalid?: boolean;
|
|
386
|
+
isReadOnly?: boolean;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
/** What an enclosing `Field` publishes, or null outside one. */
|
|
390
|
+
export type CalendarFieldAxes = { isDisabled?: boolean; isInvalid?: boolean };
|
|
391
|
+
|
|
392
|
+
export type CalendarAxes = Required<CalendarOwnAxes>;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Settles a calendar's axes: its own props, then an enclosing `Field`, then the defaults — `??`
|
|
396
|
+
* throughout, so an explicit `false` is a value. A field reaches the two state axes only;
|
|
397
|
+
* `isReadOnly` is the calendar's own.
|
|
398
|
+
*/
|
|
399
|
+
export function resolveCalendarAxes({
|
|
400
|
+
own,
|
|
401
|
+
field,
|
|
402
|
+
}: {
|
|
403
|
+
own?: CalendarOwnAxes;
|
|
404
|
+
field?: CalendarFieldAxes | null;
|
|
405
|
+
}): CalendarAxes {
|
|
406
|
+
return {
|
|
407
|
+
variant: own?.variant ?? CALENDAR_DEFAULT_VARIANT,
|
|
408
|
+
size: own?.size ?? CALENDAR_DEFAULT_SIZE,
|
|
409
|
+
isDisabled: own?.isDisabled ?? field?.isDisabled ?? false,
|
|
410
|
+
isInvalid: own?.isInvalid ?? field?.isInvalid ?? false,
|
|
411
|
+
isReadOnly: own?.isReadOnly ?? false,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* A part's `testID`, derived from the calendar's own: `booking` names its days `booking-day-2026-10-05`,
|
|
417
|
+
* its arrows `booking-prev` and `booking-next`, its caption `booking-caption` and the jump views'
|
|
418
|
+
* items `booking-month-10` and `booking-year-2026`.
|
|
419
|
+
*
|
|
420
|
+
* Derived rather than taken per part because the default anatomy renders forty-odd parts no caller
|
|
421
|
+
* writes, and an end-to-end test or a recorded flow has to reach a particular day all the same.
|
|
422
|
+
* A calendar with no `testID` gives its parts none.
|
|
423
|
+
*/
|
|
424
|
+
export function resolvePartTestID(root: string | undefined, part: string): string | undefined {
|
|
425
|
+
return root === undefined ? undefined : `${root}-${part}`;
|
|
426
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export { Calendar } from "./calendar";
|
|
2
|
+
export {
|
|
3
|
+
type CalendarContextValue,
|
|
4
|
+
CalendarProvider,
|
|
5
|
+
useCalendar,
|
|
6
|
+
useCalendarContext,
|
|
7
|
+
} from "./calendar.context";
|
|
8
|
+
export {
|
|
9
|
+
addDays,
|
|
10
|
+
addMonths,
|
|
11
|
+
addYears,
|
|
12
|
+
applyRangeTap,
|
|
13
|
+
applySingleTap,
|
|
14
|
+
CALENDAR_WEEKS,
|
|
15
|
+
CALENDAR_YEAR_REACH,
|
|
16
|
+
type CalendarCell,
|
|
17
|
+
type CalendarDate,
|
|
18
|
+
type CalendarDateBounds,
|
|
19
|
+
type CalendarDateFormatOptions,
|
|
20
|
+
type CalendarDateRange,
|
|
21
|
+
type CalendarDateStyle,
|
|
22
|
+
type CalendarSelectionState,
|
|
23
|
+
clampDate,
|
|
24
|
+
compareDates,
|
|
25
|
+
compareMonths,
|
|
26
|
+
type DateMatcher,
|
|
27
|
+
DEFAULT_WEEK_START,
|
|
28
|
+
daysInMonth,
|
|
29
|
+
formatDate,
|
|
30
|
+
formatDateRange,
|
|
31
|
+
formatMonthYear,
|
|
32
|
+
formatYear,
|
|
33
|
+
fromDate,
|
|
34
|
+
isBetween,
|
|
35
|
+
isDateDisabled,
|
|
36
|
+
isLeapYear,
|
|
37
|
+
isSameDay,
|
|
38
|
+
isSameMonth,
|
|
39
|
+
isValidDate,
|
|
40
|
+
matchesDisabled,
|
|
41
|
+
monthGrid,
|
|
42
|
+
monthNames,
|
|
43
|
+
nextSelection,
|
|
44
|
+
normaliseRange,
|
|
45
|
+
parseDate,
|
|
46
|
+
rangeContainsDisabled,
|
|
47
|
+
resolveWeekStart,
|
|
48
|
+
resolveYearSpan,
|
|
49
|
+
serialiseDate,
|
|
50
|
+
startOfMonth,
|
|
51
|
+
toDate,
|
|
52
|
+
today,
|
|
53
|
+
toggleMultiple,
|
|
54
|
+
type Weekday,
|
|
55
|
+
weekdayLabels,
|
|
56
|
+
weekdayOf,
|
|
57
|
+
weekStartFromRegion,
|
|
58
|
+
} from "./calendar.date";
|
|
59
|
+
export type { CalendarCaptionLayout, CalendarProps, CalendarSelection } from "./calendar.types";
|
|
60
|
+
export {
|
|
61
|
+
CALENDAR_DAY_TONES,
|
|
62
|
+
CALENDAR_DEFAULT_SIZE,
|
|
63
|
+
CALENDAR_DEFAULT_VARIANT,
|
|
64
|
+
CALENDAR_PAGE_THRESHOLD,
|
|
65
|
+
CALENDAR_PAGE_VELOCITY,
|
|
66
|
+
CALENDAR_RANGE_ROLES,
|
|
67
|
+
CALENDAR_SIZES,
|
|
68
|
+
CALENDAR_VARIANTS,
|
|
69
|
+
type CalendarAxes,
|
|
70
|
+
type CalendarDayState,
|
|
71
|
+
type CalendarDayTone,
|
|
72
|
+
type CalendarFieldAxes,
|
|
73
|
+
type CalendarOwnAxes,
|
|
74
|
+
type CalendarRangeRole,
|
|
75
|
+
type CalendarSize,
|
|
76
|
+
type CalendarVariant,
|
|
77
|
+
type CalendarVariantProps,
|
|
78
|
+
type CalendarView,
|
|
79
|
+
calendarVariants,
|
|
80
|
+
isDateSelected,
|
|
81
|
+
isMonthInBounds,
|
|
82
|
+
resolveCalendarAxes,
|
|
83
|
+
resolveDayState,
|
|
84
|
+
resolveDayTone,
|
|
85
|
+
resolveMonthBounds,
|
|
86
|
+
resolvePageDirection,
|
|
87
|
+
resolvePartTestID,
|
|
88
|
+
resolveRangeRole,
|
|
89
|
+
} from "./calendar.variants";
|
|
90
|
+
export type { CalendarCaptionProps } from "./calendar-caption";
|
|
91
|
+
export type { CalendarDayProps, CalendarDayRenderProps } from "./calendar-day";
|
|
92
|
+
export type { CalendarGridProps } from "./calendar-grid";
|
|
93
|
+
export type { CalendarHeaderProps } from "./calendar-header";
|
|
94
|
+
export type { CalendarNavProps } from "./calendar-nav";
|
|
95
|
+
export type { CalendarPickerProps } from "./calendar-picker";
|
|
96
|
+
export type { CalendarWeekdaysProps } from "./calendar-weekdays";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Card
|
|
2
|
+
|
|
3
|
+
A content surface with a header, a body and a footer, built on
|
|
4
|
+
[`Surface`](../surface/AGENTS.md). Compound root plus six parts: `Header`,
|
|
5
|
+
`Title`, `Description`, `Action`, `Content`, `Footer`.
|
|
6
|
+
|
|
7
|
+
`import { Card } from "@delacour/react-native-ui/card";`
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `index.ts` | → `@delacour/react-native-ui/card` |
|
|
14
|
+
| `card.tsx` | Root + the `Object.assign` compound surface |
|
|
15
|
+
| `card-header.tsx` | `Card.Header`, and the bare-text wrap it owns |
|
|
16
|
+
| `card-title.tsx` | `Card.Title` |
|
|
17
|
+
| `card-description.tsx` | `Card.Description` |
|
|
18
|
+
| `card-action.tsx` | `Card.Action` |
|
|
19
|
+
| `card-content.tsx` | `Card.Content` |
|
|
20
|
+
| `card-footer.tsx` | `Card.Footer` |
|
|
21
|
+
| `card.context.tsx` | `CardProvider`, `useCard()`, `useCardContext()`, `useCardPart()` |
|
|
22
|
+
| `card.types.ts` | Prop types shared by two or more parts |
|
|
23
|
+
| `card.variants.ts` | The slotted `tv()`, `resolveCardFooterFill`, `splitCardHeaderChildren` — no RN imports |
|
|
24
|
+
| `card.variants.test.ts` | |
|
|
25
|
+
|
|
26
|
+
## Design
|
|
27
|
+
|
|
28
|
+
- **The surface is `Surface`'s; the card adds only the rhythm between its
|
|
29
|
+
parts.** Fill, hairline, corner and clip all come from `Surface` at
|
|
30
|
+
`padding="none"`, so `variant` is `Surface`'s four fills with the same
|
|
31
|
+
nesting rule — a card that names none is the hairlined `default` on a screen
|
|
32
|
+
and steps to the next fill inside another surface. A card is not a second
|
|
33
|
+
definition of what a card-shaped surface looks like.
|
|
34
|
+
- **The root resolves the fill itself as well as passing it down.** The parts
|
|
35
|
+
need the plane the card landed on — the title's colour and a band footer's
|
|
36
|
+
fill key off it — and `Surface` does not publish a prop it was never given.
|
|
37
|
+
So `Card` runs the same two pure resolvers and hands `Surface` the explicit
|
|
38
|
+
result; the two cannot disagree, because it is one function called twice.
|
|
39
|
+
- **Padding lives on the parts, not the root.** The root carries `pt`/`pb`
|
|
40
|
+
and a `gap` of the same step; `Header`, `Content` and `Footer` carry that step as
|
|
41
|
+
`px`. An image written straight into the card therefore reaches both side
|
|
42
|
+
edges with nothing to undo, and `className="pt-0"` on the card bleeds it to
|
|
43
|
+
the top. Content that must not be clipped does not belong in a card: the root
|
|
44
|
+
is `padding="none"`, which is `Surface`'s one clipping step.
|
|
45
|
+
- **The root's vertical padding is `pt`/`pb`, never `py`.** The root is the
|
|
46
|
+
surface's view, which carries `p-0` at `padding="none"`, and Uniwind resolves
|
|
47
|
+
that shorthand over a `py-*` on the same view — the first build on a device
|
|
48
|
+
drew the title flush against the top hairline. The longhands win, and a test
|
|
49
|
+
forbids `py-` on the root so it cannot come back.
|
|
50
|
+
- **Sizes**: `sm`, `md`, `lg` — insets of 3, 4 and 6, which are `Surface`'s own
|
|
51
|
+
padding steps, so a card and a surface side by side at one size hold their
|
|
52
|
+
content at one distance from the edge. Size is one axis for five numbers —
|
|
53
|
+
vertical padding, gap, horizontal inset, title scale, description scale —
|
|
54
|
+
and the test pins the three insets to each other rather than to numbers.
|
|
55
|
+
- **The title is on its plane's foreground token.** `text-card-foreground` on
|
|
56
|
+
the card fill, `text-secondary-foreground` on a secondary card, and
|
|
57
|
+
`text-foreground` for a transparent card on the page. Written out per plane
|
|
58
|
+
in the `tv()`, because Tailwind's scanner cannot see a class built at
|
|
59
|
+
runtime. The description is `muted-foreground` everywhere.
|
|
60
|
+
- **`Card.Title` is announced as a header**, so a screen reader's rotor can
|
|
61
|
+
step card to card through a list of them.
|
|
62
|
+
- **`Card.Action` is lifted out of the header's text column, wherever it is
|
|
63
|
+
written.** React Native has no grid to place a child by type, so the header
|
|
64
|
+
walks its children with `splitCardHeaderChildren` and renders the text column
|
|
65
|
+
as `flex-1` with the actions after it. The title wraps in the width an action
|
|
66
|
+
leaves instead of pushing it off the edge. The helper takes its predicate as
|
|
67
|
+
an argument so it stays in `card.variants.ts`, free of the part and of React
|
|
68
|
+
Native, and reachable from `bun test`.
|
|
69
|
+
- **Bare text in a header is its title.** A raw string inside a `View` is a
|
|
70
|
+
red box, and a header's text is its heading — the same wrap, for the same
|
|
71
|
+
reason, as [`ListGroup.Item`](../list-group/AGENTS.md).
|
|
72
|
+
- **Footer variants**: `default`, a wrapping row of actions; `band`, a strip
|
|
73
|
+
set into the card — `border-t`, the next fill down, and the card's bottom
|
|
74
|
+
corners. The band pulls itself down over the root's bottom padding with a
|
|
75
|
+
`-mb-*` of exactly that step and restates it as `py-*`, so it meets the edge
|
|
76
|
+
and the root's clip gives it the corners. That only works as the last child,
|
|
77
|
+
which the doc comment says.
|
|
78
|
+
- **A band's fill is a step on the ladder, not a colour.**
|
|
79
|
+
`resolveCardFooterFill` is `resolveSurfaceVariant` for the card's plane — the
|
|
80
|
+
same step a surface nested in the card would take — so a band never matches
|
|
81
|
+
the card it sits in, at any depth. A transparent card on the page has no
|
|
82
|
+
plane, and its band takes the card fill.
|
|
83
|
+
- **No interaction on the card.** A card is layout. A card that selects or
|
|
84
|
+
navigates is a `Pressable` around it — `<Pressable asChild>` or a wrapper —
|
|
85
|
+
which keeps `feedback`, `haptic` and the accessibility role at the call site
|
|
86
|
+
that knows what the press means. The playground's plan picker is the example.
|
|
87
|
+
- **No text treatment on the root, header, content or footer** (rule 1). The
|
|
88
|
+
tests assert it across every combination.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useCardPart } from "./card.context";
|
|
4
|
+
import type { CardSlotProps } from "./card.types";
|
|
5
|
+
import { cardVariants } from "./card.variants";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A control pinned to the header's top-right corner — a menu button, a badge, a
|
|
9
|
+
* link. `Card.Header` lifts it out of the text column wherever it is written, so
|
|
10
|
+
* the title and description keep the width it leaves.
|
|
11
|
+
*/
|
|
12
|
+
export function CardAction({ className, ...props }: CardSlotProps): ReactElement {
|
|
13
|
+
const { size } = useCardPart("Card.Action");
|
|
14
|
+
return <View className={cardVariants({ size }).action({ className })} {...props} />;
|
|
15
|
+
}
|
|
16
|
+
CardAction.displayName = "DelacourUI.Card.Action";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useCardPart } from "./card.context";
|
|
4
|
+
import type { CardSlotProps } from "./card.types";
|
|
5
|
+
import { cardVariants } from "./card.variants";
|
|
6
|
+
|
|
7
|
+
/** The card's body, inset to line up with the header and footer and spaced by the card's size. */
|
|
8
|
+
export function CardContent({ className, ...props }: CardSlotProps): ReactElement {
|
|
9
|
+
const { size } = useCardPart("Card.Content");
|
|
10
|
+
return <View className={cardVariants({ size }).content({ className })} {...props} />;
|
|
11
|
+
}
|
|
12
|
+
CardContent.displayName = "DelacourUI.Card.Content";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text } from "../text";
|
|
3
|
+
import { useCardPart } from "./card.context";
|
|
4
|
+
import type { CardTextProps } from "./card.types";
|
|
5
|
+
import { cardVariants } from "./card.variants";
|
|
6
|
+
|
|
7
|
+
/** The muted supporting line under the title, a step down the type scale from it. */
|
|
8
|
+
export function CardDescription({ className, ...props }: CardTextProps): ReactElement {
|
|
9
|
+
const { size } = useCardPart("Card.Description");
|
|
10
|
+
return <Text className={cardVariants({ size }).description({ className })} {...props} />;
|
|
11
|
+
}
|
|
12
|
+
CardDescription.displayName = "DelacourUI.Card.Description";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useCardPart } from "./card.context";
|
|
4
|
+
import type { CardSlotProps } from "./card.types";
|
|
5
|
+
import { type CardFooterVariant, cardVariants, resolveCardFooterFill } from "./card.variants";
|
|
6
|
+
|
|
7
|
+
export type CardFooterProps = CardSlotProps & {
|
|
8
|
+
/**
|
|
9
|
+
* `default` is a row of actions under the content. `band` sets it into the
|
|
10
|
+
* card as a strip of its own — a rule across the top, the next fill down, and
|
|
11
|
+
* the card's bottom corners — for a footer that is what someone does with the
|
|
12
|
+
* card rather than more of what it says.
|
|
13
|
+
*/
|
|
14
|
+
variant?: CardFooterVariant;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The card's last row — its actions, or a status line.
|
|
19
|
+
*
|
|
20
|
+
* A `band` footer steps its fill from the card's own plane rather than naming a
|
|
21
|
+
* colour, so it stays a step apart from the card however deep it is nested.
|
|
22
|
+
* It should be the card's last child: it pulls itself down over the card's
|
|
23
|
+
* bottom padding to meet the edge.
|
|
24
|
+
*/
|
|
25
|
+
export function CardFooter({ variant = "default", className, ...props }: CardFooterProps): ReactElement {
|
|
26
|
+
const { size, plane } = useCardPart("Card.Footer");
|
|
27
|
+
const slots = cardVariants({ footer: variant, footerFill: resolveCardFooterFill(plane), size });
|
|
28
|
+
return <View className={slots.footer({ className })} {...props} />;
|
|
29
|
+
}
|
|
30
|
+
CardFooter.displayName = "DelacourUI.Card.Footer";
|