@delacour/react-native-ui 0.1.0-alpha.4 → 0.2.0-alpha.20261010061731
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +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 +10 -0
- package/src/components/badge/badge-close-button.tsx +2 -2
- package/src/components/badge/badge.context.tsx +2 -1
- package/src/components/badge/badge.tsx +13 -4
- package/src/components/badge/badge.variants.test.ts +26 -0
- package/src/components/badge/badge.variants.ts +21 -0
- package/src/components/bottom-sheet/AGENTS.md +186 -231
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
- package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
- package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
- package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
- package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
- package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
- package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
- package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
- package/src/components/bottom-sheet/index.ts +39 -17
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
- package/src/components/button/AGENTS.md +51 -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 +7 -1
- package/src/components/button/button.variants.test.ts +190 -0
- package/src/components/button/button.variants.ts +87 -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 +92 -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 +100 -0
- package/src/components/card/card.types.ts +13 -0
- package/src/components/card/card.variants.test.ts +240 -0
- package/src/components/card/card.variants.ts +177 -0
- package/src/components/card/index.ts +15 -0
- package/src/components/chart/AGENTS.md +8 -0
- package/src/components/chart/chart.tsx +8 -1
- package/src/components/chip/AGENTS.md +113 -0
- package/src/components/chip/chip-close-button.tsx +48 -0
- package/src/components/chip/chip-end-content.tsx +15 -0
- package/src/components/chip/chip-label.tsx +21 -0
- package/src/components/chip/chip-start-content.tsx +16 -0
- package/src/components/chip/chip.context.tsx +66 -0
- package/src/components/chip/chip.tsx +268 -0
- package/src/components/chip/chip.types.ts +10 -0
- package/src/components/chip/chip.variants.test.ts +335 -0
- package/src/components/chip/chip.variants.ts +321 -0
- package/src/components/chip/index.ts +24 -0
- package/src/components/collapsible/AGENTS.md +97 -0
- package/src/components/collapsible/collapsible-content.tsx +97 -0
- package/src/components/collapsible/collapsible-description.tsx +17 -0
- package/src/components/collapsible/collapsible-indicator.tsx +92 -0
- package/src/components/collapsible/collapsible-title.tsx +17 -0
- package/src/components/collapsible/collapsible-trigger.tsx +133 -0
- package/src/components/collapsible/collapsible.context.tsx +91 -0
- package/src/components/collapsible/collapsible.tsx +167 -0
- package/src/components/collapsible/collapsible.types.ts +9 -0
- package/src/components/collapsible/collapsible.variants.test.ts +338 -0
- package/src/components/collapsible/collapsible.variants.ts +228 -0
- package/src/components/collapsible/index.ts +32 -0
- package/src/components/context-menu/AGENTS.md +103 -0
- package/src/components/context-menu/context-menu-content.tsx +64 -0
- package/src/components/context-menu/context-menu-item.tsx +22 -0
- package/src/components/context-menu/context-menu-preview.tsx +60 -0
- package/src/components/context-menu/context-menu-trigger.tsx +160 -0
- package/src/components/context-menu/context-menu.context.tsx +91 -0
- package/src/components/context-menu/context-menu.tsx +90 -0
- package/src/components/context-menu/context-menu.variants.test.ts +266 -0
- package/src/components/context-menu/context-menu.variants.ts +147 -0
- package/src/components/context-menu/index.ts +18 -0
- package/src/components/dialog/AGENTS.md +97 -0
- package/src/components/dialog/dialog-body.tsx +21 -0
- package/src/components/dialog/dialog-close.tsx +82 -0
- package/src/components/dialog/dialog-content.tsx +153 -0
- package/src/components/dialog/dialog-description.tsx +23 -0
- package/src/components/dialog/dialog-footer.tsx +32 -0
- package/src/components/dialog/dialog-header.tsx +20 -0
- package/src/components/dialog/dialog-title.tsx +42 -0
- package/src/components/dialog/dialog-trigger.tsx +54 -0
- package/src/components/dialog/dialog.context.tsx +90 -0
- package/src/components/dialog/dialog.tsx +119 -0
- package/src/components/dialog/dialog.variants.test.ts +224 -0
- package/src/components/dialog/dialog.variants.ts +173 -0
- package/src/components/dialog/index.ts +30 -0
- package/src/components/drawer/AGENTS.md +123 -0
- package/src/components/drawer/drawer-body.tsx +46 -0
- package/src/components/drawer/drawer-close.tsx +76 -0
- package/src/components/drawer/drawer-content.tsx +186 -0
- package/src/components/drawer/drawer-description.tsx +23 -0
- package/src/components/drawer/drawer-footer.tsx +21 -0
- package/src/components/drawer/drawer-header.tsx +42 -0
- package/src/components/drawer/drawer-title.tsx +35 -0
- package/src/components/drawer/drawer-trigger.tsx +54 -0
- package/src/components/drawer/drawer.context.tsx +114 -0
- package/src/components/drawer/drawer.tsx +119 -0
- package/src/components/drawer/drawer.variants.test.ts +392 -0
- package/src/components/drawer/drawer.variants.ts +244 -0
- package/src/components/drawer/index.ts +41 -0
- package/src/components/drawer/use-drawer-pan.ts +108 -0
- package/src/components/empty-state/AGENTS.md +90 -0
- package/src/components/empty-state/empty-state-content.tsx +17 -0
- package/src/components/empty-state/empty-state-description.tsx +12 -0
- package/src/components/empty-state/empty-state-header.tsx +17 -0
- package/src/components/empty-state/empty-state-media.tsx +56 -0
- package/src/components/empty-state/empty-state-title.tsx +18 -0
- package/src/components/empty-state/empty-state.context.tsx +65 -0
- package/src/components/empty-state/empty-state.tsx +84 -0
- package/src/components/empty-state/empty-state.types.ts +14 -0
- package/src/components/empty-state/empty-state.variants.test.ts +245 -0
- package/src/components/empty-state/empty-state.variants.ts +132 -0
- package/src/components/empty-state/index.ts +20 -0
- package/src/components/fab/AGENTS.md +119 -0
- package/src/components/fab/fab-action.tsx +118 -0
- package/src/components/fab/fab-group.tsx +214 -0
- package/src/components/fab/fab-label.tsx +18 -0
- package/src/components/fab/fab.context.tsx +125 -0
- package/src/components/fab/fab.tsx +156 -0
- package/src/components/fab/fab.types.ts +17 -0
- package/src/components/fab/fab.variants.test.ts +274 -0
- package/src/components/fab/fab.variants.ts +234 -0
- package/src/components/fab/index.ts +37 -0
- package/src/components/feedback/AGENTS.md +95 -0
- package/src/components/feedback/feedback-action.tsx +16 -0
- package/src/components/feedback/feedback-cancel.tsx +34 -0
- package/src/components/feedback/feedback-close.tsx +21 -0
- package/src/components/feedback/feedback-content.tsx +24 -0
- package/src/components/feedback/feedback-field.tsx +70 -0
- package/src/components/feedback/feedback-footer.tsx +21 -0
- package/src/components/feedback/feedback-panel.tsx +54 -0
- package/src/components/feedback/feedback-submit.tsx +74 -0
- package/src/components/feedback/feedback-title.tsx +34 -0
- package/src/components/feedback/feedback-trigger.tsx +20 -0
- package/src/components/feedback/feedback.context.tsx +73 -0
- package/src/components/feedback/feedback.tsx +135 -0
- package/src/components/feedback/feedback.variants.test.ts +185 -0
- package/src/components/feedback/feedback.variants.ts +128 -0
- package/src/components/feedback/index.ts +24 -0
- package/src/components/field/AGENTS.md +19 -0
- package/src/components/field/field-label.tsx +19 -5
- package/src/components/field/field.context.tsx +17 -0
- package/src/components/field/field.tsx +15 -2
- package/src/components/field/field.variants.test.ts +24 -0
- package/src/components/field/field.variants.ts +20 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/input/AGENTS.md +5 -0
- package/src/components/input/input.variants.test.ts +32 -0
- package/src/components/input/input.variants.ts +13 -1
- package/src/components/item/AGENTS.md +92 -0
- package/src/components/item/index.ts +25 -0
- package/src/components/item/item-actions.tsx +28 -0
- package/src/components/item/item-content.tsx +15 -0
- package/src/components/item/item-description.tsx +12 -0
- package/src/components/item/item-footer.tsx +13 -0
- package/src/components/item/item-group.tsx +32 -0
- package/src/components/item/item-header.tsx +13 -0
- package/src/components/item/item-media.tsx +37 -0
- package/src/components/item/item-separator.tsx +13 -0
- package/src/components/item/item-title.tsx +17 -0
- package/src/components/item/item.context.tsx +66 -0
- package/src/components/item/item.tsx +218 -0
- package/src/components/item/item.types.ts +12 -0
- package/src/components/item/item.variants.test.ts +263 -0
- package/src/components/item/item.variants.ts +200 -0
- package/src/components/kpi/AGENTS.md +125 -0
- package/src/components/kpi/index.ts +45 -0
- package/src/components/kpi/kpi-action.tsx +12 -0
- package/src/components/kpi/kpi-content.tsx +35 -0
- package/src/components/kpi/kpi-footer.tsx +52 -0
- package/src/components/kpi/kpi-group.tsx +70 -0
- package/src/components/kpi/kpi-header.tsx +25 -0
- package/src/components/kpi/kpi-icon.tsx +28 -0
- package/src/components/kpi/kpi-sparkline.tsx +145 -0
- package/src/components/kpi/kpi-stat.tsx +20 -0
- package/src/components/kpi/kpi-title.tsx +19 -0
- package/src/components/kpi/kpi-trend.tsx +105 -0
- package/src/components/kpi/kpi-value.tsx +32 -0
- package/src/components/kpi/kpi.context.tsx +111 -0
- package/src/components/kpi/kpi.tsx +154 -0
- package/src/components/kpi/kpi.types.ts +9 -0
- package/src/components/kpi/kpi.variants.test.ts +428 -0
- package/src/components/kpi/kpi.variants.ts +347 -0
- package/src/components/label/AGENTS.md +89 -0
- package/src/components/label/index.ts +10 -0
- package/src/components/label/label.tsx +97 -0
- package/src/components/label/label.variants.test.ts +176 -0
- package/src/components/label/label.variants.ts +123 -0
- package/src/components/menu/AGENTS.md +176 -0
- package/src/components/menu/index.ts +44 -0
- package/src/components/menu/menu-background.tsx +17 -0
- package/src/components/menu/menu-checkbox-item.tsx +57 -0
- package/src/components/menu/menu-content.tsx +303 -0
- package/src/components/menu/menu-item.tsx +162 -0
- package/src/components/menu/menu-label.tsx +14 -0
- package/src/components/menu/menu-radio-group.tsx +50 -0
- package/src/components/menu/menu-radio-item.tsx +57 -0
- package/src/components/menu/menu-separator.tsx +11 -0
- package/src/components/menu/menu-sub-content.tsx +72 -0
- package/src/components/menu/menu-sub-trigger.tsx +50 -0
- package/src/components/menu/menu-sub.tsx +75 -0
- package/src/components/menu/menu-trigger.tsx +57 -0
- package/src/components/menu/menu.context.tsx +214 -0
- package/src/components/menu/menu.tsx +88 -0
- package/src/components/menu/menu.types.ts +33 -0
- package/src/components/menu/menu.variants.test.ts +400 -0
- package/src/components/menu/menu.variants.ts +353 -0
- package/src/components/meter/AGENTS.md +135 -0
- package/src/components/meter/index.ts +43 -0
- package/src/components/meter/meter-fill.tsx +19 -0
- package/src/components/meter/meter-header.tsx +17 -0
- package/src/components/meter/meter-label.tsx +18 -0
- package/src/components/meter/meter-output.tsx +42 -0
- package/src/components/meter/meter-segments.tsx +83 -0
- package/src/components/meter/meter-track.tsx +16 -0
- package/src/components/meter/meter.context.tsx +62 -0
- package/src/components/meter/meter.tsx +286 -0
- package/src/components/meter/meter.types.ts +30 -0
- package/src/components/meter/meter.variants.test.ts +430 -0
- package/src/components/meter/meter.variants.ts +347 -0
- package/src/components/overlay/AGENTS.md +89 -0
- package/src/components/overlay/index.ts +27 -0
- package/src/components/overlay/overlay-portal.tsx +84 -0
- package/src/components/overlay/overlay-presence.test.ts +46 -0
- package/src/components/overlay/overlay-presence.ts +49 -0
- package/src/components/overlay/overlay-registry.test.ts +96 -0
- package/src/components/overlay/overlay-registry.ts +99 -0
- package/src/components/overlay/overlay-scrim.tsx +49 -0
- package/src/components/overlay/overlay.context.tsx +48 -0
- package/src/components/overlay/overlay.tsx +118 -0
- package/src/components/overlay/overlay.variants.test.ts +37 -0
- package/src/components/overlay/overlay.variants.ts +33 -0
- package/src/components/overlay/use-overlay-back-handler.ts +34 -0
- package/src/components/overlay/use-overlay-presence.ts +87 -0
- package/src/components/popover/AGENTS.md +138 -0
- package/src/components/popover/index.ts +48 -0
- package/src/components/popover/popover-anchor.tsx +49 -0
- package/src/components/popover/popover-arrow.tsx +89 -0
- package/src/components/popover/popover-close.tsx +73 -0
- package/src/components/popover/popover-content.tsx +235 -0
- package/src/components/popover/popover-description.tsx +18 -0
- package/src/components/popover/popover-title.tsx +33 -0
- package/src/components/popover/popover-trigger.tsx +56 -0
- package/src/components/popover/popover.context.tsx +71 -0
- package/src/components/popover/popover.position.test.ts +354 -0
- package/src/components/popover/popover.position.ts +286 -0
- package/src/components/popover/popover.tsx +123 -0
- package/src/components/popover/popover.variants.test.ts +135 -0
- package/src/components/popover/popover.variants.ts +85 -0
- package/src/components/popover/use-anchor-measure.ts +76 -0
- package/src/components/popover/use-anchored-content.ts +219 -0
- package/src/components/pressable/AGENTS.md +11 -1
- package/src/components/pressable/pressable.tsx +7 -2
- package/src/components/pressable/pressable.variants.test.ts +29 -0
- package/src/components/pressable/pressable.variants.ts +17 -0
- package/src/components/progress/AGENTS.md +120 -0
- package/src/components/progress/index.ts +40 -0
- package/src/components/progress/progress-fill.tsx +152 -0
- package/src/components/progress/progress-header.tsx +19 -0
- package/src/components/progress/progress-label.tsx +27 -0
- package/src/components/progress/progress-output.tsx +47 -0
- package/src/components/progress/progress-track.tsx +38 -0
- package/src/components/progress/progress.context.tsx +79 -0
- package/src/components/progress/progress.tsx +171 -0
- package/src/components/progress/progress.types.ts +23 -0
- package/src/components/progress/progress.variants.test.ts +335 -0
- package/src/components/progress/progress.variants.ts +284 -0
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/provider/AGENTS.md +38 -25
- package/src/components/provider/provider.tsx +38 -11
- package/src/components/radio/AGENTS.md +8 -10
- package/src/components/radio/radio.variants.test.ts +5 -6
- package/src/components/radio/radio.variants.ts +6 -7
- package/src/components/rating/AGENTS.md +143 -0
- package/src/components/rating/index.ts +43 -0
- package/src/components/rating/rating-output.tsx +39 -0
- package/src/components/rating/rating-star.tsx +70 -0
- package/src/components/rating/rating-stars.tsx +219 -0
- package/src/components/rating/rating.context.tsx +79 -0
- package/src/components/rating/rating.tsx +163 -0
- package/src/components/rating/rating.variants.test.ts +347 -0
- package/src/components/rating/rating.variants.ts +354 -0
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
- package/src/components/skeleton/AGENTS.md +120 -0
- package/src/components/skeleton/index.ts +15 -0
- package/src/components/skeleton/skeleton-clock.ts +48 -0
- package/src/components/skeleton/skeleton-group.tsx +64 -0
- package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
- package/src/components/skeleton/skeleton.context.tsx +45 -0
- package/src/components/skeleton/skeleton.tsx +227 -0
- package/src/components/skeleton/skeleton.variants.test.ts +245 -0
- package/src/components/skeleton/skeleton.variants.ts +222 -0
- package/src/components/slide-button/AGENTS.md +122 -0
- package/src/components/slide-button/index.ts +43 -0
- package/src/components/slide-button/slide-button-label.tsx +70 -0
- package/src/components/slide-button/slide-button-thumb.tsx +93 -0
- package/src/components/slide-button/slide-button.context.tsx +76 -0
- package/src/components/slide-button/slide-button.tsx +457 -0
- package/src/components/slide-button/slide-button.variants.test.ts +288 -0
- package/src/components/slide-button/slide-button.variants.ts +324 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider-thumb.tsx +33 -1
- package/src/components/slider/slider.variants.test.ts +52 -0
- package/src/components/slider/slider.variants.ts +56 -2
- package/src/components/stack-card/AGENTS.md +169 -0
- package/src/components/stack-card/index.ts +37 -0
- package/src/components/stack-card/stack-card-action.tsx +66 -0
- package/src/components/stack-card/stack-card-actions.tsx +18 -0
- package/src/components/stack-card/stack-card-card.tsx +71 -0
- package/src/components/stack-card/stack-card-empty.tsx +30 -0
- package/src/components/stack-card/stack-card-slot.tsx +90 -0
- package/src/components/stack-card/stack-card-stamp.tsx +76 -0
- package/src/components/stack-card/stack-card.context.tsx +117 -0
- package/src/components/stack-card/stack-card.tsx +557 -0
- package/src/components/stack-card/stack-card.types.ts +26 -0
- package/src/components/stack-card/stack-card.variants.test.ts +355 -0
- package/src/components/stack-card/stack-card.variants.ts +360 -0
- package/src/components/steps/AGENTS.md +153 -0
- package/src/components/steps/index.ts +45 -0
- package/src/components/steps/steps-connector.tsx +31 -0
- package/src/components/steps/steps-description.tsx +26 -0
- package/src/components/steps/steps-indicator.tsx +106 -0
- package/src/components/steps/steps-item.tsx +225 -0
- package/src/components/steps/steps-panel.tsx +41 -0
- package/src/components/steps/steps-title.tsx +32 -0
- package/src/components/steps/steps-track.tsx +59 -0
- package/src/components/steps/steps.context.tsx +123 -0
- package/src/components/steps/steps.tsx +166 -0
- package/src/components/steps/steps.variants.test.ts +388 -0
- package/src/components/steps/steps.variants.ts +468 -0
- package/src/components/surface/AGENTS.md +82 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +56 -0
- package/src/components/surface/surface.tsx +87 -0
- package/src/components/surface/surface.variants.test.ts +227 -0
- package/src/components/surface/surface.variants.ts +165 -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/text/AGENTS.md +8 -2
- package/src/components/text/text.tsx +7 -0
- package/src/components/text/text.variants.test.ts +24 -1
- package/src/components/text/text.variants.ts +5 -0
- package/src/components/textarea/AGENTS.md +89 -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.parity.test.ts +62 -0
- package/src/lib/cn.test.ts +14 -0
- package/src/lib/cn.ts +11 -6
- 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 +44 -0
- package/src/styles/tokens.test.ts +56 -0
- package/src/styles/tokens.ts +29 -1
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
2
1
|
import type { ReactElement, ReactNode } from "react";
|
|
3
2
|
import type { StyleProp, ViewStyle } from "react-native";
|
|
4
3
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
5
4
|
import { KeyboardProvider } from "react-native-keyboard-controller";
|
|
6
5
|
import { initialWindowMetrics, type Metrics, SafeAreaProvider } from "react-native-safe-area-context";
|
|
6
|
+
import { CalmMotionProvider } from "../../hooks/use-calm-motion";
|
|
7
7
|
import { KeyboardStateSync } from "../../hooks/use-keyboard-state-sync";
|
|
8
8
|
|
|
9
9
|
export type DelacourProviderProps = {
|
|
@@ -21,6 +21,16 @@ export type DelacourProviderProps = {
|
|
|
21
21
|
* `null` is a value here rather than an absence.
|
|
22
22
|
*/
|
|
23
23
|
initialMetrics?: Metrics | null;
|
|
24
|
+
/**
|
|
25
|
+
* Hold every decorative loop still — a skeleton's shimmer or pulse — whatever
|
|
26
|
+
* the OS reduce-motion setting says. Defaults to `false`.
|
|
27
|
+
*
|
|
28
|
+
* For an E2E build. A test runner that waits for the screen to stop moving
|
|
29
|
+
* before each gesture (Argent, Detox, Maestro) pays its whole timeout on any
|
|
30
|
+
* screen holding a loop that never ends. Motion that *is* the behaviour — a
|
|
31
|
+
* `Spinner`, an indeterminate `Progress` — keeps moving either way.
|
|
32
|
+
*/
|
|
33
|
+
isMotionCalm?: boolean;
|
|
24
34
|
/**
|
|
25
35
|
* Style for the outermost `GestureHandlerRootView`.
|
|
26
36
|
*
|
|
@@ -52,15 +62,23 @@ export type DelacourProviderProps = {
|
|
|
52
62
|
* `will` events, so a keyboard that vanishes without one — an interactive
|
|
53
63
|
* dismiss interrupted by navigation, a stack pop, an app suspend — leaves
|
|
54
64
|
* every screen in the app believing it is still open.
|
|
55
|
-
* 5. `
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* keyboard values a sheet's footer rides. It wraps `{children}` and nothing
|
|
59
|
-
* else moves, which is what a new layer here always looks like.
|
|
65
|
+
* 5. `CalmMotionProvider` — the app's `isMotionCalm`, for `useCalmMotion()`.
|
|
66
|
+
* Pure context, no native view, so it wraps `{children}` alone — innermost,
|
|
67
|
+
* where a new layer goes.
|
|
60
68
|
*
|
|
61
|
-
* `
|
|
62
|
-
*
|
|
63
|
-
*
|
|
69
|
+
* **`BottomSheetProvider` is not here, and the app mounts it.** The sheet's
|
|
70
|
+
* engine, `@delacour/react-native-bottom-sheet`, is an optional peer of this
|
|
71
|
+
* library: importing it from the recommended root would make every app resolve
|
|
72
|
+
* it, sheet or no sheet. So the provider is exported from
|
|
73
|
+
* `@delacour/react-native-ui/bottom-sheet` and goes innermost, inside this and
|
|
74
|
+
* around the navigator — it reads the gesture root for its pans, the safe area
|
|
75
|
+
* for its insets and the keyboard values a sheet's footer rides, and it draws
|
|
76
|
+
* above the app, so it wraps `{children}` and nothing else moves. That is what
|
|
77
|
+
* a new layer here always looks like, one package along.
|
|
78
|
+
*
|
|
79
|
+
* `KeyboardStateSync` is a sibling of `{children}` rather than a wrapper. The
|
|
80
|
+
* repair is global and has to run for the whole app's lifetime; inside a layer
|
|
81
|
+
* that can remount it would be torn down with it.
|
|
64
82
|
*
|
|
65
83
|
* There are no per-layer escape hatches and no layer-named props on purpose.
|
|
66
84
|
* An app that needs a different stack composes the providers by hand; they are
|
|
@@ -69,13 +87,16 @@ export type DelacourProviderProps = {
|
|
|
69
87
|
* @example
|
|
70
88
|
* // expo-router root layout. The css import must stay the first statement.
|
|
71
89
|
* import "../styles/global.css";
|
|
90
|
+
* import { BottomSheetProvider } from "@delacour/react-native-ui/bottom-sheet";
|
|
72
91
|
* import { DelacourProvider } from "@delacour/react-native-ui/provider";
|
|
73
92
|
* import { Stack } from "expo-router";
|
|
74
93
|
*
|
|
75
94
|
* export default function RootLayout() {
|
|
76
95
|
* return (
|
|
77
96
|
* <DelacourProvider>
|
|
78
|
-
* <
|
|
97
|
+
* <BottomSheetProvider>
|
|
98
|
+
* <Stack screenOptions={{ headerShown: false }} />
|
|
99
|
+
* </BottomSheetProvider>
|
|
79
100
|
* </DelacourProvider>
|
|
80
101
|
* );
|
|
81
102
|
* }
|
|
@@ -85,10 +106,16 @@ export type DelacourProviderProps = {
|
|
|
85
106
|
* // app that launches into a rotated or split-screen window and cannot
|
|
86
107
|
* // tolerate one stale frame.
|
|
87
108
|
* <DelacourProvider initialMetrics={null}>{children}</DelacourProvider>
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* // An E2E build: decorative loops hold still so the test runner never waits
|
|
112
|
+
* // out a shimmer. `IS_E2E` is the app's own build flag.
|
|
113
|
+
* <DelacourProvider isMotionCalm={IS_E2E}>{children}</DelacourProvider>
|
|
88
114
|
*/
|
|
89
115
|
export function DelacourProvider({
|
|
90
116
|
children,
|
|
91
117
|
initialMetrics = initialWindowMetrics,
|
|
118
|
+
isMotionCalm = false,
|
|
92
119
|
style,
|
|
93
120
|
}: DelacourProviderProps): ReactElement {
|
|
94
121
|
return (
|
|
@@ -96,7 +123,7 @@ export function DelacourProvider({
|
|
|
96
123
|
<SafeAreaProvider initialMetrics={initialMetrics}>
|
|
97
124
|
<KeyboardProvider>
|
|
98
125
|
<KeyboardStateSync />
|
|
99
|
-
<
|
|
126
|
+
<CalmMotionProvider isMotionCalm={isMotionCalm}>{children}</CalmMotionProvider>
|
|
100
127
|
</KeyboardProvider>
|
|
101
128
|
</SafeAreaProvider>
|
|
102
129
|
</GestureHandlerRootView>
|
|
@@ -78,16 +78,14 @@ sit in `radio.variants.ts` beside the pure resolver that reads them. See
|
|
|
78
78
|
A test asserts the `dot` slot claims neither `opacity-*` nor `scale-*` in any
|
|
79
79
|
cell: a class fighting a `useAnimatedStyle` for the same property is a dot that
|
|
80
80
|
never appears, with no error anywhere.
|
|
81
|
-
- **The disabled fade lands on the ring and the label, never on the root.**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
quarter opacity. Tests pin both halves. This is the failure `Switch`, `Tabs`
|
|
90
|
-
and `Accordion` all cite back here.
|
|
81
|
+
- **The disabled fade lands on the ring and the label, never on the root.** It
|
|
82
|
+
was placed there because [`Pressable`](../pressable/AGENTS.md)'s animated
|
|
83
|
+
style used to overwrite an `opacity-50` on the root; `Pressable` now
|
|
84
|
+
multiplies a className's opacity into its press, so the root *could* fade,
|
|
85
|
+
but the fade stays on the parts: an `opacity-50` on the root as well would
|
|
86
|
+
compound with them and land at a quarter. The group must not fade either, for
|
|
87
|
+
the same reason. Tests pin both halves. `Switch`, `Tabs` and `Accordion` all
|
|
88
|
+
cite back here.
|
|
91
89
|
- **The ring is drawn from `View`s rather than a Central Icon.** Rule 5 governs
|
|
92
90
|
icons and [Spinner](../spinner/AGENTS.md)'s arc is the precedent for
|
|
93
91
|
primitives; here the set has no ring-with-a-centred-dot glyph at all. Two
|
|
@@ -192,10 +192,9 @@ describe("the root slot", () => {
|
|
|
192
192
|
expect([...gaps]).toEqual([...gaps].sort((a, b) => a - b));
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
test("never carries an opacity, which Pressable's animated style would overwrite", () => {
|
|
195
|
+
// The fade lives on the ring and the label; an `opacity-50` on the root as
|
|
196
|
+
// well would compound with them and land at a quarter.
|
|
197
|
+
test("never carries an opacity, which would compound with its children's", () => {
|
|
199
198
|
for (const cell of CELLS) {
|
|
200
199
|
expect(radioVariants(cell).root()).not.toMatch(/\bopacity-/);
|
|
201
200
|
}
|
|
@@ -397,8 +396,8 @@ describe("a trailing indicator", () => {
|
|
|
397
396
|
});
|
|
398
397
|
|
|
399
398
|
describe("a disabled radio", () => {
|
|
400
|
-
// The ring and the label
|
|
401
|
-
//
|
|
399
|
+
// The ring and the label carry the fade; the root stays clear so the two
|
|
400
|
+
// never compound.
|
|
402
401
|
test("fades its ring and its label", () => {
|
|
403
402
|
expect(radioVariants({ isDisabled: true }).indicator()).toContain("opacity-50");
|
|
404
403
|
expect(cls(radioVariants({ isDisabled: true }).label())).toContain("opacity-50");
|
|
@@ -119,13 +119,12 @@ export const radioVariants = tv({
|
|
|
119
119
|
// caller's `justify-between` because the root already knows where the
|
|
120
120
|
// indicator landed — see {@link resolveIndicatorPlacement}.
|
|
121
121
|
isIndicatorTrailing: { true: { root: "justify-between" }, false: {} },
|
|
122
|
-
// The fade lands on the children, never on the `root
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// multiplies with the root's instead of fighting it.
|
|
122
|
+
// The fade lands on the children, never on the `root`. `root` is worn by
|
|
123
|
+
// `Pressable`'s own `Animated.View`, which now multiplies a className's
|
|
124
|
+
// opacity into its press — but the ring and the label fading on their own
|
|
125
|
+
// keeps the row's hit area at full contrast and lets each part be tuned,
|
|
126
|
+
// so it stays here. Putting `opacity-50` on the root as well would
|
|
127
|
+
// compound with these and land at a quarter.
|
|
129
128
|
//
|
|
130
129
|
// The group must not fade either, or a disabled group would compound
|
|
131
130
|
// `opacity-50` with each of its rows and land at a quarter opacity.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Rating
|
|
2
|
+
|
|
3
|
+
A row of stars that reads or sets a score. Compound root plus `Rating.Stars` and
|
|
4
|
+
`Rating.Output`. Read-only or interactive, in whole or fractional steps, set by a
|
|
5
|
+
tap or a drag along the row.
|
|
6
|
+
|
|
7
|
+
`import { Rating } from "@delacour/react-native-ui/rating";`
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `index.ts` | → `@delacour/react-native-ui/rating` |
|
|
14
|
+
| `rating.tsx` | Root + the `Object.assign` compound surface |
|
|
15
|
+
| `rating-stars.tsx` | `Rating.Stars` — owns the pan, the measurement, the haptic and the accessibility surface |
|
|
16
|
+
| `rating-star.tsx` | One star: an empty glyph and a clipped filled one. Internal |
|
|
17
|
+
| `rating-output.tsx` | `Rating.Output`, the `Text.Label` readout |
|
|
18
|
+
| `rating.context.tsx` | `RatingContext`, `useRating()`, `useRatingContext()`, `useRatingPart()`, `RatingRenderProps` |
|
|
19
|
+
| `rating.variants.ts` | Pure `tv()` slots, the value maths and the paint resolver, no RN imports |
|
|
20
|
+
| `rating.variants.test.ts` | |
|
|
21
|
+
|
|
22
|
+
There is no `rating.types.ts`: `RatingRenderProps` is the one shared type and it
|
|
23
|
+
is part of the context's own shape, so it lives beside it.
|
|
24
|
+
|
|
25
|
+
## Design
|
|
26
|
+
|
|
27
|
+
- **Colours**: `default`, `primary`, `success`, `warning`, `destructive`, `info` —
|
|
28
|
+
Slider's set. The default is **`warning`**, not `default`: a star is read as
|
|
29
|
+
amber before it is read as a star, and a row of ink stars reads as a set of
|
|
30
|
+
icons rather than a score. **Sizes**: `sm`, `md`, `lg`, which are `icon-lg`,
|
|
31
|
+
`icon-xl` and `icon-2xl` on the icon scale — a star is a mark, the way a
|
|
32
|
+
checkbox's tick is.
|
|
33
|
+
- **The anatomy is written out**, `Rating.Stars` and an optional
|
|
34
|
+
`Rating.Output`, rather than a `showValue` boolean. A readout beside the stars,
|
|
35
|
+
under them, or formatted as "4.3 · 128 reviews" is three layouts one boolean
|
|
36
|
+
could not express; the root is a column and a `flex-row` class puts it inline.
|
|
37
|
+
- **`maxValue` is the star count and `step` the precision**, the names `Slider`
|
|
38
|
+
uses for the same two ideas. A fraction of a count rounds *down*; anything
|
|
39
|
+
that is not a count falls back to five. A step must divide one star — `0.5`
|
|
40
|
+
and `0.25` do, `0.3` does not — and falls back to whole stars otherwise,
|
|
41
|
+
because a rating is usually fed a number from a server rather than a literal.
|
|
42
|
+
|
|
43
|
+
## Value
|
|
44
|
+
|
|
45
|
+
- **Any value is drawn; only a touch is snapped.** `clampRating` holds the value
|
|
46
|
+
inside zero and the count and does not snap, so a read-only average of 3.7
|
|
47
|
+
fills seven tenths of the fourth star. `step` constrains what a finger can set.
|
|
48
|
+
A non-finite value draws as zero — a star whose width came out of `NaN` is a
|
|
49
|
+
row that lies about its score.
|
|
50
|
+
- **A touch rounds up to the next stop.** Any touch inside a star's cell takes
|
|
51
|
+
that star, and with half stars its left half is the half. A touch exactly on a
|
|
52
|
+
cell's edge belongs to the star before it; the epsilon in `ratingFromOffset` is
|
|
53
|
+
what stops `Math.ceil` and binary floating point disagreeing there.
|
|
54
|
+
- **A drag past the first star holds the lowest stop; it does not clear.** A drag
|
|
55
|
+
that overshoots the start is someone rating one star, not someone withdrawing
|
|
56
|
+
a rating. Clearing is its own gesture.
|
|
57
|
+
- **`allowClear` is a tap on the value already held**, decided in `onFinalize` by
|
|
58
|
+
`shouldClearRating`. It reads the travel since touch-down, so a drag that
|
|
59
|
+
wanders off and comes back to the same star is a drag rather than a clear, and
|
|
60
|
+
`RATING_TAP_SLOP` is the line between them.
|
|
61
|
+
- **The fills sum back to the value.** `starFillOf` gives every star before the
|
|
62
|
+
value a full fill, every star after it none and the one it lands in the
|
|
63
|
+
remainder, and a test sums them.
|
|
64
|
+
|
|
65
|
+
## Drawing
|
|
66
|
+
|
|
67
|
+
- **The star is an SVG path drawn here, not an icon.** Central Icons is an
|
|
68
|
+
outlined set: a filled star, and the clipped half of one, has no glyph there.
|
|
69
|
+
The path is ten vertices, stroked in its own fill colour with round joins, which
|
|
70
|
+
softens the points to sit beside the rounded icon set.
|
|
71
|
+
- **A partial star is two layers and a clip, not a gradient.** The empty glyph
|
|
72
|
+
sits underneath and the filled one is inside a `View` whose width is the fill as
|
|
73
|
+
a percentage, with `overflow-hidden`. The filled glyph keeps the full glyph's
|
|
74
|
+
size — the window narrows, never the star. A hard-stop gradient would draw the
|
|
75
|
+
same thing, but its stops are paint props re-parsed on every change.
|
|
76
|
+
- **The glyph's size is a class on a `View`, and the `Svg` fills it at `100%`.**
|
|
77
|
+
That keeps the icon scale the one source of the size without wrapping `Svg` in
|
|
78
|
+
`withUniwind`, which rule 7 forbids.
|
|
79
|
+
- **The colour is a token read with `useThemeColor`, not a class.** A path's
|
|
80
|
+
`fill` is a paint prop no `className` reaches. `RATING_FILL_TOKEN` names the
|
|
81
|
+
token per colour and `resolveRatingPaint` settles it, so the whole paint matrix
|
|
82
|
+
is reachable from `bun test`, which also checks every token it names exists in
|
|
83
|
+
both variants of `theme.css`.
|
|
84
|
+
- **An empty star is `muted-foreground` at 40% — the same chrome at every colour.**
|
|
85
|
+
No token sits where an empty star needs to: `border` and `input` vanish against
|
|
86
|
+
a light card, and `muted-foreground` at full strength reads as a disabled
|
|
87
|
+
filled star. The opacity is on the whole `Svg` view, not on the path's
|
|
88
|
+
`fillOpacity` and `strokeOpacity` — half the stroke overlaps the fill, and two
|
|
89
|
+
translucent paints draw a darker ring round every empty star.
|
|
90
|
+
- **Invalid outranks the colour on the empty stars too.** A required rating left
|
|
91
|
+
at zero has no filled star to turn red, so the outlines carry the signal or
|
|
92
|
+
there is none.
|
|
93
|
+
|
|
94
|
+
## Gesture
|
|
95
|
+
|
|
96
|
+
- **Neither the row nor a star is a `Pressable`**, for the reasons
|
|
97
|
+
[Slider](../slider/AGENTS.md) gives for its track: a tap-to-set would fire
|
|
98
|
+
`onPress` every time, and a `Pressable` per star would nest five taps inside the
|
|
99
|
+
row's pan. One `Gesture.Pan()` on the row reads the star under the finger. What
|
|
100
|
+
is inherited is `playHaptic`, the one haptic switch.
|
|
101
|
+
- **The value is written in `onBegin`**, because a pan activates on the first
|
|
102
|
+
movement and a stationary tap would never reach `onUpdate`. `minDistance(0)`
|
|
103
|
+
wins the touch from an enclosing scroll view and `shouldCancelWhenOutside(false)`
|
|
104
|
+
keeps a drag past the last star tracking — both exactly as `Slider.Track`.
|
|
105
|
+
- **Each star's cell carries the gap as padding**, so every cell is the same width
|
|
106
|
+
and a star's half is the half of its cell. A `gap-*` on the row would leave dead
|
|
107
|
+
space between cells that belongs to no star.
|
|
108
|
+
- **The row is padded on the cross axis only**, to 44pt at every size, and a test
|
|
109
|
+
asserts the sum. The main axis is not padded: the value is read straight off
|
|
110
|
+
the touch's offset along the row, and a gutter there would shift every value.
|
|
111
|
+
- **The stars render from React state; the gesture reads a shared value.** Unlike
|
|
112
|
+
a slider, a rating crosses a handful of stops in a whole drag, so a commit per
|
|
113
|
+
crossing is a handful of commits and there is no UI-thread mirror to pay for.
|
|
114
|
+
The shared value exists so the worklet can tell a crossing from a repeat
|
|
115
|
+
without waiting on a render. It is re-synced from React on every gesture's end,
|
|
116
|
+
through a token that exists only to re-run the effect — without it a controlled
|
|
117
|
+
parent that rejects a value leaves the shared value holding it.
|
|
118
|
+
- **The haptic ticks on grab, on every star crossed and on a clear.** There is no
|
|
119
|
+
distance rate limit as there is on `Slider`: the stops are a star apart, so a
|
|
120
|
+
flick across ten of them is ten ticks a hand can feel.
|
|
121
|
+
|
|
122
|
+
## Accessibility
|
|
123
|
+
|
|
124
|
+
- **The row is one `adjustable` control, not five buttons.** Its value is spoken
|
|
125
|
+
as "3.5 out of 5" and a swipe up or down calls `stepRating`, which lands a value
|
|
126
|
+
between stops on the *next* stop in the direction asked rather than a whole
|
|
127
|
+
step past it. Five buttons would make a screen-reader user visit every star to
|
|
128
|
+
learn one number.
|
|
129
|
+
- **A read-only rating is an `image` with the same value** — something to read,
|
|
130
|
+
not something to set, and no actions.
|
|
131
|
+
- **`Rating.Output` is hidden from assistive technology**, because the row already
|
|
132
|
+
speaks the value and a second, terser reading one swipe later is noise.
|
|
133
|
+
- **Pass `accessibilityLabel` to `Rating.Stars`**, not the root. It defaults to
|
|
134
|
+
"Rating", which is correct and says nothing about *what* is being rated.
|
|
135
|
+
|
|
136
|
+
## Axes
|
|
137
|
+
|
|
138
|
+
- **The ladder is two rungs**, the rating's own props then an enclosing
|
|
139
|
+
[Field](../field/AGENTS.md), as on `Slider`. A `Field` reaches the two *state*
|
|
140
|
+
axes only, and a test pins that it cannot acquire a paint axis.
|
|
141
|
+
- **`isReadOnly` is the rating's own and does not fade.** A score on a review card
|
|
142
|
+
is content, not a control that happens to be off, so it takes no touch and no
|
|
143
|
+
assistive actions but keeps full strength. `isDisabled` is the one that fades.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export { Rating, type RatingProps } from "./rating";
|
|
2
|
+
export {
|
|
3
|
+
type RatingContextValue,
|
|
4
|
+
RatingProvider,
|
|
5
|
+
type RatingRenderProps,
|
|
6
|
+
useRating,
|
|
7
|
+
useRatingContext,
|
|
8
|
+
} from "./rating.context";
|
|
9
|
+
export {
|
|
10
|
+
clampRating,
|
|
11
|
+
describeRating,
|
|
12
|
+
formatRatingValue,
|
|
13
|
+
normalizeRatingCount,
|
|
14
|
+
normalizeRatingStep,
|
|
15
|
+
RATING_COLORS,
|
|
16
|
+
RATING_COUNT,
|
|
17
|
+
RATING_DEFAULT_COLOR,
|
|
18
|
+
RATING_DEFAULT_SIZE,
|
|
19
|
+
RATING_EMPTY_OPACITY,
|
|
20
|
+
RATING_FILL_TOKEN,
|
|
21
|
+
RATING_OUTPUT_TEXT_SIZE,
|
|
22
|
+
RATING_SIZES,
|
|
23
|
+
RATING_STAR_PATH,
|
|
24
|
+
RATING_STAR_STROKE,
|
|
25
|
+
RATING_STEP,
|
|
26
|
+
RATING_TAP_SLOP,
|
|
27
|
+
type RatingAxes,
|
|
28
|
+
type RatingColor,
|
|
29
|
+
type RatingFieldAxes,
|
|
30
|
+
type RatingOwnAxes,
|
|
31
|
+
type RatingPaint,
|
|
32
|
+
type RatingSize,
|
|
33
|
+
type RatingVariantProps,
|
|
34
|
+
ratingFromOffset,
|
|
35
|
+
ratingVariants,
|
|
36
|
+
resolveRatingAxes,
|
|
37
|
+
resolveRatingPaint,
|
|
38
|
+
shouldClearRating,
|
|
39
|
+
starFillOf,
|
|
40
|
+
stepRating,
|
|
41
|
+
} from "./rating.variants";
|
|
42
|
+
export type { RatingOutputProps } from "./rating-output";
|
|
43
|
+
export type { RatingStarsProps } from "./rating-stars";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { type RatingRenderProps, useRatingPart } from "./rating.context";
|
|
4
|
+
import { formatRatingValue, RATING_OUTPUT_TEXT_SIZE, ratingVariants } from "./rating.variants";
|
|
5
|
+
|
|
6
|
+
export type RatingOutputProps = Omit<TextPresetProps, "children"> & {
|
|
7
|
+
/** Custom content, or a function called with the rating's settled state. */
|
|
8
|
+
children?: ReactNode | ((props: RatingRenderProps) => ReactNode);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The value, formatted — `4`, or `3.5`.
|
|
13
|
+
*
|
|
14
|
+
* Renders `Text.Label` and names a size step, never a scale of its own, the rule
|
|
15
|
+
* `Slider.Output` follows: the preset owns the weight and the colour, and the
|
|
16
|
+
* readout still tracks the rating's size.
|
|
17
|
+
*
|
|
18
|
+
* It is hidden from assistive technology. `Rating.Stars` already speaks the
|
|
19
|
+
* value as "3.5 out of 5", and a second, terser reading of the same number one
|
|
20
|
+
* swipe later is noise.
|
|
21
|
+
*/
|
|
22
|
+
export function RatingOutput({ children, className, size, ...props }: RatingOutputProps): ReactElement {
|
|
23
|
+
const context = useRatingPart("Rating.Output");
|
|
24
|
+
const content =
|
|
25
|
+
typeof children === "function" ? children(context.renderProps) : (children ?? formatRatingValue(context.value));
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Text.Label
|
|
29
|
+
accessibilityElementsHidden
|
|
30
|
+
className={ratingVariants({ size: context.size }).output({ className })}
|
|
31
|
+
importantForAccessibility="no-hide-descendants"
|
|
32
|
+
size={size ?? RATING_OUTPUT_TEXT_SIZE[context.size]}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
{content}
|
|
36
|
+
</Text.Label>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
RatingOutput.displayName = "DelacourUI.Rating.Output";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import Svg, { Path } from "react-native-svg";
|
|
4
|
+
import { RATING_EMPTY_OPACITY, RATING_STAR_PATH, RATING_STAR_STROKE } from "./rating.variants";
|
|
5
|
+
|
|
6
|
+
export type RatingStarProps = {
|
|
7
|
+
/** How much of the star is filled, 0–1. */
|
|
8
|
+
fill: number;
|
|
9
|
+
/** The resolved colour of the filled part. */
|
|
10
|
+
fillColor: string | undefined;
|
|
11
|
+
/** The resolved colour of the empty star, drawn at {@link RATING_EMPTY_OPACITY}. */
|
|
12
|
+
emptyColor: string | undefined;
|
|
13
|
+
/** The `size-icon-*` box the glyph fills. */
|
|
14
|
+
glyphClassName: string;
|
|
15
|
+
/** The clip window's class. Its width is set here, from `fill`. */
|
|
16
|
+
clipClassName: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One star: an empty glyph, and a filled one clipped to `fill` of its width.
|
|
21
|
+
*
|
|
22
|
+
* **Two layers rather than a gradient.** A hard-stop linear gradient on one path
|
|
23
|
+
* would draw the half star too, but its stops are paint props that React Native
|
|
24
|
+
* SVG re-parses on every change, and a clip is a `View` width the layout engine
|
|
25
|
+
* already animates cheaply. The filled glyph inside the clip keeps the full
|
|
26
|
+
* glyph's size — it is the *window* that narrows, never the star.
|
|
27
|
+
*
|
|
28
|
+
* Both glyphs are sized by a `size-icon-*` class on a `View` and fill it at
|
|
29
|
+
* `100%`, which keeps the icon scale the single source of the size without
|
|
30
|
+
* wrapping `Svg` in `withUniwind` (rule 7).
|
|
31
|
+
*
|
|
32
|
+
* Internal: `Rating.Stars` renders one per count, and nothing outside reaches it.
|
|
33
|
+
*/
|
|
34
|
+
export function RatingStar({
|
|
35
|
+
fill,
|
|
36
|
+
fillColor,
|
|
37
|
+
emptyColor,
|
|
38
|
+
glyphClassName,
|
|
39
|
+
clipClassName,
|
|
40
|
+
}: RatingStarProps): ReactElement {
|
|
41
|
+
return (
|
|
42
|
+
<View className={glyphClassName}>
|
|
43
|
+
<StarGlyph color={emptyColor} opacity={RATING_EMPTY_OPACITY} />
|
|
44
|
+
{fill > 0 ? (
|
|
45
|
+
<View className={clipClassName} style={{ width: `${fill * 100}%` }}>
|
|
46
|
+
<View className={glyphClassName}>
|
|
47
|
+
<StarGlyph color={fillColor} opacity={1} />
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
) : null}
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
RatingStar.displayName = "DelacourUI.Rating.Stars.Star";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The path itself, filled and stroked in one colour so the points come out rounded.
|
|
58
|
+
*
|
|
59
|
+
* The opacity is the whole view's, not the path's `fillOpacity` and
|
|
60
|
+
* `strokeOpacity`: half the stroke overlaps the fill, and two translucent paints
|
|
61
|
+
* over one another draw a darker ring round every empty star.
|
|
62
|
+
*/
|
|
63
|
+
function StarGlyph({ color, opacity }: { color: string | undefined; opacity: number }): ReactElement {
|
|
64
|
+
return (
|
|
65
|
+
<Svg height="100%" style={{ opacity }} viewBox="0 0 24 24" width="100%">
|
|
66
|
+
<Path d={RATING_STAR_PATH} fill={color} stroke={color} strokeLinejoin="round" strokeWidth={RATING_STAR_STROKE} />
|
|
67
|
+
</Svg>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
StarGlyph.displayName = "DelacourUI.Rating.Stars.Star.Glyph";
|