@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,76 @@
|
|
|
1
|
+
import { type RefObject, useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { type HostInstance, useWindowDimensions } from "react-native";
|
|
3
|
+
import type { AnchorRect } from "./popover.position";
|
|
4
|
+
|
|
5
|
+
/** A native view that can report its frame — what an anchor ref points at. */
|
|
6
|
+
export type MeasurableNode = HostInstance;
|
|
7
|
+
|
|
8
|
+
export type UseAnchorMeasureOptions = {
|
|
9
|
+
/** Measure while true: on the transition to true, and again on every window-size change. */
|
|
10
|
+
isEnabled: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AnchorMeasure = {
|
|
14
|
+
/** A callback ref for the view to measure. */
|
|
15
|
+
ref: (node: MeasurableNode | null) => void;
|
|
16
|
+
/** The node the ref last received. */
|
|
17
|
+
node: RefObject<MeasurableNode | null>;
|
|
18
|
+
/** The view's frame in window coordinates; `null` from each enable until the measure lands. */
|
|
19
|
+
rect: AnchorRect | null;
|
|
20
|
+
/** Measure again now. */
|
|
21
|
+
measure: () => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Measures a view in **window** coordinates, which is what makes an anchored
|
|
26
|
+
* panel land on its anchor wherever the anchor is — inside a `ScrollView`, a
|
|
27
|
+
* bottom sheet or under a navigator header — because the panel is teleported to
|
|
28
|
+
* a host that fills the window.
|
|
29
|
+
*
|
|
30
|
+
* `rect` is cleared on each enable, so a panel re-opened after the anchor moved
|
|
31
|
+
* never paints one frame at the old position, and kept while disabled, so a
|
|
32
|
+
* panel animating closed still has somewhere to be. A window-size change while
|
|
33
|
+
* enabled — a rotation — measures again without clearing.
|
|
34
|
+
*
|
|
35
|
+
* **A leaf.** Tooltip imports it; it imports nothing from `./popover` or
|
|
36
|
+
* `./index`.
|
|
37
|
+
*/
|
|
38
|
+
export function useAnchorMeasure({ isEnabled }: UseAnchorMeasureOptions): AnchorMeasure {
|
|
39
|
+
const node = useRef<MeasurableNode | null>(null);
|
|
40
|
+
const [rect, setRect] = useState<AnchorRect | null>(null);
|
|
41
|
+
const { width, height } = useWindowDimensions();
|
|
42
|
+
|
|
43
|
+
const ref = useCallback((next: MeasurableNode | null) => {
|
|
44
|
+
node.current = next;
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const measure = useCallback(() => {
|
|
48
|
+
node.current?.measureInWindow((x, y, measuredWidth, measuredHeight) => {
|
|
49
|
+
setRect((previous) =>
|
|
50
|
+
previous !== null &&
|
|
51
|
+
previous.x === x &&
|
|
52
|
+
previous.y === y &&
|
|
53
|
+
previous.width === measuredWidth &&
|
|
54
|
+
previous.height === measuredHeight
|
|
55
|
+
? previous
|
|
56
|
+
: { x, y, width: measuredWidth, height: measuredHeight }
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!isEnabled) return;
|
|
63
|
+
setRect(null);
|
|
64
|
+
measure();
|
|
65
|
+
}, [isEnabled, measure]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!isEnabled || width === 0 || height === 0) return;
|
|
69
|
+
// A frame later: the window reports its new size before the anchor has
|
|
70
|
+
// been laid out at it, and a measure now would read the old frame.
|
|
71
|
+
const frame = requestAnimationFrame(measure);
|
|
72
|
+
return () => cancelAnimationFrame(frame);
|
|
73
|
+
}, [isEnabled, measure, width, height]);
|
|
74
|
+
|
|
75
|
+
return { ref, node, rect, measure };
|
|
76
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { I18nManager, type LayoutChangeEvent, useWindowDimensions, type ViewStyle } from "react-native";
|
|
3
|
+
import { KeyboardEvents } from "react-native-keyboard-controller";
|
|
4
|
+
import { type AnimatedStyle, useAnimatedStyle } from "react-native-reanimated";
|
|
5
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
|
+
import { type OverlayPresence, useOverlayPresence } from "../overlay";
|
|
7
|
+
import {
|
|
8
|
+
type AnchoredPosition,
|
|
9
|
+
type AnchoredSize,
|
|
10
|
+
type AnchorRect,
|
|
11
|
+
type PopoverAlign,
|
|
12
|
+
type PopoverPlacement,
|
|
13
|
+
type PopoverWidth,
|
|
14
|
+
type ResolvedPopoverWidth,
|
|
15
|
+
resolveAnchoredPosition,
|
|
16
|
+
resolveEnterTranslate,
|
|
17
|
+
resolvePopoverWidth,
|
|
18
|
+
resolveTransformOrigin,
|
|
19
|
+
} from "./popover.position";
|
|
20
|
+
|
|
21
|
+
export type UseAnchoredContentOptions = {
|
|
22
|
+
/** The owner's open state. The entrance waits for the first measure; the exit does not. */
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
/** The anchor's frame in window coordinates, `null` while it is being measured. */
|
|
25
|
+
anchor: AnchorRect | null;
|
|
26
|
+
placement: PopoverPlacement;
|
|
27
|
+
align: PopoverAlign;
|
|
28
|
+
offset: number;
|
|
29
|
+
alignOffset: number;
|
|
30
|
+
collisionPadding: number;
|
|
31
|
+
arrowInset: number;
|
|
32
|
+
width: PopoverWidth;
|
|
33
|
+
minWidth?: number;
|
|
34
|
+
maxHeight?: number;
|
|
35
|
+
/** How far the panel travels on its way in. */
|
|
36
|
+
enterDistance: number;
|
|
37
|
+
/** The scale it grows from. */
|
|
38
|
+
enterScale: number;
|
|
39
|
+
onEntered?: () => void;
|
|
40
|
+
onExited?: () => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AnchoredContent = {
|
|
44
|
+
presence: OverlayPresence;
|
|
45
|
+
/** Render the panel while true: from the open until the exit animation ends. */
|
|
46
|
+
isMounted: boolean;
|
|
47
|
+
/** The panel's resolved frame, `null` until both the anchor and the panel are measured. */
|
|
48
|
+
position: AnchoredPosition | null;
|
|
49
|
+
/** The panel's measured size, `null` until its first layout. */
|
|
50
|
+
size: AnchoredSize | null;
|
|
51
|
+
/** Put on the positioner: it is how the panel is measured. */
|
|
52
|
+
onLayout: (event: LayoutChangeEvent) => void;
|
|
53
|
+
/**
|
|
54
|
+
* The positioner's cap: the safe span. It sits on a wrapper rather than the
|
|
55
|
+
* panel because an inline `maxWidth` beats any class, and a caller's
|
|
56
|
+
* `max-w-64` on the panel has to keep working inside it.
|
|
57
|
+
*/
|
|
58
|
+
positionerStyle: { position: "absolute"; left: 0; top: 0; maxWidth: number };
|
|
59
|
+
/** The panel's fixed width or `minWidth`, and the clamped `maxHeight`. */
|
|
60
|
+
frameStyle: Omit<ResolvedPopoverWidth, "maxWidth"> & { maxHeight?: number };
|
|
61
|
+
/** Position, opacity and the entrance transform — put on the positioner after `positionerStyle`. */
|
|
62
|
+
animatedStyle: AnimatedStyle<ViewStyle>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The keyboard's height while it is up, so a panel treats it as the bottom of
|
|
67
|
+
* the screen — a field inside a popover flips the panel above its trigger
|
|
68
|
+
* rather than typing under the keyboard. JS state, not a shared value: the
|
|
69
|
+
* position is resolved on the JS thread, once per keyboard transition.
|
|
70
|
+
*/
|
|
71
|
+
function useKeyboardHeight(): number {
|
|
72
|
+
const [height, setHeight] = useState(0);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const subscriptions = [
|
|
75
|
+
KeyboardEvents.addListener("keyboardWillShow", (event) => setHeight(event.height)),
|
|
76
|
+
KeyboardEvents.addListener("keyboardDidShow", (event) => setHeight(event.height)),
|
|
77
|
+
KeyboardEvents.addListener("keyboardWillHide", () => setHeight(0)),
|
|
78
|
+
KeyboardEvents.addListener("keyboardDidHide", () => setHeight(0)),
|
|
79
|
+
];
|
|
80
|
+
return () => {
|
|
81
|
+
for (const subscription of subscriptions) subscription.remove();
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
return height;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Everything an anchored panel needs between its trigger and its first frame.
|
|
89
|
+
*
|
|
90
|
+
* 1. **Measure off-screen.** The panel mounts at the window's origin, inside
|
|
91
|
+
* a positioner that carries the measure, the cap and the motion, with
|
|
92
|
+
* opacity 0; its first layout reports its size.
|
|
93
|
+
* 2. **Resolve.** With the anchor's rect and the panel's size,
|
|
94
|
+
* `resolveAnchoredPosition` picks the side, the shift, the clamped
|
|
95
|
+
* `maxHeight` and the arrow. The keyboard counts as the bottom inset.
|
|
96
|
+
* 3. **Enter.** Only then does presence start: `progress` fades the panel in,
|
|
97
|
+
* slides it `enterDistance` from the anchor's side and scales it from
|
|
98
|
+
* `enterScale` about the arrow. Under reduce motion only the fade runs.
|
|
99
|
+
*
|
|
100
|
+
* The position is applied as a translate, never `left` / `top`: a translate is
|
|
101
|
+
* not layout, so moving the panel never re-wraps its content and never fires
|
|
102
|
+
* another `onLayout`. A window-size change re-resolves on the next render and
|
|
103
|
+
* moves the panel there directly — re-placed, not re-animated.
|
|
104
|
+
*
|
|
105
|
+
* **A leaf.** Tooltip imports it; it imports nothing from `./popover` or
|
|
106
|
+
* `./index`.
|
|
107
|
+
*/
|
|
108
|
+
export function useAnchoredContent({
|
|
109
|
+
isOpen,
|
|
110
|
+
anchor,
|
|
111
|
+
placement,
|
|
112
|
+
align,
|
|
113
|
+
offset,
|
|
114
|
+
alignOffset,
|
|
115
|
+
collisionPadding,
|
|
116
|
+
arrowInset,
|
|
117
|
+
width,
|
|
118
|
+
minWidth,
|
|
119
|
+
maxHeight,
|
|
120
|
+
enterDistance,
|
|
121
|
+
enterScale,
|
|
122
|
+
onEntered,
|
|
123
|
+
onExited,
|
|
124
|
+
}: UseAnchoredContentOptions): AnchoredContent {
|
|
125
|
+
const window = useWindowDimensions();
|
|
126
|
+
const safeArea = useSafeAreaInsets();
|
|
127
|
+
const keyboardHeight = useKeyboardHeight();
|
|
128
|
+
const [size, setSize] = useState<AnchoredSize | null>(null);
|
|
129
|
+
|
|
130
|
+
const bounds = {
|
|
131
|
+
width: window.width,
|
|
132
|
+
height: window.height,
|
|
133
|
+
insets: {
|
|
134
|
+
top: safeArea.top,
|
|
135
|
+
right: safeArea.right,
|
|
136
|
+
bottom: Math.max(safeArea.bottom, keyboardHeight),
|
|
137
|
+
left: safeArea.left,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const position =
|
|
142
|
+
anchor === null || size === null
|
|
143
|
+
? null
|
|
144
|
+
: resolveAnchoredPosition({
|
|
145
|
+
anchor,
|
|
146
|
+
content: size,
|
|
147
|
+
bounds,
|
|
148
|
+
placement,
|
|
149
|
+
align,
|
|
150
|
+
offset,
|
|
151
|
+
alignOffset,
|
|
152
|
+
collisionPadding,
|
|
153
|
+
arrowInset,
|
|
154
|
+
maxHeight,
|
|
155
|
+
isRTL: I18nManager.isRTL,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const presence = useOverlayPresence({ isOpen: isOpen && position !== null, onEntered, onExited });
|
|
159
|
+
const isMounted = isOpen || presence.isPresent;
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!isMounted) setSize(null);
|
|
163
|
+
}, [isMounted]);
|
|
164
|
+
|
|
165
|
+
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
166
|
+
const { width: measuredWidth, height: measuredHeight } = event.nativeEvent.layout;
|
|
167
|
+
setSize((previous) =>
|
|
168
|
+
previous !== null && previous.width === measuredWidth && previous.height === measuredHeight
|
|
169
|
+
? previous
|
|
170
|
+
: { width: measuredWidth, height: measuredHeight }
|
|
171
|
+
);
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
const widths = resolvePopoverWidth({ width, anchorWidth: anchor?.width ?? 0, bounds, collisionPadding, minWidth });
|
|
175
|
+
const { maxWidth, ...panelWidths } = widths;
|
|
176
|
+
const positionerStyle = { position: "absolute", left: 0, top: 0, maxWidth } as const;
|
|
177
|
+
const frameStyle = position === null ? panelWidths : { ...panelWidths, maxHeight: position.maxHeight };
|
|
178
|
+
|
|
179
|
+
// Destructured to primitives on purpose, as `Pressable` does: the worklet
|
|
180
|
+
// closes over what it reads, and these are what change between renders.
|
|
181
|
+
const progress = presence.progress;
|
|
182
|
+
const isReduced = presence.isReduced;
|
|
183
|
+
const isPlaced = position !== null;
|
|
184
|
+
const x = position?.x ?? 0;
|
|
185
|
+
const y = position?.y ?? 0;
|
|
186
|
+
const enter = resolveEnterTranslate(position?.placement ?? placement, enterDistance);
|
|
187
|
+
const enterX = enter.x;
|
|
188
|
+
const enterY = enter.y;
|
|
189
|
+
const origin = resolveTransformOrigin(
|
|
190
|
+
position?.placement ?? placement,
|
|
191
|
+
position?.arrowOffset ?? 0,
|
|
192
|
+
size ?? { width: 0, height: 0 }
|
|
193
|
+
);
|
|
194
|
+
const originX = origin.x;
|
|
195
|
+
const originY = origin.y;
|
|
196
|
+
|
|
197
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
198
|
+
if (!isPlaced) {
|
|
199
|
+
return {
|
|
200
|
+
opacity: 0,
|
|
201
|
+
transformOrigin: [0, 0, 0],
|
|
202
|
+
transform: [{ translateX: 0 }, { translateY: 0 }, { scale: 1 }],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const shown = progress.value;
|
|
206
|
+
const away = isReduced ? 0 : 1 - shown;
|
|
207
|
+
return {
|
|
208
|
+
opacity: shown,
|
|
209
|
+
transformOrigin: [originX, originY, 0],
|
|
210
|
+
transform: [
|
|
211
|
+
{ translateX: x + enterX * away },
|
|
212
|
+
{ translateY: y + enterY * away },
|
|
213
|
+
{ scale: isReduced ? 1 : enterScale + (1 - enterScale) * shown },
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return { presence, isMounted, position, size, onLayout, positionerStyle, frameStyle, animatedStyle };
|
|
219
|
+
}
|
|
@@ -45,7 +45,17 @@ and only `onPress` and `onLongPress` cross back to JS.
|
|
|
45
45
|
the control as disabled. Use `busy` for a temporary state the component clears
|
|
46
46
|
itself, so assistive tech reports a control that is momentarily unavailable
|
|
47
47
|
rather than one that is inert. Neither applies any opacity — that is the
|
|
48
|
-
caller's variant's job.
|
|
48
|
+
caller's variant's job, and the point below is what lets it do that job.
|
|
49
|
+
- **A className's opacity is multiplied into the press, never overwritten.**
|
|
50
|
+
The press feedback is an animated style, and an animated style owns every key
|
|
51
|
+
it writes on the view — so its resting `opacity: 1` silently replaced a
|
|
52
|
+
variant's `opacity-50`, and a disabled `Button`, `Badge`, `Checkbox`,
|
|
53
|
+
`ListGroup.Item` or `Input.Group` never faded while blocking taps correctly.
|
|
54
|
+
The root now resolves its own `className` with Uniwind's
|
|
55
|
+
`useResolveClassNames` and animates `rest * press` instead, with
|
|
56
|
+
`resolveRestOpacity` guarding the value. A press still fades relative to the
|
|
57
|
+
rest — a disabled control cannot be pressed, so the two never compound. No
|
|
58
|
+
unit test could have seen the bug; it was found on a simulator.
|
|
49
59
|
- **A worklet crosses back to JS with `scheduleOnRN`**, imported from
|
|
50
60
|
`react-native-worklets` — never Reanimated's `runOnJS`, which since Reanimated 4
|
|
51
61
|
is a deprecated shim that forwards to exactly that call. `scheduleOnRN(fn, ...args)`
|
|
@@ -14,9 +14,10 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
|
14
14
|
import { Presets } from "react-native-pulsar";
|
|
15
15
|
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from "react-native-reanimated";
|
|
16
16
|
import { scheduleOnRN } from "react-native-worklets";
|
|
17
|
+
import { useResolveClassNames } from "uniwind";
|
|
17
18
|
import { composeRefs } from "../../lib/compose-refs";
|
|
18
19
|
import { mergeProps } from "../../lib/merge-props";
|
|
19
|
-
import { type PressableFeedback, resolvePressedState } from "./pressable.variants";
|
|
20
|
+
import { type PressableFeedback, resolvePressedState, resolveRestOpacity } from "./pressable.variants";
|
|
20
21
|
|
|
21
22
|
const PRESS_SPRING = { damping: 18, mass: 0.4, stiffness: 320 } as const;
|
|
22
23
|
|
|
@@ -154,6 +155,10 @@ export function Pressable({
|
|
|
154
155
|
// the worklet closes over, and a fresh object each render would rebuild the
|
|
155
156
|
// animated style every render.
|
|
156
157
|
const { opacity: targetOpacity, scale: targetScale } = resolvePressedState(feedback, pressedScale, pressedOpacity);
|
|
158
|
+
// The animated style below owns `opacity` on the view, so a className's own
|
|
159
|
+
// opacity — a disabled variant's `opacity-50` — is read here and multiplied
|
|
160
|
+
// in rather than overwritten by the resting 1. See `resolveRestOpacity`.
|
|
161
|
+
const restOpacity = resolveRestOpacity(useResolveClassNames(className ?? "").opacity);
|
|
157
162
|
|
|
158
163
|
const gesture = useMemo(() => {
|
|
159
164
|
const tap = Gesture.Tap()
|
|
@@ -187,7 +192,7 @@ export function Pressable({
|
|
|
187
192
|
}, [haptic, interactive, onLongPress, onPress, pressed]);
|
|
188
193
|
|
|
189
194
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
190
|
-
opacity: 1 - pressed.value * (1 - targetOpacity),
|
|
195
|
+
opacity: restOpacity * (1 - pressed.value * (1 - targetOpacity)),
|
|
191
196
|
transform: [{ scale: 1 - pressed.value * (1 - targetScale) }],
|
|
192
197
|
}));
|
|
193
198
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
PRESSABLE_FEEDBACK_FALLBACK,
|
|
5
5
|
PRESSABLE_FEEDBACKS,
|
|
6
6
|
resolvePressedState,
|
|
7
|
+
resolveRestOpacity,
|
|
7
8
|
} from "./pressable.variants";
|
|
8
9
|
|
|
9
10
|
describe("PRESSABLE_FEEDBACK", () => {
|
|
@@ -97,3 +98,31 @@ describe("resolvePressedState", () => {
|
|
|
97
98
|
expect(resolvePressedState("scale", 0, 0)).toEqual({ opacity: 0, scale: 0 });
|
|
98
99
|
});
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
describe("resolveRestOpacity", () => {
|
|
103
|
+
// The animated style owns `opacity` on the view, so a caller's `opacity-50`
|
|
104
|
+
// would be overwritten by the resting `1` unless the press multiplies it in.
|
|
105
|
+
test("keeps an opacity the className resolved to", () => {
|
|
106
|
+
expect(resolveRestOpacity(0.5)).toBe(0.5);
|
|
107
|
+
expect(resolveRestOpacity(0)).toBe(0);
|
|
108
|
+
expect(resolveRestOpacity(1)).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("falls back to fully opaque when the className sets none", () => {
|
|
112
|
+
expect(resolveRestOpacity(undefined)).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Uniwind types `opacity` as an animatable value, so anything that is not a
|
|
116
|
+
// plain number is treated as unset rather than multiplied in as NaN.
|
|
117
|
+
test("ignores a value that is not a finite number", () => {
|
|
118
|
+
expect(resolveRestOpacity(Number.NaN)).toBe(1);
|
|
119
|
+
expect(resolveRestOpacity(Number.POSITIVE_INFINITY)).toBe(1);
|
|
120
|
+
expect(resolveRestOpacity("0.5")).toBe(1);
|
|
121
|
+
expect(resolveRestOpacity({})).toBe(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("clamps a number into the unit range", () => {
|
|
125
|
+
expect(resolveRestOpacity(1.5)).toBe(1);
|
|
126
|
+
expect(resolveRestOpacity(-0.2)).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -61,3 +61,20 @@ export function resolvePressedState(
|
|
|
61
61
|
scale: pressedScale ?? named.scale,
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The opacity a pressable rests at, read from what its `className` resolved to.
|
|
67
|
+
*
|
|
68
|
+
* The press feedback is an animated style, and an animated style owns every
|
|
69
|
+
* key it writes on the view — so a resting `opacity: 1` overwrote a caller's
|
|
70
|
+
* `opacity-50` and a disabled `Button`, `Badge` or `ListGroup.Item` never
|
|
71
|
+
* faded. The press now multiplies this in rather than replacing it. Uniwind
|
|
72
|
+
* types the value as animatable, so anything that is not a finite number is
|
|
73
|
+
* treated as unset, and a number is clamped into `[0, 1]`.
|
|
74
|
+
*
|
|
75
|
+
* Pure, so the whole matrix is reachable from `bun test`. See AGENTS.md.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveRestOpacity(resolved: unknown): number {
|
|
78
|
+
if (typeof resolved !== "number" || !Number.isFinite(resolved)) return 1;
|
|
79
|
+
return Math.min(1, Math.max(0, resolved));
|
|
80
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Progress
|
|
2
|
+
|
|
3
|
+
How far a task has got, or — indeterminate — that it is under way. Compound root
|
|
4
|
+
plus `Progress.Header`, `Progress.Label`, `Progress.Output`, `Progress.Track` and
|
|
5
|
+
`Progress.Fill`. An output, not a control: nothing here is pressed or dragged.
|
|
6
|
+
|
|
7
|
+
`import { Progress } from "@delacour/react-native-ui/progress";`
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `index.ts` | → `@delacour/react-native-ui/progress` |
|
|
14
|
+
| `progress.tsx` | Root + the `Object.assign` compound surface, and the accessibility surface |
|
|
15
|
+
| `progress-header.tsx` | `Progress.Header`, the label-and-readout row |
|
|
16
|
+
| `progress-label.tsx` | `Progress.Label`, a `Text.Label` naming what is measured |
|
|
17
|
+
| `progress-output.tsx` | `Progress.Output`, the formatted value |
|
|
18
|
+
| `progress-track.tsx` | `Progress.Track` — the clip, and the width measurement |
|
|
19
|
+
| `progress-fill.tsx` | `Progress.Fill` — every animation the component runs |
|
|
20
|
+
| `progress.context.tsx` | `ProgressContext`, `useProgress()`, `useProgressContext()`, `useProgressPart()` |
|
|
21
|
+
| `progress.types.ts` | `ProgressRenderProps`, shared by the context and the readout |
|
|
22
|
+
| `progress.variants.ts` | Pure `tv()` slots + the ratio, geometry, formatting and accessibility resolvers |
|
|
23
|
+
| `progress.variants.test.ts` | |
|
|
24
|
+
|
|
25
|
+
## Design
|
|
26
|
+
|
|
27
|
+
- **Colours**: `default`, `primary`, `success`, `warning`, `destructive`, `info` —
|
|
28
|
+
the slider's set, and a test pins the two lists equal so a bar and a slider in
|
|
29
|
+
one form name a colour with the same word. **Sizes**: `sm`, `md`, `lg`, the
|
|
30
|
+
track's thickness (`h-1`/`h-2`/`h-3`) and the header's type step.
|
|
31
|
+
- **The colour paints the fill, never the track.** An empty bar is the slider's
|
|
32
|
+
groove, `bg-secondary`, at every colour. A test asserts the track's class does
|
|
33
|
+
not move with `color`.
|
|
34
|
+
- **A bare `<Progress value={40} />` draws the bar.** The anatomy is written out
|
|
35
|
+
when a label or readout is wanted, but a compound root with no default children
|
|
36
|
+
renders an empty `View` — the failure the CLI's verify screen already hit once
|
|
37
|
+
with `Slider`, and one no error reports.
|
|
38
|
+
- **No `isDisabled`, no `isInvalid`, no `Field`.** A progress bar reports; it
|
|
39
|
+
does not accept input, so there is nothing to disable and no value to reject.
|
|
40
|
+
`resolveProgressAxes` takes one argument and a test pins that, so a `Field`
|
|
41
|
+
rung cannot be added by accident. A failed task is `color="destructive"`.
|
|
42
|
+
|
|
43
|
+
## Motion
|
|
44
|
+
|
|
45
|
+
- **The fill is a full-length bar slid under a clipping track.** Its position is
|
|
46
|
+
a `translateX` of `(ratio − 1) × trackWidth` — a transform, composited on the UI
|
|
47
|
+
thread with no layout pass. Animating `width` re-lays the bar out on every frame
|
|
48
|
+
of every update. The track's `overflow-hidden rounded-full` is what makes the
|
|
49
|
+
slide read as growth, and it keeps the leading end's round cap because that end
|
|
50
|
+
is the fill's own, not a cut.
|
|
51
|
+
- **One timing per value, on the UI thread.** `useEffect` hands the new ratio to
|
|
52
|
+
`withTiming` once (`PROGRESS_FILL_DURATION_MS`, ease-out); the frames that
|
|
53
|
+
follow never touch React. An upload reporting ten times a second costs ten
|
|
54
|
+
renders.
|
|
55
|
+
- **The first frame is at the value, not animated up from zero.** The shared
|
|
56
|
+
value is seeded with the ratio, so a list of bars scrolling in does not
|
|
57
|
+
replay a fill on every row that mounts.
|
|
58
|
+
- **Hidden until measured.** The track reports its width in `onLayout`; before
|
|
59
|
+
that the fill is `opacity: 0`, because a bar positioned against a width of 0
|
|
60
|
+
flashes at full length for one frame.
|
|
61
|
+
- **Indeterminate is a segment sweeping from wholly off-left to wholly
|
|
62
|
+
off-right.** `indeterminateSegment` puts both ends of the loop outside the
|
|
63
|
+
track, so the frame where `withRepeat` restarts is never on screen. A sweep
|
|
64
|
+
that started at the track's left edge pops into view every pass.
|
|
65
|
+
- **Reduce motion swaps the sweep for a pulse, and does not stop it.** With the
|
|
66
|
+
system setting on, the fill covers the whole track and breathes between
|
|
67
|
+
`PROGRESS_PULSE.from` and `to`. Both animations opt out with
|
|
68
|
+
`ReduceMotion.Never`: under the default policy a timing completes instantly and
|
|
69
|
+
`withRepeat(-1)` spins a zero-length animation forever — a frozen bar, which
|
|
70
|
+
reads as hung. `Spinner` makes the same call for the same reason. **Both layers
|
|
71
|
+
opt out** — the timing *and* the `withRepeat`, which has a policy of its own:
|
|
72
|
+
left at `System`, a reversing repeat never starts and a forward one stops after
|
|
73
|
+
one pass. The pulse sat at full opacity on a simulator until the repeat's own
|
|
74
|
+
argument was set. The pulse
|
|
75
|
+
never reaches zero opacity, because a bar that disappears reads as finished.
|
|
76
|
+
- **The animated style returns the same keys in every mode** — `opacity`,
|
|
77
|
+
`transform`, `width` — so flipping `isIndeterminate` never asks Reanimated to
|
|
78
|
+
change a style's shape mid-animation.
|
|
79
|
+
- **Both effects clean up.** `cancelAnimation` on unmount and on every flip back
|
|
80
|
+
to a value, or the repeat outlives the bar.
|
|
81
|
+
|
|
82
|
+
## Values and formatting
|
|
83
|
+
|
|
84
|
+
- **`progressRatio` clamps, and returns 0 for a degenerate range or a
|
|
85
|
+
non-finite value.** An upload dividing by a total it has not learned yet
|
|
86
|
+
produces `NaN`, and `NaN` written into a shared value freezes the fill for good.
|
|
87
|
+
- **The default readout is the share of the range** — `72%`, or `75%` for 18 of
|
|
88
|
+
24. With `formatOptions` whose style is not `percent`, it formats the value
|
|
89
|
+
itself (`$1,250`). A `percent` style still formats the *ratio*, merging the
|
|
90
|
+
caller's other options, because `Intl` multiplies by a hundred and a value of 72
|
|
91
|
+
would read `7,200%`.
|
|
92
|
+
- **`valueLabel` replaces the readout and the spoken text alike.** One string,
|
|
93
|
+
so the screen and the screen reader cannot disagree.
|
|
94
|
+
- **`Progress.Output` takes a function child** handed `ProgressRenderProps`, for
|
|
95
|
+
a readout the formatter cannot express — `18 of 24 seats`. While
|
|
96
|
+
indeterminate the default readout renders nothing; a function still runs.
|
|
97
|
+
|
|
98
|
+
## Accessibility
|
|
99
|
+
|
|
100
|
+
- **The root is the one accessible element**, `accessibilityRole="progressbar"`,
|
|
101
|
+
with the range and value in `accessibilityValue`. `resolveProgressAccessibility`
|
|
102
|
+
decides the spoken text: the percentage over 0–100, `18 of 24` over any other
|
|
103
|
+
range (formatted like the readout), or `valueLabel`. The count is what the
|
|
104
|
+
screen is about; "seventy-five percent" makes the listener divide back.
|
|
105
|
+
- **`Progress.Label` stays visible to assistive technology** and becomes the
|
|
106
|
+
element's name; `Progress.Output` is hidden (`accessibilityElementsHidden`,
|
|
107
|
+
`importantForAccessibility="no-hide-descendants"`) because the root already
|
|
108
|
+
speaks the value — left visible, it is read twice.
|
|
109
|
+
- **Indeterminate reports `busy: true` and no value.** A `now` of 0 is read out
|
|
110
|
+
as "zero percent", a lie about work that is under way. A bar with no label
|
|
111
|
+
needs an `accessibilityLabel`.
|
|
112
|
+
|
|
113
|
+
## Reuse
|
|
114
|
+
|
|
115
|
+
- **`Progress.Track` and `Progress.Fill` read only `ratio`, `isIndeterminate`,
|
|
116
|
+
`color`, `size` and `trackSize` from context.** A meter is the same track and
|
|
117
|
+
fill with a different root deciding the ratio and the colour, so a root that
|
|
118
|
+
publishes `ProgressContextValue` through `ProgressProvider` reuses both parts
|
|
119
|
+
as they stand. Keep anything a meter would not have — formatting, the
|
|
120
|
+
indeterminate loop's *decision* — out of those two parts' reads.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { Progress, type ProgressProps } from "./progress";
|
|
2
|
+
export {
|
|
3
|
+
type ProgressContextValue,
|
|
4
|
+
ProgressProvider,
|
|
5
|
+
useProgress,
|
|
6
|
+
useProgressContext,
|
|
7
|
+
} from "./progress.context";
|
|
8
|
+
export type { ProgressRenderChildren, ProgressRenderProps } from "./progress.types";
|
|
9
|
+
export {
|
|
10
|
+
fillTranslate,
|
|
11
|
+
formatProgressValue,
|
|
12
|
+
indeterminateSegment,
|
|
13
|
+
PROGRESS_COLORS,
|
|
14
|
+
PROGRESS_DEFAULT_COLOR,
|
|
15
|
+
PROGRESS_DEFAULT_SIZE,
|
|
16
|
+
PROGRESS_FILL_DURATION_MS,
|
|
17
|
+
PROGRESS_INDETERMINATE_DURATION_MS,
|
|
18
|
+
PROGRESS_INDETERMINATE_SEGMENT,
|
|
19
|
+
PROGRESS_MAX_VALUE,
|
|
20
|
+
PROGRESS_MIN_VALUE,
|
|
21
|
+
PROGRESS_OUTPUT_TEXT_SIZE,
|
|
22
|
+
PROGRESS_PULSE,
|
|
23
|
+
PROGRESS_SIZES,
|
|
24
|
+
type ProgressAccessibility,
|
|
25
|
+
type ProgressAxes,
|
|
26
|
+
type ProgressColor,
|
|
27
|
+
type ProgressFormatInput,
|
|
28
|
+
type ProgressOwnAxes,
|
|
29
|
+
type ProgressSize,
|
|
30
|
+
type ProgressVariantProps,
|
|
31
|
+
progressRatio,
|
|
32
|
+
progressVariants,
|
|
33
|
+
resolveProgressAccessibility,
|
|
34
|
+
resolveProgressAxes,
|
|
35
|
+
} from "./progress.variants";
|
|
36
|
+
export type { ProgressFillProps } from "./progress-fill";
|
|
37
|
+
export type { ProgressHeaderProps } from "./progress-header";
|
|
38
|
+
export type { ProgressLabelProps } from "./progress-label";
|
|
39
|
+
export type { ProgressOutputProps } from "./progress-output";
|
|
40
|
+
export type { ProgressTrackProps } from "./progress-track";
|