@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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { type AccessibilityActionEvent, View, type ViewProps, type ViewStyle } from "react-native";
|
|
3
|
+
import Animated, {
|
|
4
|
+
ReduceMotion,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useReducedMotion,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
withSpring,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from "react-native-reanimated";
|
|
11
|
+
import { Pressable } from "../pressable";
|
|
12
|
+
import {
|
|
13
|
+
type SelectionModeItemContextValue,
|
|
14
|
+
SelectionModeItemProvider,
|
|
15
|
+
useSelectionModePart,
|
|
16
|
+
} from "./selection-mode.context";
|
|
17
|
+
import {
|
|
18
|
+
resolveIndicatorShown,
|
|
19
|
+
resolveItemAccessibility,
|
|
20
|
+
resolveItemPress,
|
|
21
|
+
SELECTION_MODE_INDICATOR_OFFSET,
|
|
22
|
+
SELECTION_MODE_MOTION,
|
|
23
|
+
type SelectionIndicator,
|
|
24
|
+
selectionModeVariants,
|
|
25
|
+
} from "./selection-mode.variants";
|
|
26
|
+
import { SelectionModeIndicator, SelectionModeRing } from "./selection-mode-indicator";
|
|
27
|
+
|
|
28
|
+
export type SelectionModeItemProps = Omit<ViewProps, "children"> & {
|
|
29
|
+
/** The item's id. Selection is a set of ids, never indices. */
|
|
30
|
+
value: string;
|
|
31
|
+
/** What it wraps — a row, an avatar, a swatch. */
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
/** The item's own press, run while the mode is off — and always, for a disabled item. */
|
|
34
|
+
onPress?: () => void;
|
|
35
|
+
/** Cannot start the mode or be picked. Its own `onPress` still runs. */
|
|
36
|
+
isDisabled?: boolean;
|
|
37
|
+
/** How it shows it is picked. Default `leading`. */
|
|
38
|
+
indicator?: SelectionIndicator;
|
|
39
|
+
/** Show the indicator even while the mode is off — an always-on picker. */
|
|
40
|
+
isIndicatorAlwaysShown?: boolean;
|
|
41
|
+
className?: string;
|
|
42
|
+
/** Reshapes the `ring` to the item's own corner — `rounded-full` around a round swatch. */
|
|
43
|
+
ringClassName?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Slides a `leading` indicator in: a spacer opens from 0 to 34pt and pushes the
|
|
48
|
+
* content over while the mark fades in. Under reduced motion the spacer snaps
|
|
49
|
+
* and only the fade plays, 150ms.
|
|
50
|
+
*/
|
|
51
|
+
function useLeadingStyles(isShown: boolean): {
|
|
52
|
+
spacerStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
|
|
53
|
+
markStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
|
|
54
|
+
} {
|
|
55
|
+
const isReducedMotion = useReducedMotion();
|
|
56
|
+
const slide = useSharedValue(isShown ? 1 : 0);
|
|
57
|
+
const fade = useSharedValue(isShown ? 1 : 0);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const target = isShown ? 1 : 0;
|
|
61
|
+
if (isReducedMotion) {
|
|
62
|
+
slide.value = target;
|
|
63
|
+
fade.value = withTiming(target, {
|
|
64
|
+
duration: SELECTION_MODE_MOTION.reducedFadeMs,
|
|
65
|
+
reduceMotion: ReduceMotion.Never,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
slide.value = withSpring(target, SELECTION_MODE_MOTION.spring);
|
|
70
|
+
fade.value = withTiming(target, { duration: SELECTION_MODE_MOTION.fadeMs });
|
|
71
|
+
}, [fade, isReducedMotion, isShown, slide]);
|
|
72
|
+
|
|
73
|
+
const offset = SELECTION_MODE_INDICATOR_OFFSET;
|
|
74
|
+
const spacerStyle = useAnimatedStyle(() => ({ width: slide.value * offset }));
|
|
75
|
+
const markStyle = useAnimatedStyle(() => ({ opacity: fade.value }));
|
|
76
|
+
return { markStyle, spacerStyle };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One pickable thing. Wraps whatever it holds rather than replacing it.
|
|
81
|
+
*
|
|
82
|
+
* Off, a press runs the item's own `onPress` and a long press turns the mode on
|
|
83
|
+
* with this item picked. On, a press toggles it and `onPress` does not run. A
|
|
84
|
+
* disabled item never toggles and never starts the mode, but keeps its own
|
|
85
|
+
* press — a section header, a "load more" row.
|
|
86
|
+
*
|
|
87
|
+
* While the mode is on it is announced as a checkbox; while off it keeps the
|
|
88
|
+
* semantics of what it wraps and offers "Start selecting" as an action.
|
|
89
|
+
*/
|
|
90
|
+
export function SelectionModeItem({
|
|
91
|
+
value,
|
|
92
|
+
children,
|
|
93
|
+
onPress,
|
|
94
|
+
isDisabled = false,
|
|
95
|
+
indicator = "leading",
|
|
96
|
+
isIndicatorAlwaysShown = false,
|
|
97
|
+
className,
|
|
98
|
+
ringClassName,
|
|
99
|
+
onAccessibilityAction,
|
|
100
|
+
...props
|
|
101
|
+
}: SelectionModeItemProps): ReactElement {
|
|
102
|
+
const selection = useSelectionModePart("SelectionMode.Item");
|
|
103
|
+
const { isActive, toggle, enter } = selection;
|
|
104
|
+
const isSelected = selection.isSelected(value);
|
|
105
|
+
const isShown = resolveIndicatorShown({ indicator, isActive, isIndicatorAlwaysShown });
|
|
106
|
+
|
|
107
|
+
const press = resolveItemPress({ hasOnPress: onPress !== undefined, isActive, isDisabled });
|
|
108
|
+
const pressKind = press.kind;
|
|
109
|
+
const handlePress = useCallback(() => {
|
|
110
|
+
if (pressKind === "toggle") toggle(value);
|
|
111
|
+
else if (pressKind === "press") onPress?.();
|
|
112
|
+
}, [onPress, pressKind, toggle, value]);
|
|
113
|
+
|
|
114
|
+
const canEnter = !isActive && !isDisabled;
|
|
115
|
+
const handleLongPress = useCallback(() => enter(value), [enter, value]);
|
|
116
|
+
|
|
117
|
+
const accessibility = resolveItemAccessibility({ isActive, isDisabled, isSelected });
|
|
118
|
+
const handleAccessibilityAction = useCallback(
|
|
119
|
+
(event: AccessibilityActionEvent) => {
|
|
120
|
+
if (canEnter && event.nativeEvent.actionName === "longpress") enter(value);
|
|
121
|
+
onAccessibilityAction?.(event);
|
|
122
|
+
},
|
|
123
|
+
[canEnter, enter, onAccessibilityAction, value]
|
|
124
|
+
);
|
|
125
|
+
const accessibilityProps =
|
|
126
|
+
accessibility.kind === "checkbox"
|
|
127
|
+
? {
|
|
128
|
+
accessibilityHint: accessibility.accessibilityHint,
|
|
129
|
+
accessibilityRole: accessibility.accessibilityRole,
|
|
130
|
+
accessibilityState: accessibility.accessibilityState,
|
|
131
|
+
}
|
|
132
|
+
: {
|
|
133
|
+
accessibilityActions: accessibility.accessibilityActions,
|
|
134
|
+
accessibilityRole: onPress ? ("button" as const) : undefined,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const context = useMemo<SelectionModeItemContextValue>(
|
|
138
|
+
() => ({ indicator, isDisabled, isSelected, value }),
|
|
139
|
+
[indicator, isDisabled, isSelected, value]
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const slots = selectionModeVariants({ indicator, isSelected: isSelected && isShown });
|
|
143
|
+
const { markStyle, spacerStyle } = useLeadingStyles(isShown);
|
|
144
|
+
|
|
145
|
+
// A leading item is a row: a spacer that opens to push the content over, the
|
|
146
|
+
// content, then the mark drawn on top of the gap the spacer made. The mark
|
|
147
|
+
// comes last so a child with a fill of its own never paints over it.
|
|
148
|
+
const content =
|
|
149
|
+
indicator === "leading" ? (
|
|
150
|
+
<>
|
|
151
|
+
<Animated.View style={spacerStyle} />
|
|
152
|
+
<View className="flex-1">{children}</View>
|
|
153
|
+
<Animated.View className={slots.indicatorSlot()} pointerEvents="none" style={markStyle}>
|
|
154
|
+
<SelectionModeIndicator />
|
|
155
|
+
</Animated.View>
|
|
156
|
+
</>
|
|
157
|
+
) : (
|
|
158
|
+
<>
|
|
159
|
+
{children}
|
|
160
|
+
{indicator === "ring" && isShown && isSelected ? (
|
|
161
|
+
<SelectionModeRing className={ringClassName} isSelected={isSelected} />
|
|
162
|
+
) : null}
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<SelectionModeItemProvider value={context}>
|
|
168
|
+
<Pressable
|
|
169
|
+
className={slots.item({ className })}
|
|
170
|
+
feedback={indicator === "leading" ? "fade" : "scale"}
|
|
171
|
+
onAccessibilityAction={handleAccessibilityAction}
|
|
172
|
+
onLongPress={canEnter ? handleLongPress : undefined}
|
|
173
|
+
onPress={pressKind === "none" ? undefined : handlePress}
|
|
174
|
+
{...accessibilityProps}
|
|
175
|
+
{...props}
|
|
176
|
+
>
|
|
177
|
+
{content}
|
|
178
|
+
</Pressable>
|
|
179
|
+
</SelectionModeItemProvider>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
SelectionModeItem.displayName = "DelacourUI.SelectionMode.Item";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { SelectionModeState } from "./selection-mode.types";
|
|
3
|
+
import type { SelectionIndicator } from "./selection-mode.variants";
|
|
4
|
+
|
|
5
|
+
export type SelectionModeContextValue = SelectionModeState;
|
|
6
|
+
|
|
7
|
+
const SelectionModeContext = createContext<SelectionModeContextValue | null>(null);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Supplies a selection to every part beneath it.
|
|
11
|
+
*
|
|
12
|
+
* In a leaf of its own, importing nothing but types, so a part can read it
|
|
13
|
+
* without importing `./selection-mode` and closing a cycle (package AGENTS.md
|
|
14
|
+
* rule 3).
|
|
15
|
+
*/
|
|
16
|
+
export function SelectionModeProvider({
|
|
17
|
+
value,
|
|
18
|
+
children,
|
|
19
|
+
}: {
|
|
20
|
+
value: SelectionModeContextValue;
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): ReactElement {
|
|
23
|
+
return <SelectionModeContext value={value}>{children}</SelectionModeContext>;
|
|
24
|
+
}
|
|
25
|
+
SelectionModeProvider.displayName = "DelacourUI.SelectionMode.Provider";
|
|
26
|
+
|
|
27
|
+
/** The enclosing selection, or null outside a `<SelectionMode>`. */
|
|
28
|
+
export function useSelectionModeContext(): SelectionModeContextValue | null {
|
|
29
|
+
return use(SelectionModeContext);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads the enclosing selection: whether the mode is on, what is picked, and
|
|
34
|
+
* the actions that move it.
|
|
35
|
+
*
|
|
36
|
+
* For a custom child — an item drawn with `indicator="none"` that paints its own
|
|
37
|
+
* picked state, a toolbar of its own. Throws outside a `<SelectionMode>` — use
|
|
38
|
+
* {@link useSelectionModeContext} where the selection is optional.
|
|
39
|
+
*/
|
|
40
|
+
export function useSelectionMode(): SelectionModeContextValue {
|
|
41
|
+
const context = useSelectionModeContext();
|
|
42
|
+
if (!context) {
|
|
43
|
+
throw new Error("useSelectionMode must be called inside a <SelectionMode>.");
|
|
44
|
+
}
|
|
45
|
+
return context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The enclosing selection, for a compound part that cannot work without it.
|
|
50
|
+
*
|
|
51
|
+
* Internal: deliberately not re-exported from `index.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export function useSelectionModePart(component: string): SelectionModeContextValue {
|
|
54
|
+
const context = useSelectionModeContext();
|
|
55
|
+
if (!context) {
|
|
56
|
+
throw new Error(`${component} must be rendered inside a <SelectionMode>.`);
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type SelectionModeItemContextValue = {
|
|
62
|
+
/** The item's id. */
|
|
63
|
+
value: string;
|
|
64
|
+
/** Whether it is picked. */
|
|
65
|
+
isSelected: boolean;
|
|
66
|
+
/** Whether it can be picked at all. */
|
|
67
|
+
isDisabled: boolean;
|
|
68
|
+
/** How it shows it is picked. */
|
|
69
|
+
indicator: SelectionIndicator;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const SelectionModeItemContext = createContext<SelectionModeItemContextValue | null>(null);
|
|
73
|
+
|
|
74
|
+
/** Supplies one item's id and state, so a `SelectionMode.Indicator` inside it needs no `value`. */
|
|
75
|
+
export function SelectionModeItemProvider({
|
|
76
|
+
value,
|
|
77
|
+
children,
|
|
78
|
+
}: {
|
|
79
|
+
value: SelectionModeItemContextValue;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}): ReactElement {
|
|
82
|
+
return <SelectionModeItemContext value={value}>{children}</SelectionModeItemContext>;
|
|
83
|
+
}
|
|
84
|
+
SelectionModeItemProvider.displayName = "DelacourUI.SelectionMode.Item.Provider";
|
|
85
|
+
|
|
86
|
+
/** The enclosing item, or null outside a `<SelectionMode.Item>`. */
|
|
87
|
+
export function useSelectionModeItemContext(): SelectionModeItemContextValue | null {
|
|
88
|
+
return use(SelectionModeItemContext);
|
|
89
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useMemo } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
4
|
+
import { type HapticFeedback, playHaptic } from "../pressable/pressable";
|
|
5
|
+
import { type SelectionModeContextValue, SelectionModeProvider } from "./selection-mode.context";
|
|
6
|
+
import {
|
|
7
|
+
resolveIsAllSelected,
|
|
8
|
+
resolveIsSameSelection,
|
|
9
|
+
resolveSelectAll,
|
|
10
|
+
resolveToggle,
|
|
11
|
+
selectionModeVariants,
|
|
12
|
+
} from "./selection-mode.variants";
|
|
13
|
+
import { SelectionModeAction } from "./selection-mode-action";
|
|
14
|
+
import { SelectionModeBar } from "./selection-mode-bar";
|
|
15
|
+
import { SelectionModeGroup } from "./selection-mode-group";
|
|
16
|
+
import { SelectionModeHeader } from "./selection-mode-header";
|
|
17
|
+
import { SelectionModeIndicator } from "./selection-mode-indicator";
|
|
18
|
+
import { SelectionModeItem } from "./selection-mode-item";
|
|
19
|
+
|
|
20
|
+
export type SelectionModeProps = ViewProps & {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Every pickable id, in list order — what select-all picks and "n of m" counts. */
|
|
24
|
+
values?: readonly string[];
|
|
25
|
+
/** Whether the mode is on. Controlled. */
|
|
26
|
+
isActive?: boolean;
|
|
27
|
+
/** Whether the mode starts on, while uncontrolled. */
|
|
28
|
+
defaultActive?: boolean;
|
|
29
|
+
onActiveChange?: (isActive: boolean) => void;
|
|
30
|
+
/** The picked ids, in the order they were picked. Controlled. */
|
|
31
|
+
selected?: readonly string[];
|
|
32
|
+
/** The ids picked to begin with, while uncontrolled. */
|
|
33
|
+
defaultSelected?: readonly string[];
|
|
34
|
+
/** Called with the whole new list. Never called for a change that changes nothing. */
|
|
35
|
+
onSelectedChange?: (selected: string[]) => void;
|
|
36
|
+
/** The most ids that can be picked. Select-all stops here too. */
|
|
37
|
+
max?: number;
|
|
38
|
+
/** Played on entering the mode and on every toggle. Off by default. */
|
|
39
|
+
haptic?: false | HapticFeedback;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function SelectionModeRoot({
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
values,
|
|
46
|
+
isActive,
|
|
47
|
+
defaultActive = false,
|
|
48
|
+
onActiveChange,
|
|
49
|
+
selected,
|
|
50
|
+
defaultSelected = EMPTY,
|
|
51
|
+
onSelectedChange,
|
|
52
|
+
max,
|
|
53
|
+
haptic = false,
|
|
54
|
+
...props
|
|
55
|
+
}: SelectionModeProps): ReactElement {
|
|
56
|
+
const [active, setActive] = useControllableState<boolean>({
|
|
57
|
+
defaultValue: defaultActive,
|
|
58
|
+
onChange: onActiveChange,
|
|
59
|
+
value: isActive,
|
|
60
|
+
});
|
|
61
|
+
const [picked, setPicked] = useControllableState<readonly string[]>({
|
|
62
|
+
defaultValue: defaultSelected,
|
|
63
|
+
onChange: onSelectedChange as ((next: readonly string[]) => void) | undefined,
|
|
64
|
+
value: selected,
|
|
65
|
+
});
|
|
66
|
+
const isSelectedControlled = selected !== undefined;
|
|
67
|
+
|
|
68
|
+
const commit = useCallback(
|
|
69
|
+
(next: string[]): boolean => {
|
|
70
|
+
if (resolveIsSameSelection(next, picked)) return false;
|
|
71
|
+
setPicked(next);
|
|
72
|
+
return true;
|
|
73
|
+
},
|
|
74
|
+
[picked, setPicked]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const toggle = useCallback(
|
|
78
|
+
(value: string) => {
|
|
79
|
+
if (commit(resolveToggle({ max, selected: picked, value })) && haptic) playHaptic(haptic);
|
|
80
|
+
},
|
|
81
|
+
[commit, haptic, max, picked]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const enter = useCallback(
|
|
85
|
+
(value?: string) => {
|
|
86
|
+
if (!active) setActive(true);
|
|
87
|
+
const didPick =
|
|
88
|
+
value !== undefined && !picked.includes(value) && commit(resolveToggle({ max, selected: picked, value }));
|
|
89
|
+
if (haptic && (!active || didPick)) playHaptic(haptic);
|
|
90
|
+
},
|
|
91
|
+
[active, commit, haptic, max, picked, setActive]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const exit = useCallback(() => {
|
|
95
|
+
if (active) setActive(false);
|
|
96
|
+
if (!isSelectedControlled) commit([]);
|
|
97
|
+
}, [active, commit, isSelectedControlled, setActive]);
|
|
98
|
+
|
|
99
|
+
const selectAll = useCallback(() => {
|
|
100
|
+
commit(resolveSelectAll({ max, selected: picked, values: values ?? EMPTY }));
|
|
101
|
+
}, [commit, max, picked, values]);
|
|
102
|
+
|
|
103
|
+
const clear = useCallback(() => {
|
|
104
|
+
commit([]);
|
|
105
|
+
}, [commit]);
|
|
106
|
+
|
|
107
|
+
const isSelected = useCallback((value: string) => picked.includes(value), [picked]);
|
|
108
|
+
|
|
109
|
+
const context = useMemo<SelectionModeContextValue>(
|
|
110
|
+
() => ({
|
|
111
|
+
clear,
|
|
112
|
+
count: picked.length,
|
|
113
|
+
enter,
|
|
114
|
+
exit,
|
|
115
|
+
haptic,
|
|
116
|
+
isActive: active,
|
|
117
|
+
isAllSelected: resolveIsAllSelected({ max, selected: picked, values: values ?? EMPTY }),
|
|
118
|
+
isSelected,
|
|
119
|
+
max,
|
|
120
|
+
selectAll,
|
|
121
|
+
selected: [...picked],
|
|
122
|
+
toggle,
|
|
123
|
+
total: values?.length,
|
|
124
|
+
}),
|
|
125
|
+
[active, clear, enter, exit, haptic, isSelected, max, picked, selectAll, toggle, values]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<SelectionModeProvider value={context}>
|
|
130
|
+
<View className={selectionModeVariants().root({ className })} {...props}>
|
|
131
|
+
{children}
|
|
132
|
+
</View>
|
|
133
|
+
</SelectionModeProvider>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A stable empty list, so an uncontrolled selection does not seed its state
|
|
139
|
+
* from a fresh array on every render.
|
|
140
|
+
*/
|
|
141
|
+
const EMPTY: readonly string[] = [];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Pick several things at once, then act on them from a bar.
|
|
145
|
+
*
|
|
146
|
+
* A mode: off, the list is for reading and each item keeps its own press; a
|
|
147
|
+
* long press on an item turns the mode on with that item picked. On, a press
|
|
148
|
+
* toggles, `SelectionMode.Header` says so and offers the way out, and
|
|
149
|
+
* `SelectionMode.Bar` rises over the bottom edge with the actions.
|
|
150
|
+
*
|
|
151
|
+
* Selection is a set of ids — a `string[]` in the order things were picked,
|
|
152
|
+
* never indices — controlled with `selected` + `onSelectedChange` or held from
|
|
153
|
+
* `defaultSelected`. The mode is `isActive` + `onActiveChange`, or
|
|
154
|
+
* `defaultActive`. Exiting clears an uncontrolled selection; a controlled one is
|
|
155
|
+
* left to the caller. `values` lists every pickable id, which is what
|
|
156
|
+
* select-all and the "n of m" count need. `max` caps both toggling and
|
|
157
|
+
* select-all.
|
|
158
|
+
*
|
|
159
|
+
* The root fills the height it is offered (`flex-1`), so give it one. The bar is
|
|
160
|
+
* absolute, over the list, so pad the bottom of the list (`pb-24`) for the last
|
|
161
|
+
* row to scroll clear of it.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* <SelectionMode onSelectedChange={setSelected} selected={selected} values={ids}>
|
|
165
|
+
* <SelectionMode.Header title="Choose" />
|
|
166
|
+
* <FlatList
|
|
167
|
+
* contentContainerClassName="pb-24"
|
|
168
|
+
* data={messages}
|
|
169
|
+
* renderItem={({ item }) => (
|
|
170
|
+
* <SelectionMode.Item onPress={() => open(item)} value={item.id}>
|
|
171
|
+
* <Item>…</Item>
|
|
172
|
+
* </SelectionMode.Item>
|
|
173
|
+
* )}
|
|
174
|
+
* />
|
|
175
|
+
* <SelectionMode.Bar>
|
|
176
|
+
* <SelectionMode.Action icon={IconTrashCan} isDestructive isExitOnPress onPress={remove}>
|
|
177
|
+
* Delete
|
|
178
|
+
* </SelectionMode.Action>
|
|
179
|
+
* </SelectionMode.Bar>
|
|
180
|
+
* </SelectionMode>
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* <SelectionMode defaultActive values={swatches}>
|
|
184
|
+
* <SelectionMode.Group columns={5}>
|
|
185
|
+
* {swatches.map((swatch) => (
|
|
186
|
+
* <SelectionMode.Item indicator="ring" key={swatch} ringClassName="rounded-full" value={swatch}>
|
|
187
|
+
* <Swatch color={swatch} />
|
|
188
|
+
* </SelectionMode.Item>
|
|
189
|
+
* ))}
|
|
190
|
+
* </SelectionMode.Group>
|
|
191
|
+
* </SelectionMode>
|
|
192
|
+
*/
|
|
193
|
+
export const SelectionMode = Object.assign(SelectionModeRoot, {
|
|
194
|
+
/** One pickable thing. Wraps whatever it holds. */
|
|
195
|
+
Item: SelectionModeItem,
|
|
196
|
+
/** The round picked mark, for an item that places its own. */
|
|
197
|
+
Indicator: SelectionModeIndicator,
|
|
198
|
+
/** Shown while the mode is on: close, the count and select-all. */
|
|
199
|
+
Header: SelectionModeHeader,
|
|
200
|
+
/** The actions, over the bottom edge, while something is picked. */
|
|
201
|
+
Bar: SelectionModeBar,
|
|
202
|
+
/** One action in the bar: an icon over a label. */
|
|
203
|
+
Action: SelectionModeAction,
|
|
204
|
+
/** Lays items out as a stacked card, a grid or a horizontal strip. */
|
|
205
|
+
Group: SelectionModeGroup,
|
|
206
|
+
displayName: "DelacourUI.SelectionMode",
|
|
207
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HapticFeedback } from "../pressable/pressable";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Everything a selection exposes — what the root publishes and what
|
|
5
|
+
* `useSelectionMode()` returns.
|
|
6
|
+
*
|
|
7
|
+
* Shared by the root, which builds it, and the context leaf, which carries it.
|
|
8
|
+
*/
|
|
9
|
+
export type SelectionModeState = {
|
|
10
|
+
/** Whether the mode is on: a press toggles, the header and bar show. */
|
|
11
|
+
isActive: boolean;
|
|
12
|
+
/** The picked ids, in the order they were picked. */
|
|
13
|
+
selected: string[];
|
|
14
|
+
/** Whether `value` is picked. */
|
|
15
|
+
isSelected: (value: string) => boolean;
|
|
16
|
+
/** Picks or unpicks `value`, respecting `max`. */
|
|
17
|
+
toggle: (value: string) => void;
|
|
18
|
+
/** Picks every value up to `max`, keeping existing picks first. */
|
|
19
|
+
selectAll: () => void;
|
|
20
|
+
/** Unpicks everything, and stays in the mode. */
|
|
21
|
+
clear: () => void;
|
|
22
|
+
/** Turns the mode on, picking `value` when one is given. */
|
|
23
|
+
enter: (value?: string) => void;
|
|
24
|
+
/** Turns the mode off. Clears an uncontrolled selection; a controlled one is left to the caller. */
|
|
25
|
+
exit: () => void;
|
|
26
|
+
/** How many ids are picked. */
|
|
27
|
+
count: number;
|
|
28
|
+
/** How many ids are pickable, or `undefined` when the root was given no `values`. */
|
|
29
|
+
total: number | undefined;
|
|
30
|
+
/** The cap on picks, if any. */
|
|
31
|
+
max: number | undefined;
|
|
32
|
+
/** Whether select-all has nothing left to add — the control reads "Deselect all". */
|
|
33
|
+
isAllSelected: boolean;
|
|
34
|
+
/** Played on enter and on every toggle. */
|
|
35
|
+
haptic: false | HapticFeedback;
|
|
36
|
+
};
|