@delacour/react-native-ui 0.1.0-alpha.3 → 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 +11 -7
- 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 +5 -0
- package/src/components/badge/badge.tsx +6 -1
- package/src/components/bottom-sheet/AGENTS.md +186 -221
- 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 -207
- 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 -120
- 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 -52
- 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 -70
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
- 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 +9 -1
- 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/screen/screen-navbar-back-button.tsx +1 -1
- 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
|
@@ -41,6 +41,11 @@ export type ButtonSpinnerPlacement = (typeof BUTTON_SPINNER_PLACEMENTS)[number];
|
|
|
41
41
|
export type ButtonGroupOrientation = (typeof BUTTON_GROUP_ORIENTATIONS)[number];
|
|
42
42
|
export type ButtonGroupPosition = (typeof BUTTON_GROUP_POSITIONS)[number];
|
|
43
43
|
|
|
44
|
+
/** How a group shapes its run. Omitted, a group joins — that is what a group is. */
|
|
45
|
+
export type ButtonGroupShapeOptions = {
|
|
46
|
+
isAttached?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
44
49
|
/** A member's place in its group, or `none` for a button standing on its own. */
|
|
45
50
|
export type ButtonGroupSlotPosition = ButtonGroupPosition | "none";
|
|
46
51
|
|
|
@@ -135,8 +140,9 @@ export const buttonVariants = tv({
|
|
|
135
140
|
/**
|
|
136
141
|
* A `Button.Group`'s box.
|
|
137
142
|
*
|
|
138
|
-
* Holds no `gap` and no `overflow-hidden
|
|
139
|
-
* load-bearing. A gap is the seam
|
|
143
|
+
* Holds no `gap` while attached and no `overflow-hidden` ever, and both
|
|
144
|
+
* absences are load-bearing. A gap is the seam a joined run exists to
|
|
145
|
+
* close — only `isAttached: false` adds one, where there is no seam. A clip
|
|
140
146
|
* would square off the very corners the position compounds just rounded,
|
|
141
147
|
* and would have to restate the group's own corner to avoid it — while the
|
|
142
148
|
* members already square themselves, which is the whole point.
|
|
@@ -192,6 +198,21 @@ export const buttonVariants = tv({
|
|
|
192
198
|
horizontal: { group: "flex-row self-start" },
|
|
193
199
|
vertical: { group: "flex-col" },
|
|
194
200
|
},
|
|
201
|
+
// Declared after `orientation` on purpose: `tv` merges in declaration
|
|
202
|
+
// order, so `self-stretch` here replaces the horizontal run's `self-start`
|
|
203
|
+
// rather than losing to it.
|
|
204
|
+
/** A `Button.Group` that spans its parent. The members split it — see `isStretched`. */
|
|
205
|
+
isFullWidth: { true: { group: "w-full self-stretch" }, false: {} },
|
|
206
|
+
/** A `Button.Group` whose members join into one shape. Detached, they stand apart by a gap. */
|
|
207
|
+
isAttached: { true: {}, false: { group: "gap-2" } },
|
|
208
|
+
/**
|
|
209
|
+
* A member of a full-width horizontal run, taking an equal share of it.
|
|
210
|
+
*
|
|
211
|
+
* `basis-0` is the half that matters. With `flex-1` alone Yoga shares out
|
|
212
|
+
* only the space left over after each member's content width, so the
|
|
213
|
+
* widths still follow the labels; from a zero basis every share is equal.
|
|
214
|
+
*/
|
|
215
|
+
isStretched: { true: { root: "flex-1 basis-0" }, false: {} },
|
|
195
216
|
// `middle` is the one position whose corner needs neither the size nor the
|
|
196
217
|
// orientation, so it lands here rather than in six compound cells that
|
|
197
218
|
// would all say the same word.
|
|
@@ -304,6 +325,9 @@ export const buttonVariants = tv({
|
|
|
304
325
|
orientation: "horizontal",
|
|
305
326
|
groupPosition: "none",
|
|
306
327
|
isSeamed: false,
|
|
328
|
+
isFullWidth: false,
|
|
329
|
+
isAttached: true,
|
|
330
|
+
isStretched: false,
|
|
307
331
|
isDisabled: false,
|
|
308
332
|
isLoading: false,
|
|
309
333
|
isDimmedWhileLoading: false,
|
|
@@ -413,9 +437,18 @@ export function resolveGroupedButtonSize(own: ButtonSize | undefined, group: But
|
|
|
413
437
|
* trade {@link resolveSpinnerSwapIndex} already makes. React Native has no
|
|
414
438
|
* sibling selector, so this is the only place a member's corner can be decided.
|
|
415
439
|
*
|
|
440
|
+
* A detached run (`isAttached: false`) places every member `only` — the lone
|
|
441
|
+
* position, which draws the lone button's corner — rather than minting a fourth
|
|
442
|
+
* sentinel: a spaced member *is* a button on its own that happens to share props.
|
|
443
|
+
*
|
|
416
444
|
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
417
445
|
*/
|
|
418
|
-
export function resolveGroupPositions(
|
|
446
|
+
export function resolveGroupPositions(
|
|
447
|
+
isMember: readonly boolean[],
|
|
448
|
+
{ isAttached = true }: ButtonGroupShapeOptions = {}
|
|
449
|
+
): (ButtonGroupPosition | null)[] {
|
|
450
|
+
if (!isAttached) return isMember.map((member) => (member ? "only" : null));
|
|
451
|
+
|
|
419
452
|
const count = isMember.reduce((total, member) => (member ? total + 1 : total), 0);
|
|
420
453
|
let seen = 0;
|
|
421
454
|
|
|
@@ -445,9 +478,16 @@ export function resolveGroupPositions(isMember: readonly boolean[]): (ButtonGrou
|
|
|
445
478
|
* `isMember[-1]` is `undefined`, so the first child falls out as `false` without
|
|
446
479
|
* a case of its own.
|
|
447
480
|
*
|
|
481
|
+
* A detached run has no shared edge, so nothing overlaps: a point of overlap
|
|
482
|
+
* there would only eat into the gap.
|
|
483
|
+
*
|
|
448
484
|
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
449
485
|
*/
|
|
450
|
-
export function resolveGroupSeams(
|
|
486
|
+
export function resolveGroupSeams(
|
|
487
|
+
isMember: readonly boolean[],
|
|
488
|
+
{ isAttached = true }: ButtonGroupShapeOptions = {}
|
|
489
|
+
): boolean[] {
|
|
490
|
+
if (!isAttached) return isMember.map(() => false);
|
|
451
491
|
return isMember.map((member, index) => member && isMember[index - 1] === true);
|
|
452
492
|
}
|
|
453
493
|
|
|
@@ -463,6 +503,10 @@ export function resolveGroupSeams(isMember: readonly boolean[]): boolean[] {
|
|
|
463
503
|
* while its neighbours hold still, so the seam the group exists to close tears
|
|
464
504
|
* open for the length of the press. `fade` moves no geometry.
|
|
465
505
|
*
|
|
506
|
+
* Only while the group is *attached*. A detached run has no seam to tear, so
|
|
507
|
+
* its members fall back to the lone button's `scale`. `isAttached` defaults to
|
|
508
|
+
* `true`, because joining is what a group does when it says nothing.
|
|
509
|
+
*
|
|
466
510
|
* A caller still gets `scale` by asking for it, on one button or on a whole
|
|
467
511
|
* group. `pressedScale` beats both, because `resolvePressedState` lets an
|
|
468
512
|
* explicit value win on the axis it names — see `pressable.variants.ts`.
|
|
@@ -472,9 +516,30 @@ export function resolveGroupSeams(isMember: readonly boolean[]): boolean[] {
|
|
|
472
516
|
export function resolveButtonFeedback(
|
|
473
517
|
own: PressableFeedback | undefined,
|
|
474
518
|
group: PressableFeedback | undefined,
|
|
475
|
-
isGrouped: boolean
|
|
519
|
+
isGrouped: boolean,
|
|
520
|
+
isAttached = true
|
|
476
521
|
): PressableFeedback {
|
|
477
|
-
return own ?? group ?? (isGrouped ? BUTTON_GROUP_FEEDBACK : BUTTON_FEEDBACK);
|
|
522
|
+
return own ?? group ?? (isGrouped && isAttached ? BUTTON_GROUP_FEEDBACK : BUTTON_FEEDBACK);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Whether a group's members take an equal share of its width.
|
|
527
|
+
*
|
|
528
|
+
* Horizontal only. A vertical run already stretches its members on the cross
|
|
529
|
+
* axis, and `basis-0` on the main axis of a column with no definite height
|
|
530
|
+
* would collapse every member to nothing — so `isFullWidth` on a vertical group
|
|
531
|
+
* widens the group and does nothing further.
|
|
532
|
+
*
|
|
533
|
+
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
534
|
+
*/
|
|
535
|
+
export function resolveGroupMemberStretch({
|
|
536
|
+
isFullWidth,
|
|
537
|
+
orientation,
|
|
538
|
+
}: {
|
|
539
|
+
isFullWidth: boolean;
|
|
540
|
+
orientation: ButtonGroupOrientation;
|
|
541
|
+
}): boolean {
|
|
542
|
+
return isFullWidth && orientation === "horizontal";
|
|
478
543
|
}
|
|
479
544
|
|
|
480
545
|
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`.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { type AccessibilityActionEvent, View, type ViewProps } from "react-native";
|
|
3
|
+
import { IconChevronDownSmall } from "../../icons/central";
|
|
4
|
+
import { Icon } from "../icon";
|
|
5
|
+
import { Pressable } from "../pressable";
|
|
6
|
+
import { Text } from "../text";
|
|
7
|
+
import { useCalendarPart } from "./calendar.context";
|
|
8
|
+
import { formatMonthYear, formatYear } from "./calendar.date";
|
|
9
|
+
import { calendarVariants, resolvePartTestID } from "./calendar.variants";
|
|
10
|
+
|
|
11
|
+
export type CalendarCaptionProps = Omit<ViewProps, "children"> & {
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const PAGE_ACTIONS = [
|
|
16
|
+
{ name: "decrement", label: "Previous" },
|
|
17
|
+
{ name: "increment", label: "Next" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The month and year above the grid.
|
|
22
|
+
*
|
|
23
|
+
* With `captionLayout="picker"` it is a button: in the grid it opens the months view, in the
|
|
24
|
+
* months view the years view, and in the years view it returns to the grid. With `"label"` it is
|
|
25
|
+
* text.
|
|
26
|
+
*
|
|
27
|
+
* **It is where a screen-reader user pages.** A grid of buttons cannot itself be an adjustable
|
|
28
|
+
* element — iOS and Android both fold an accessible container's children into it, and the days
|
|
29
|
+
* would become unreachable — so the caption carries the increment and decrement actions instead:
|
|
30
|
+
* swipe up or down on "October 2026" to move a month. As a plain label it takes the `adjustable`
|
|
31
|
+
* role outright; as a button the two actions sit beside its activation.
|
|
32
|
+
*/
|
|
33
|
+
export function CalendarCaption({ className, ...props }: CalendarCaptionProps): ReactElement {
|
|
34
|
+
const {
|
|
35
|
+
canGoNext,
|
|
36
|
+
canGoPrev,
|
|
37
|
+
captionLayout,
|
|
38
|
+
goNext,
|
|
39
|
+
goPrev,
|
|
40
|
+
isDisabled,
|
|
41
|
+
locale,
|
|
42
|
+
setView,
|
|
43
|
+
size,
|
|
44
|
+
testID,
|
|
45
|
+
variant,
|
|
46
|
+
view,
|
|
47
|
+
visibleMonth,
|
|
48
|
+
yearSpan,
|
|
49
|
+
} = useCalendarPart("Calendar.Caption");
|
|
50
|
+
const slots = calendarVariants({ size, variant });
|
|
51
|
+
|
|
52
|
+
const text =
|
|
53
|
+
view === "days"
|
|
54
|
+
? formatMonthYear(visibleMonth, locale)
|
|
55
|
+
: view === "months"
|
|
56
|
+
? formatYear(visibleMonth.year, locale)
|
|
57
|
+
: `${formatYear(yearSpan.from, locale)} – ${formatYear(yearSpan.to, locale)}`;
|
|
58
|
+
|
|
59
|
+
const handleAction = useCallback(
|
|
60
|
+
(event: AccessibilityActionEvent) => {
|
|
61
|
+
if (event.nativeEvent.actionName === "increment" && canGoNext) goNext();
|
|
62
|
+
if (event.nativeEvent.actionName === "decrement" && canGoPrev) goPrev();
|
|
63
|
+
},
|
|
64
|
+
[canGoNext, canGoPrev, goNext, goPrev]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const handlePress = useCallback(() => {
|
|
68
|
+
setView(view === "days" ? "months" : view === "months" ? "years" : "days");
|
|
69
|
+
}, [setView, view]);
|
|
70
|
+
|
|
71
|
+
const label = <Text className={slots.captionText()}>{text}</Text>;
|
|
72
|
+
|
|
73
|
+
if (captionLayout === "label") {
|
|
74
|
+
return (
|
|
75
|
+
<View
|
|
76
|
+
accessibilityActions={PAGE_ACTIONS}
|
|
77
|
+
accessibilityLabel={text}
|
|
78
|
+
accessibilityRole="adjustable"
|
|
79
|
+
accessible
|
|
80
|
+
className={slots.caption({ className })}
|
|
81
|
+
onAccessibilityAction={handleAction}
|
|
82
|
+
testID={resolvePartTestID(testID, "caption")}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
{label}
|
|
86
|
+
</View>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const hint =
|
|
91
|
+
view === "days"
|
|
92
|
+
? "Opens the month and year picker"
|
|
93
|
+
: view === "months"
|
|
94
|
+
? "Opens the year list"
|
|
95
|
+
: "Returns to the month";
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Pressable
|
|
99
|
+
accessibilityActions={PAGE_ACTIONS}
|
|
100
|
+
accessibilityHint={hint}
|
|
101
|
+
accessibilityLabel={text}
|
|
102
|
+
className={slots.caption({ className })}
|
|
103
|
+
disabled={isDisabled}
|
|
104
|
+
feedback="fade"
|
|
105
|
+
haptic="selection"
|
|
106
|
+
onAccessibilityAction={handleAction}
|
|
107
|
+
onPress={handlePress}
|
|
108
|
+
testID={resolvePartTestID(testID, "caption")}
|
|
109
|
+
{...props}
|
|
110
|
+
>
|
|
111
|
+
{label}
|
|
112
|
+
<Icon color="muted-foreground" icon={IconChevronDownSmall} size="sm" />
|
|
113
|
+
</Pressable>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
CalendarCaption.displayName = "DelacourUI.Calendar.Caption";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { Pressable } from "../pressable";
|
|
4
|
+
import { Text } from "../text";
|
|
5
|
+
import { useCalendarPart } from "./calendar.context";
|
|
6
|
+
import { type CalendarDate, formatDate, isSameMonth, serialiseDate } from "./calendar.date";
|
|
7
|
+
import { type CalendarDayState, calendarVariants, resolveDayState, resolvePartTestID } from "./calendar.variants";
|
|
8
|
+
|
|
9
|
+
/** What a function child of `Calendar.Day` is handed. */
|
|
10
|
+
export type CalendarDayRenderProps = CalendarDayState & {
|
|
11
|
+
date: CalendarDate;
|
|
12
|
+
/** The class the default number is drawn with — size, weight and colour for this state. */
|
|
13
|
+
labelClassName: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CalendarDayProps = {
|
|
17
|
+
/** The day this cell draws. */
|
|
18
|
+
date: CalendarDate;
|
|
19
|
+
/**
|
|
20
|
+
* Custom content inside the circle, in place of the number. A function is handed the day's
|
|
21
|
+
* state, so a price under the number can take the selected colour with it.
|
|
22
|
+
*/
|
|
23
|
+
children?: ReactNode | ((props: CalendarDayRenderProps) => ReactNode);
|
|
24
|
+
className?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** The state words a screen reader adds after the date. */
|
|
28
|
+
function describeState(state: CalendarDayState): string | undefined {
|
|
29
|
+
const parts: string[] = [];
|
|
30
|
+
if (state.isToday) parts.push("Today");
|
|
31
|
+
if (state.rangeRole === "start") parts.push("Range start");
|
|
32
|
+
if (state.rangeRole === "end") parts.push("Range end");
|
|
33
|
+
return parts.length > 0 ? parts.join(", ") : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One day: its column share, the range band behind it, and the circle with the number.
|
|
38
|
+
*
|
|
39
|
+
* **The whole cell is the target**, not the circle — a column on a phone is wider than the
|
|
40
|
+
* circle is tall, and the gap between two circles belongs to whichever day it sits beside.
|
|
41
|
+
*
|
|
42
|
+
* The band is drawn for a range's start, middle and end and never for a lone day, which is a
|
|
43
|
+
* circle with nothing to join. An outside day with `showOutsideDays` off is an empty cell of the
|
|
44
|
+
* same height, so the grid's rows never move.
|
|
45
|
+
*
|
|
46
|
+
* Announced as a button with the full date — "Monday 5 October 2026" — plus today and the range
|
|
47
|
+
* end it is, and `selected` and `disabled` as accessibility state.
|
|
48
|
+
*/
|
|
49
|
+
export function CalendarDay({ date, children, className }: CalendarDayProps): ReactElement {
|
|
50
|
+
const calendar = useCalendarPart("Calendar.Day");
|
|
51
|
+
const state = resolveDayState({
|
|
52
|
+
cell: { date, isOutside: !isSameMonth(date, calendar.visibleMonth), weekIndex: 0, dayIndex: 0 },
|
|
53
|
+
isCalendarDisabled: calendar.isDisabled,
|
|
54
|
+
isDisabled: calendar.isDateDisabled,
|
|
55
|
+
isReadOnly: calendar.isReadOnly,
|
|
56
|
+
selectOutsideDays: calendar.selectOutsideDays,
|
|
57
|
+
selection: calendar.selection,
|
|
58
|
+
showOutsideDays: calendar.showOutsideDays,
|
|
59
|
+
today: calendar.today,
|
|
60
|
+
});
|
|
61
|
+
const slots = calendarVariants({
|
|
62
|
+
isDisabled: state.isDisabled,
|
|
63
|
+
isInvalid: calendar.isInvalid,
|
|
64
|
+
isOutside: state.isOutside,
|
|
65
|
+
isToday: state.isToday,
|
|
66
|
+
rangeRole: state.rangeRole ?? "none",
|
|
67
|
+
size: calendar.size,
|
|
68
|
+
tone: state.tone,
|
|
69
|
+
variant: calendar.variant,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const { select } = calendar;
|
|
73
|
+
const handlePress = useCallback(() => select(date), [date, select]);
|
|
74
|
+
|
|
75
|
+
if (state.isHidden) {
|
|
76
|
+
return <View className={slots.cell({ className })} importantForAccessibility="no-hide-descendants" />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const labelClassName = slots.dayLabel();
|
|
80
|
+
const content =
|
|
81
|
+
typeof children === "function"
|
|
82
|
+
? children({ ...state, date, labelClassName })
|
|
83
|
+
: (children ?? <Text className={labelClassName}>{date.day}</Text>);
|
|
84
|
+
const showsBand = state.rangeRole !== null && state.rangeRole !== "only";
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Pressable
|
|
88
|
+
accessibilityHint={describeState(state)}
|
|
89
|
+
accessibilityLabel={formatDate(date, { locale: calendar.locale, style: "full" })}
|
|
90
|
+
accessibilityState={{ selected: state.isSelected }}
|
|
91
|
+
className={slots.cell({ className })}
|
|
92
|
+
disabled={!state.isPressable}
|
|
93
|
+
feedback="fade"
|
|
94
|
+
haptic="selection"
|
|
95
|
+
onPress={handlePress}
|
|
96
|
+
testID={resolvePartTestID(calendar.testID, `day-${serialiseDate(date)}`)}
|
|
97
|
+
>
|
|
98
|
+
{showsBand ? <View className={slots.band()} /> : null}
|
|
99
|
+
<View className={slots.dayBase()}>{content}</View>
|
|
100
|
+
</Pressable>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
CalendarDay.displayName = "DelacourUI.Calendar.Day";
|