@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,97 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
One section of content, shown and hidden by its own trigger. Compound root plus
|
|
4
|
+
`Collapsible.Trigger`, `Collapsible.Title`, `Collapsible.Description`,
|
|
5
|
+
`Collapsible.Indicator` and `Collapsible.Content`. It is a single
|
|
6
|
+
[Accordion](../accordion/AGENTS.md) item without the group — the same row, the
|
|
7
|
+
same measured panel, the same spring — so read that file's **The measured panel**
|
|
8
|
+
section first. What follows is only where a standalone disclosure differs.
|
|
9
|
+
|
|
10
|
+
`import { Collapsible } from "@delacour/react-native-ui/collapsible";`
|
|
11
|
+
|
|
12
|
+
## Files
|
|
13
|
+
|
|
14
|
+
| File | What it holds |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `index.ts` | → `@delacour/react-native-ui/collapsible` |
|
|
17
|
+
| `collapsible.tsx` | Root — owns the open state, `progress`, the measured height and the spring; the `Object.assign` |
|
|
18
|
+
| `collapsible-trigger.tsx` | `Collapsible.Trigger`, and the row it assembles from its children |
|
|
19
|
+
| `collapsible-title.tsx` | `Collapsible.Title` |
|
|
20
|
+
| `collapsible-description.tsx` | `Collapsible.Description` |
|
|
21
|
+
| `collapsible-indicator.tsx` | `Collapsible.Indicator`, the one rotation |
|
|
22
|
+
| `collapsible-content.tsx` | `Collapsible.Content` — the measured clip |
|
|
23
|
+
| `collapsible.context.tsx` | `CollapsibleContext`, `useCollapsible` and the internal part hook |
|
|
24
|
+
| `collapsible.types.ts` | `CollapsibleTextProps`, shared by the title, description and trigger |
|
|
25
|
+
| `collapsible.variants.ts` | Pure `tv()` slots, the constants, the toggle and the accessibility resolver — no RN imports |
|
|
26
|
+
| `collapsible.variants.test.ts` | `collapsibleTravelTarget`, pinned equal to the accordion's, and the JS-thread `contentHeight.value` sweep |
|
|
27
|
+
|
|
28
|
+
## Design
|
|
29
|
+
|
|
30
|
+
- **It looks exactly like a one-item `Accordion`, and a test holds it there.**
|
|
31
|
+
Variants (`default`, `secondary`, `tertiary`, `transparent`), sizes (`sm`,
|
|
32
|
+
`md`, `lg`), the trigger's metrics, the type scale, the glyph step and the
|
|
33
|
+
panel's inset are `Accordion`'s class for class. A collapsible and an accordion
|
|
34
|
+
on one screen are the same control at two scales; if either retunes, the test
|
|
35
|
+
fails until the other follows.
|
|
36
|
+
- **Its constants are restated, not imported from `../accordion`.** The registry
|
|
37
|
+
derives an item's dependencies from its imports, so importing even the
|
|
38
|
+
accordion's leaf would make `delacour add collapsible` copy an accordion in
|
|
39
|
+
too. The spring, fade, rotation, variants, sizes and glyph step are written
|
|
40
|
+
again here and pinned equal by `collapsible.variants.test.ts` — the test file
|
|
41
|
+
is the only thing that imports both, and tests are not shipped.
|
|
42
|
+
- **The root owns the state and the travel.** With no item layer, what
|
|
43
|
+
`Accordion.Item` owns — `progress`, `contentHeight`, the spring, the
|
|
44
|
+
`isMeasured` state `onMeasured` flips — lives on the root. The race it closes
|
|
45
|
+
(`onLayout` landing either side of React's effects) is the same one, so the
|
|
46
|
+
panel reports its first measurement and never starts the spring itself.
|
|
47
|
+
- **Nothing on the JS thread reads `contentHeight.value`** — the accordion's
|
|
48
|
+
Release-build bug, in the same shape: a JS read can lag the panel's queued
|
|
49
|
+
write, see `COLLAPSIBLE_UNMEASURED`, and leave the panel shut. "Measured" is
|
|
50
|
+
React state and the decision is `collapsibleTravelTarget`; the test sweeps both
|
|
51
|
+
files. See the accordion's AGENTS.md.
|
|
52
|
+
- **The disabled fade lands on the root.** Never on the trigger, which is a
|
|
53
|
+
`Pressable` whose `Animated.View` writes `opacity` every frame; the accordion
|
|
54
|
+
fades its item for the same reason, and a collapsible's root is its item.
|
|
55
|
+
|
|
56
|
+
## State
|
|
57
|
+
|
|
58
|
+
- **`isOpen` / `defaultOpen` / `onOpenChange`, `BottomSheet`'s names.** One
|
|
59
|
+
boolean through `useControllableState`, so it is controlled or uncontrolled
|
|
60
|
+
from one hook and `onOpenChange` hears the trigger and `toggle()` alike.
|
|
61
|
+
- **Disabled refuses the tap in both directions.** `toggleCollapsibleOpen`
|
|
62
|
+
returns the current state while disabled, so a disabled section that is open
|
|
63
|
+
stays open. A disabled control is one that cannot be used, not one that undoes
|
|
64
|
+
itself. The trigger is also a disabled `Pressable`; the pure rule is the second
|
|
65
|
+
line, and it is what keeps a caller's own `useCollapsible().toggle()` honest.
|
|
66
|
+
- **A refused toggle reports nothing.** The root compares the next state with
|
|
67
|
+
the current and skips `setOpen`, so `onOpenChange` never fires for a change
|
|
68
|
+
that did not happen.
|
|
69
|
+
- **`toggle` and the change callback are ref-backed**, so the context keeps its
|
|
70
|
+
identity across a caller passing a fresh arrow. `Accordion`'s trampoline.
|
|
71
|
+
|
|
72
|
+
## The trigger
|
|
73
|
+
|
|
74
|
+
- **A `Pressable` with `fade` and `haptic="selection"`**, `Accordion.Trigger`'s
|
|
75
|
+
two defaults for its two reasons. `onPress` is `Omit`ed: the press *is* the
|
|
76
|
+
toggle, and a side effect belongs on `onOpenChange`, which also hears changes
|
|
77
|
+
the trigger never sees.
|
|
78
|
+
- **It assembles its own row** — bare strings become one `Collapsible.Title`,
|
|
79
|
+
titles and descriptions stack in a column, anything else stays on the row, and
|
|
80
|
+
the indicator moves to the end or is composed in when absent. Same rule, same
|
|
81
|
+
limitation: an indicator must be a *direct* `Collapsible.Indicator` child, so
|
|
82
|
+
the indicator takes a render function (`({ isOpen }) => …`) rather than asking
|
|
83
|
+
for a wrapper.
|
|
84
|
+
|
|
85
|
+
## Accessibility
|
|
86
|
+
|
|
87
|
+
- **`resolveCollapsibleAccessibility` is the one source.** The trigger announces
|
|
88
|
+
`expanded` and `disabled` from it; the panel takes
|
|
89
|
+
`accessibilityElementsHidden` and `importantForAccessibility` from it. Pure, so
|
|
90
|
+
`bun test` holds the matrix.
|
|
91
|
+
- **A closed panel is out of the tree, not just clipped.** It stays mounted after
|
|
92
|
+
its first open, and mounted content is content a screen reader reads. A
|
|
93
|
+
disabled but open panel is still on screen and still read.
|
|
94
|
+
- **The root is a plain `View` with no role**, the accordion's rule.
|
|
95
|
+
- **Reduce motion is Reanimated's default `System` policy.** With it on, the
|
|
96
|
+
spring completes instantly: the panel snaps between its two states. The state
|
|
97
|
+
is carried by whether the panel is there, so that is the right degradation.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useState } from "react";
|
|
2
|
+
import { type LayoutChangeEvent, StyleSheet, View, type ViewProps } from "react-native";
|
|
3
|
+
import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated";
|
|
4
|
+
import { useCollapsiblePart } from "./collapsible.context";
|
|
5
|
+
import { COLLAPSIBLE_CONTENT_FADE, collapsibleVariants, resolveCollapsibleAccessibility } from "./collapsible.variants";
|
|
6
|
+
|
|
7
|
+
export type CollapsibleContentProps = Omit<ViewProps, "style"> & {
|
|
8
|
+
className?: string;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The body the trigger discloses.
|
|
14
|
+
*
|
|
15
|
+
* **Its height is measured and animated, never laid out.** The outer view clips,
|
|
16
|
+
* and its height runs from zero to whatever the inner view reported, so the
|
|
17
|
+
* animation lives on this one node and whatever sits below follows it through
|
|
18
|
+
* ordinary layout — nothing on the screen has to opt into anything.
|
|
19
|
+
*
|
|
20
|
+
* **The content mounts on first open and stays mounted**, so a form keeps what was
|
|
21
|
+
* typed and a list keeps where it was scrolled across every later close. A closed
|
|
22
|
+
* panel is therefore taken out of the accessibility tree and stops taking touches,
|
|
23
|
+
* rather than merely clipped to zero.
|
|
24
|
+
*
|
|
25
|
+
* It reports its first measurement and never starts the travel itself — the root
|
|
26
|
+
* owns the spring alone. See `onMeasured` on the context.
|
|
27
|
+
*
|
|
28
|
+
* `className` lands on the inner, measured layer, so padding set there is part of
|
|
29
|
+
* what the panel measures.
|
|
30
|
+
*/
|
|
31
|
+
export function CollapsibleContent({ className, children, ...props }: CollapsibleContentProps): ReactElement {
|
|
32
|
+
const { contentHeight, isDisabled, isOpen, onMeasured, progress, size } = useCollapsiblePart("Collapsible.Content");
|
|
33
|
+
const slots = collapsibleVariants({ size });
|
|
34
|
+
const { content: a11y } = resolveCollapsibleAccessibility({ isDisabled, isOpen });
|
|
35
|
+
|
|
36
|
+
// Adjusted during render rather than in an effect, so the panel mounts in the
|
|
37
|
+
// same commit as the tap that opened it.
|
|
38
|
+
const [hasOpened, setHasOpened] = useState(isOpen);
|
|
39
|
+
if (isOpen && !hasOpened) setHasOpened(true);
|
|
40
|
+
|
|
41
|
+
const [isMeasured, setMeasured] = useState(false);
|
|
42
|
+
|
|
43
|
+
const handleLayout = useCallback(
|
|
44
|
+
(event: LayoutChangeEvent) => {
|
|
45
|
+
contentHeight.value = event.nativeEvent.layout.height;
|
|
46
|
+
// "First" is this component's own state, never `contentHeight.value`: the
|
|
47
|
+
// write above is queued onto the UI runtime, and a read straight after it
|
|
48
|
+
// can still see the old value.
|
|
49
|
+
if (isMeasured) return;
|
|
50
|
+
setMeasured(true);
|
|
51
|
+
onMeasured();
|
|
52
|
+
},
|
|
53
|
+
[contentHeight, isMeasured, onMeasured]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const clipStyle = useAnimatedStyle(() => ({
|
|
57
|
+
height: progress.value * Math.max(contentHeight.value, 0),
|
|
58
|
+
opacity: interpolate(
|
|
59
|
+
progress.value,
|
|
60
|
+
[0, COLLAPSIBLE_CONTENT_FADE.start, COLLAPSIBLE_CONTENT_FADE.end, 1],
|
|
61
|
+
[0, 0, 1, 1]
|
|
62
|
+
),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
// Never `auto` while measuring: the clip is stretched over the collapsible for
|
|
66
|
+
// that commit, and an invisible layer over the trigger is a tap that goes nowhere.
|
|
67
|
+
const pointerEvents = isOpen && isMeasured ? "auto" : "none";
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Animated.View
|
|
71
|
+
accessibilityElementsHidden={a11y.accessibilityElementsHidden}
|
|
72
|
+
className={slots.content()}
|
|
73
|
+
importantForAccessibility={a11y.importantForAccessibility}
|
|
74
|
+
pointerEvents={pointerEvents}
|
|
75
|
+
style={isMeasured ? clipStyle : collapsibleStyleSheet.measuring}
|
|
76
|
+
>
|
|
77
|
+
{hasOpened ? (
|
|
78
|
+
<View className={slots.contentInner({ className })} onLayout={handleLayout} {...props}>
|
|
79
|
+
{children}
|
|
80
|
+
</View>
|
|
81
|
+
) : null}
|
|
82
|
+
</Animated.View>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
CollapsibleContent.displayName = "DelacourUI.Collapsible.Content";
|
|
86
|
+
|
|
87
|
+
const collapsibleStyleSheet = StyleSheet.create({
|
|
88
|
+
/**
|
|
89
|
+
* What the clip wears until the panel has reported a height.
|
|
90
|
+
*
|
|
91
|
+
* Absolutely positioned and transparent, so nothing on screen moves while it
|
|
92
|
+
* measures — and deliberately not `height: 0`, because an out-of-flow child
|
|
93
|
+
* mounted into a zero-height parent never fires `onLayout` at all. Found on a
|
|
94
|
+
* simulator by `Accordion.Content`, which carries the same sheet.
|
|
95
|
+
*/
|
|
96
|
+
measuring: { bottom: 0, left: 0, opacity: 0, position: "absolute", right: 0, top: 0 },
|
|
97
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text } from "../text";
|
|
3
|
+
import { useCollapsiblePart } from "./collapsible.context";
|
|
4
|
+
import type { CollapsibleTextProps } from "./collapsible.types";
|
|
5
|
+
import { collapsibleVariants } from "./collapsible.variants";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The trigger's secondary line, a step down in scale and on the muted token.
|
|
9
|
+
*
|
|
10
|
+
* Stacks under the title inside the column the trigger assembles, so the two
|
|
11
|
+
* need no wrapper part between them.
|
|
12
|
+
*/
|
|
13
|
+
export function CollapsibleDescription({ className, ...props }: CollapsibleTextProps): ReactElement {
|
|
14
|
+
const { size } = useCollapsiblePart("Collapsible.Description");
|
|
15
|
+
return <Text className={collapsibleVariants({ size }).description({ className })} {...props} />;
|
|
16
|
+
}
|
|
17
|
+
CollapsibleDescription.displayName = "DelacourUI.Collapsible.Description";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useMemo } from "react";
|
|
2
|
+
import type { ViewProps } from "react-native";
|
|
3
|
+
import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated";
|
|
4
|
+
import { IconChevronBottom } from "../../icons/central";
|
|
5
|
+
import { Icon, type IconDefaults, IconDefaultsProvider } from "../icon";
|
|
6
|
+
import { useCollapsiblePart } from "./collapsible.context";
|
|
7
|
+
import {
|
|
8
|
+
COLLAPSIBLE_INDICATOR_ROTATION,
|
|
9
|
+
COLLAPSIBLE_INDICATOR_TOKEN,
|
|
10
|
+
collapsibleVariants,
|
|
11
|
+
} from "./collapsible.variants";
|
|
12
|
+
|
|
13
|
+
/** What a render-function indicator is handed. */
|
|
14
|
+
export type CollapsibleIndicatorState = { isOpen: boolean };
|
|
15
|
+
|
|
16
|
+
export type CollapsibleIndicatorProps = Omit<ViewProps, "children" | "style"> & {
|
|
17
|
+
/**
|
|
18
|
+
* Turn with the panel. Set `false` for a glyph that *swaps* rather than
|
|
19
|
+
* rotates — a plus becoming a minus reads as broken when it also spins.
|
|
20
|
+
*/
|
|
21
|
+
isAnimated?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* A glyph, or a function handed the settled state.
|
|
25
|
+
*
|
|
26
|
+
* The function form exists so a swapping glyph needs no component of its own.
|
|
27
|
+
* The trigger finds its indicator by element type, so one wrapped in a
|
|
28
|
+
* component of the caller's own is invisible to it and a second, default one is
|
|
29
|
+
* composed in beside it — handing the state over removes the reason to write
|
|
30
|
+
* that wrapper.
|
|
31
|
+
*/
|
|
32
|
+
children?: ReactNode | ((state: CollapsibleIndicatorState) => ReactNode);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The glyph saying the section opens, turning as it does.
|
|
37
|
+
*
|
|
38
|
+
* Composed into a trigger that holds none, so a bare `<Collapsible.Trigger>` is
|
|
39
|
+
* already complete.
|
|
40
|
+
*
|
|
41
|
+
* **The rotation interpolates off the collapsible's own `progress`**, the value
|
|
42
|
+
* the panel's height reads, so the glyph and the panel are in phase by
|
|
43
|
+
* construction — and a custom glyph turns exactly like the default one. The
|
|
44
|
+
* rotation lives on this node rather than the trigger's because `Pressable`
|
|
45
|
+
* already owns `transform` there.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* <Collapsible.Indicator isAnimated={false}>
|
|
49
|
+
* {({ isOpen }) => <Icon icon={isOpen ? IconMinusSmall : IconPlusSmall} />}
|
|
50
|
+
* </Collapsible.Indicator>
|
|
51
|
+
*/
|
|
52
|
+
export function CollapsibleIndicator({
|
|
53
|
+
isAnimated = true,
|
|
54
|
+
className,
|
|
55
|
+
children,
|
|
56
|
+
...props
|
|
57
|
+
}: CollapsibleIndicatorProps): ReactElement {
|
|
58
|
+
const { isOpen, progress, size } = useCollapsiblePart("Collapsible.Indicator");
|
|
59
|
+
|
|
60
|
+
const slots = collapsibleVariants({ size });
|
|
61
|
+
const glyphClassName = slots.glyph();
|
|
62
|
+
|
|
63
|
+
const rotationStyle = useAnimatedStyle(() => ({
|
|
64
|
+
transform: [
|
|
65
|
+
{
|
|
66
|
+
rotate: `${interpolate(
|
|
67
|
+
progress.value,
|
|
68
|
+
[0, 1],
|
|
69
|
+
[COLLAPSIBLE_INDICATOR_ROTATION.collapsed, COLLAPSIBLE_INDICATOR_ROTATION.expanded]
|
|
70
|
+
)}deg`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
const iconDefaults = useMemo<IconDefaults>(
|
|
76
|
+
() => ({ className: glyphClassName, color: COLLAPSIBLE_INDICATOR_TOKEN }),
|
|
77
|
+
[glyphClassName]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Animated.View className={slots.indicator({ className })} style={isAnimated ? rotationStyle : undefined} {...props}>
|
|
82
|
+
<IconDefaultsProvider value={iconDefaults}>{renderGlyph(children, isOpen)}</IconDefaultsProvider>
|
|
83
|
+
</Animated.View>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
CollapsibleIndicator.displayName = "DelacourUI.Collapsible.Indicator";
|
|
87
|
+
|
|
88
|
+
/** The caller's glyph, the state they asked for, or the default chevron. */
|
|
89
|
+
function renderGlyph(children: CollapsibleIndicatorProps["children"], isOpen: boolean): ReactNode {
|
|
90
|
+
if (typeof children === "function") return children({ isOpen });
|
|
91
|
+
return children ?? <Icon icon={IconChevronBottom} />;
|
|
92
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text } from "../text";
|
|
3
|
+
import { useCollapsiblePart } from "./collapsible.context";
|
|
4
|
+
import type { CollapsibleTextProps } from "./collapsible.types";
|
|
5
|
+
import { collapsibleVariants } from "./collapsible.variants";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The trigger's primary line — what the section is called.
|
|
9
|
+
*
|
|
10
|
+
* Carries its own colour and type scale, read from the collapsible's context: a
|
|
11
|
+
* React Native `View` does not cascade colour to a `Text` descendant.
|
|
12
|
+
*/
|
|
13
|
+
export function CollapsibleTitle({ className, ...props }: CollapsibleTextProps): ReactElement {
|
|
14
|
+
const { size } = useCollapsiblePart("Collapsible.Title");
|
|
15
|
+
return <Text className={collapsibleVariants({ size }).title({ className })} {...props} />;
|
|
16
|
+
}
|
|
17
|
+
CollapsibleTitle.displayName = "DelacourUI.Collapsible.Title";
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Children, isValidElement, type ReactElement, type ReactNode, useMemo } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { type IconDefaults, IconDefaultsProvider } from "../icon";
|
|
4
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
5
|
+
import { useCollapsiblePart } from "./collapsible.context";
|
|
6
|
+
import {
|
|
7
|
+
COLLAPSIBLE_FOREGROUND_TOKEN,
|
|
8
|
+
collapsibleVariants,
|
|
9
|
+
resolveCollapsibleAccessibility,
|
|
10
|
+
} from "./collapsible.variants";
|
|
11
|
+
import { CollapsibleDescription } from "./collapsible-description";
|
|
12
|
+
import { CollapsibleIndicator } from "./collapsible-indicator";
|
|
13
|
+
import { CollapsibleTitle } from "./collapsible-title";
|
|
14
|
+
|
|
15
|
+
export type CollapsibleTriggerProps = Omit<PressableProps, "children" | "disabled" | "onPress"> & {
|
|
16
|
+
className?: string;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The row that opens and closes the section.
|
|
22
|
+
*
|
|
23
|
+
* A `Pressable`, so `feedback`, `haptic` and the rest are inherited. Two defaults
|
|
24
|
+
* differ, for `Accordion.Trigger`'s reasons: `fade`, because a full-bleed row that
|
|
25
|
+
* scales reads as the whole card flexing, and `haptic="selection"`, because the
|
|
26
|
+
* press is a state toggle.
|
|
27
|
+
*
|
|
28
|
+
* `onPress` is `Omit`ed rather than forwarded: the press **is** the toggle, and a
|
|
29
|
+
* side effect belongs on the root's `onOpenChange`, which also hears a controlled
|
|
30
|
+
* change the trigger never sees.
|
|
31
|
+
*
|
|
32
|
+
* It assembles its own row — titles and descriptions stack in a column, anything
|
|
33
|
+
* else stays where it was written, an indicator moves to the end, and one is
|
|
34
|
+
* composed in when the children hold none.
|
|
35
|
+
*/
|
|
36
|
+
export function CollapsibleTrigger({
|
|
37
|
+
feedback = "fade",
|
|
38
|
+
haptic = "selection",
|
|
39
|
+
className,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: CollapsibleTriggerProps): ReactElement {
|
|
43
|
+
const { isDisabled, isOpen, size, toggle } = useCollapsiblePart("Collapsible.Trigger");
|
|
44
|
+
const { trigger: a11y } = resolveCollapsibleAccessibility({ isDisabled, isOpen });
|
|
45
|
+
|
|
46
|
+
const slots = collapsibleVariants({ size });
|
|
47
|
+
const columnClassName = slots.triggerContent();
|
|
48
|
+
const glyphClassName = slots.glyph();
|
|
49
|
+
|
|
50
|
+
const content = useMemo(() => composeRow(children, columnClassName), [children, columnClassName]);
|
|
51
|
+
|
|
52
|
+
const iconDefaults = useMemo<IconDefaults>(
|
|
53
|
+
() => ({ className: glyphClassName, color: COLLAPSIBLE_FOREGROUND_TOKEN }),
|
|
54
|
+
[glyphClassName]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Pressable
|
|
59
|
+
accessibilityState={{ expanded: a11y.expanded }}
|
|
60
|
+
className={slots.trigger({ className })}
|
|
61
|
+
disabled={a11y.disabled}
|
|
62
|
+
feedback={feedback}
|
|
63
|
+
haptic={haptic}
|
|
64
|
+
onPress={toggle}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<IconDefaultsProvider value={iconDefaults}>{content}</IconDefaultsProvider>
|
|
68
|
+
</Pressable>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
CollapsibleTrigger.displayName = "DelacourUI.Collapsible.Trigger";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Arranges the trigger's children into its row.
|
|
75
|
+
*
|
|
76
|
+
* Bare strings become one `Collapsible.Title` (a raw string outside a `<Text>`
|
|
77
|
+
* red-boxes), titles and descriptions stack in a column, anything else stays on
|
|
78
|
+
* the row where it was written, and the indicator goes last — composed in when
|
|
79
|
+
* there is none. `Accordion.Trigger`'s arrangement, rule for rule.
|
|
80
|
+
*/
|
|
81
|
+
function composeRow(children: ReactNode, columnClassName: string): ReactNode {
|
|
82
|
+
const items = Children.toArray(children);
|
|
83
|
+
const row: ReactNode[] = [];
|
|
84
|
+
const trailing: ReactNode[] = [];
|
|
85
|
+
let column: ReactNode[] = [];
|
|
86
|
+
let run: (string | number)[] = [];
|
|
87
|
+
let hasIndicator = false;
|
|
88
|
+
|
|
89
|
+
const flushRun = () => {
|
|
90
|
+
if (run.length === 0) return;
|
|
91
|
+
column.push(<CollapsibleTitle key={`title-${column.length}`}>{run.join("")}</CollapsibleTitle>);
|
|
92
|
+
run = [];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const flushColumn = () => {
|
|
96
|
+
if (column.length === 0) return;
|
|
97
|
+
row.push(
|
|
98
|
+
<View className={columnClassName} key={`content-${row.length}`}>
|
|
99
|
+
{column}
|
|
100
|
+
</View>
|
|
101
|
+
);
|
|
102
|
+
column = [];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const child of items) {
|
|
106
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
107
|
+
run.push(child);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
flushRun();
|
|
111
|
+
|
|
112
|
+
if (isValidElement(child) && child.type === CollapsibleIndicator) {
|
|
113
|
+
hasIndicator = true;
|
|
114
|
+
trailing.push(child);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isValidElement(child) && (child.type === CollapsibleTitle || child.type === CollapsibleDescription)) {
|
|
119
|
+
column.push(child);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
flushColumn();
|
|
124
|
+
row.push(child);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
flushRun();
|
|
128
|
+
flushColumn();
|
|
129
|
+
|
|
130
|
+
if (!hasIndicator) trailing.push(<CollapsibleIndicator key="indicator" />);
|
|
131
|
+
|
|
132
|
+
return [...row, ...trailing];
|
|
133
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
3
|
+
import type { CollapsibleSize, CollapsibleVariant } from "./collapsible.variants";
|
|
4
|
+
|
|
5
|
+
export type CollapsibleContextValue = {
|
|
6
|
+
variant: CollapsibleVariant;
|
|
7
|
+
size: CollapsibleSize;
|
|
8
|
+
/** The trigger is inert and the surface faded. An open section stays open. */
|
|
9
|
+
isDisabled: boolean;
|
|
10
|
+
/** Whether the panel is open, on the **JS thread** — what accessibility reads. */
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
/** Opens or closes the panel. Refused while disabled. Stable for the collapsible's lifetime. */
|
|
13
|
+
toggle: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* How far open the panel is, on the **UI thread**: `0` closed, `1` open.
|
|
16
|
+
*
|
|
17
|
+
* The one value every animated style reads — the panel's height, its opacity
|
|
18
|
+
* and the indicator's rotation — so they cannot drift out of step by a frame.
|
|
19
|
+
*/
|
|
20
|
+
progress: SharedValue<number>;
|
|
21
|
+
/**
|
|
22
|
+
* The panel's natural height in points, or {@link COLLAPSIBLE_UNMEASURED} until it
|
|
23
|
+
* has reported. Read on the UI runtime only, by the height style — a JS-thread read
|
|
24
|
+
* can lag the panel's write. See {@link onMeasured}.
|
|
25
|
+
*/
|
|
26
|
+
contentHeight: SharedValue<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Told by the panel that it has measured itself for the first time.
|
|
29
|
+
*
|
|
30
|
+
* Internal machinery: the root is the only owner of the spring, and this flips
|
|
31
|
+
* its `isMeasured` state — the reason its effect re-runs once there is a height
|
|
32
|
+
* to travel against, and how it knows, never by reading `contentHeight`. The
|
|
33
|
+
* panel cannot start the spring itself — `onLayout` lands either side of
|
|
34
|
+
* React's effects, and a spring started there is sometimes cancelled by the
|
|
35
|
+
* cleanup of the effect it raced. `Accordion.Item`'s `onMeasured`, verbatim.
|
|
36
|
+
*/
|
|
37
|
+
onMeasured: () => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const CollapsibleContext = createContext<CollapsibleContextValue | null>(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Supplies one collapsible's axes, state and shared values to its parts.
|
|
44
|
+
*
|
|
45
|
+
* Lives in its own module, importing nothing but React and types, so a part can
|
|
46
|
+
* read it without importing `./collapsible` — which would close a cycle.
|
|
47
|
+
*/
|
|
48
|
+
export function CollapsibleProvider({
|
|
49
|
+
value,
|
|
50
|
+
children,
|
|
51
|
+
}: {
|
|
52
|
+
value: CollapsibleContextValue;
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
}): ReactElement {
|
|
55
|
+
return <CollapsibleContext value={value}>{children}</CollapsibleContext>;
|
|
56
|
+
}
|
|
57
|
+
CollapsibleProvider.displayName = "DelacourUI.Collapsible.Provider";
|
|
58
|
+
|
|
59
|
+
/** The enclosing collapsible's state, or null outside a `<Collapsible>`. */
|
|
60
|
+
export function useCollapsibleContext(): CollapsibleContextValue | null {
|
|
61
|
+
return use(CollapsibleContext);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reads the enclosing collapsible's state.
|
|
66
|
+
*
|
|
67
|
+
* What a custom child reaches for: `isOpen` to swap a glyph or a label,
|
|
68
|
+
* `progress` to animate against exactly the travel the panel is running, or
|
|
69
|
+
* `toggle` to open it from somewhere other than the trigger. Throws outside a
|
|
70
|
+
* `<Collapsible>` — use {@link useCollapsibleContext} where one is optional.
|
|
71
|
+
*/
|
|
72
|
+
export function useCollapsible(): CollapsibleContextValue {
|
|
73
|
+
const context = useCollapsibleContext();
|
|
74
|
+
if (!context) {
|
|
75
|
+
throw new Error("useCollapsible must be called inside a <Collapsible>.");
|
|
76
|
+
}
|
|
77
|
+
return context;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The enclosing collapsible's state, for a compound part that cannot work without one.
|
|
82
|
+
*
|
|
83
|
+
* Internal: deliberately not re-exported from `index.ts`.
|
|
84
|
+
*/
|
|
85
|
+
export function useCollapsiblePart(component: string): CollapsibleContextValue {
|
|
86
|
+
const context = useCollapsibleContext();
|
|
87
|
+
if (!context) {
|
|
88
|
+
throw new Error(`${component} must be rendered inside a <Collapsible>.`);
|
|
89
|
+
}
|
|
90
|
+
return context;
|
|
91
|
+
}
|