@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
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
|
|
4
|
+
export const MENU_PLACEMENTS = ["top", "bottom"] as const;
|
|
5
|
+
export const MENU_ALIGNS = ["start", "center", "end"] as const;
|
|
6
|
+
export const MENU_ITEM_VARIANTS = ["default", "destructive"] as const;
|
|
7
|
+
export const MENU_RADIO_INDICATORS = ["check", "dot"] as const;
|
|
8
|
+
|
|
9
|
+
export type MenuPlacement = (typeof MENU_PLACEMENTS)[number];
|
|
10
|
+
export type MenuAlign = (typeof MENU_ALIGNS)[number];
|
|
11
|
+
export type MenuItemVariant = (typeof MENU_ITEM_VARIANTS)[number];
|
|
12
|
+
export type MenuRadioIndicator = (typeof MENU_RADIO_INDICATORS)[number];
|
|
13
|
+
|
|
14
|
+
/** Where the panel is anchored, in window coordinates. A zero-size rect is a point. */
|
|
15
|
+
export type MenuAnchorRect = { x: number; y: number; width: number; height: number };
|
|
16
|
+
|
|
17
|
+
/** A screen edge's safe-area inset, in points. */
|
|
18
|
+
export type MenuInsets = { top: number; right: number; bottom: number; left: number };
|
|
19
|
+
|
|
20
|
+
/** `align` with RTL already applied — the edge the panel lines up with on screen. */
|
|
21
|
+
export type MenuPhysicalAlign = "left" | "center" | "right";
|
|
22
|
+
|
|
23
|
+
/** Gap between the anchor and the panel, in points. */
|
|
24
|
+
export const MENU_DEFAULT_OFFSET = 6;
|
|
25
|
+
|
|
26
|
+
/** What the panel keeps clear of the safe area on every side, in points. */
|
|
27
|
+
export const MENU_EDGE_MARGIN = 8;
|
|
28
|
+
|
|
29
|
+
/** The narrowest a panel resolves to when nothing names a width. */
|
|
30
|
+
export const MENU_DEFAULT_MIN_WIDTH = 200;
|
|
31
|
+
|
|
32
|
+
/** The scale the panel grows from on enter and shrinks to on exit. */
|
|
33
|
+
export const MENU_ENTER_SCALE = 0.96;
|
|
34
|
+
|
|
35
|
+
/** How long the panel takes to appear and to leave, in milliseconds. */
|
|
36
|
+
export const MENU_MOTION = { enterMs: 160, exitMs: 120 } as const;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A row's press: the fill fades in fast and out slower, and the row dips to
|
|
40
|
+
* `scale`. Both read one shared value.
|
|
41
|
+
*/
|
|
42
|
+
export const MENU_PRESS = { inMs: 90, outMs: 160, scale: 0.98 } as const;
|
|
43
|
+
|
|
44
|
+
/** How far a tap may travel before it stops being a tap — a scroll, in a long menu. */
|
|
45
|
+
export const MENU_TAP_MAX_DISTANCE = 10;
|
|
46
|
+
|
|
47
|
+
/** Where the sub-trigger's chevron points at each end of the travel, in degrees. */
|
|
48
|
+
export const MENU_SUB_ROTATION = { collapsed: 0, expanded: 90 } as const;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The submenu's spring — `Collapsible`'s, restated rather than imported so
|
|
52
|
+
* `delacour add menu` copies one folder. Critically damped, because an
|
|
53
|
+
* overshoot in height flashes the panel behind the rows.
|
|
54
|
+
*/
|
|
55
|
+
export const MENU_SUB_SPRING = { damping: 26, mass: 0.4, stiffness: 400 } as const;
|
|
56
|
+
|
|
57
|
+
/** Theme token a row's icon is drawn in, per variant. */
|
|
58
|
+
export const MENU_ICON_TOKEN: Record<MenuItemVariant, string> = {
|
|
59
|
+
default: "popover-foreground",
|
|
60
|
+
destructive: "destructive",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Theme token an indicator (check, chevron) is drawn in. */
|
|
64
|
+
export const MENU_INDICATOR_TOKEN = "popover-foreground";
|
|
65
|
+
|
|
66
|
+
/** The accessibility hint a trigger carries. */
|
|
67
|
+
export const MENU_TRIGGER_HINT = "Opens a menu";
|
|
68
|
+
|
|
69
|
+
/** `align` as a screen edge: start and end swap under RTL, center does not. */
|
|
70
|
+
export function resolveMenuPhysicalAlign(align: MenuAlign, isRTL: boolean): MenuPhysicalAlign {
|
|
71
|
+
if (align === "center") return "center";
|
|
72
|
+
const isStart = align === "start";
|
|
73
|
+
return isStart !== isRTL ? "left" : "right";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type MenuPlacementInput = {
|
|
77
|
+
anchor: MenuAnchorRect;
|
|
78
|
+
/** The panel's size — its resolved width and its measured (or estimated) height. */
|
|
79
|
+
content: { width: number; height: number };
|
|
80
|
+
window: { width: number; height: number };
|
|
81
|
+
insets: MenuInsets;
|
|
82
|
+
placement: MenuPlacement;
|
|
83
|
+
align: MenuAlign;
|
|
84
|
+
offset: number;
|
|
85
|
+
isRTL: boolean;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type MenuPlacementResult = {
|
|
89
|
+
/** The panel's top edge, for a panel below the anchor. */
|
|
90
|
+
top: number;
|
|
91
|
+
/**
|
|
92
|
+
* The distance from the window's bottom to the panel's bottom edge — what a
|
|
93
|
+
* panel above the anchor is positioned by, so it grows away from the anchor
|
|
94
|
+
* when its content does.
|
|
95
|
+
*/
|
|
96
|
+
bottom: number;
|
|
97
|
+
left: number;
|
|
98
|
+
/** The side it actually opened on, after any flip. */
|
|
99
|
+
placement: MenuPlacement;
|
|
100
|
+
/** The room on that side, inside the safe area and the margin. Never negative. */
|
|
101
|
+
maxHeight: number;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Where the panel goes.
|
|
106
|
+
*
|
|
107
|
+
* **Vertical.** The requested side wins when the content fits there; otherwise it
|
|
108
|
+
* flips to the other side if the content fits there; otherwise it takes the
|
|
109
|
+
* larger side and `maxHeight` caps it, so the rows scroll. Room is measured
|
|
110
|
+
* inside the safe area plus {@link MENU_EDGE_MARGIN}.
|
|
111
|
+
*
|
|
112
|
+
* **Horizontal.** `align` lines an edge (or the centre) up with the anchor's —
|
|
113
|
+
* start and end swap under RTL — and the result is clamped inside the safe area
|
|
114
|
+
* plus the margin. A panel wider than the room pins to the left edge.
|
|
115
|
+
*
|
|
116
|
+
* A zero-size anchor (a long-press point) runs through the same arithmetic with
|
|
117
|
+
* no special case: its edges coincide.
|
|
118
|
+
*
|
|
119
|
+
* Pure, and the one place the geometry lives — when Popover lands, Menu.Content
|
|
120
|
+
* moves onto it and this resolver moves with it.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveMenuPlacement({
|
|
123
|
+
anchor,
|
|
124
|
+
content,
|
|
125
|
+
window,
|
|
126
|
+
insets,
|
|
127
|
+
placement,
|
|
128
|
+
align,
|
|
129
|
+
offset,
|
|
130
|
+
isRTL,
|
|
131
|
+
}: MenuPlacementInput): MenuPlacementResult {
|
|
132
|
+
const topBound = insets.top + MENU_EDGE_MARGIN;
|
|
133
|
+
const bottomBound = window.height - insets.bottom - MENU_EDGE_MARGIN;
|
|
134
|
+
const belowStart = anchor.y + anchor.height + offset;
|
|
135
|
+
const aboveEnd = anchor.y - offset;
|
|
136
|
+
|
|
137
|
+
const room: Record<MenuPlacement, number> = {
|
|
138
|
+
bottom: Math.max(0, bottomBound - belowStart),
|
|
139
|
+
top: Math.max(0, aboveEnd - topBound),
|
|
140
|
+
};
|
|
141
|
+
const other: MenuPlacement = placement === "bottom" ? "top" : "bottom";
|
|
142
|
+
|
|
143
|
+
let side: MenuPlacement;
|
|
144
|
+
if (content.height <= room[placement]) side = placement;
|
|
145
|
+
else if (content.height <= room[other]) side = other;
|
|
146
|
+
else side = room[placement] >= room[other] ? placement : other;
|
|
147
|
+
|
|
148
|
+
const maxHeight = room[side];
|
|
149
|
+
const height = Math.min(content.height, maxHeight);
|
|
150
|
+
const top = side === "bottom" ? belowStart : aboveEnd - height;
|
|
151
|
+
const bottom = window.height - (top + height);
|
|
152
|
+
|
|
153
|
+
const physical = resolveMenuPhysicalAlign(align, isRTL);
|
|
154
|
+
let left: number;
|
|
155
|
+
if (physical === "left") left = anchor.x;
|
|
156
|
+
else if (physical === "right") left = anchor.x + anchor.width - content.width;
|
|
157
|
+
else left = anchor.x + anchor.width / 2 - content.width / 2;
|
|
158
|
+
|
|
159
|
+
const minLeft = insets.left + MENU_EDGE_MARGIN;
|
|
160
|
+
const maxLeft = window.width - insets.right - MENU_EDGE_MARGIN - content.width;
|
|
161
|
+
left = maxLeft < minLeft ? minLeft : Math.min(Math.max(left, minLeft), maxLeft);
|
|
162
|
+
|
|
163
|
+
return { bottom, left, maxHeight, placement: side, top };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The translate that makes a centre-origin scale look like it grows out of the
|
|
168
|
+
* anchor's side, at the panel's smallest. The worklet multiplies it by
|
|
169
|
+
* `1 - progress`, so it is zero once the panel has arrived.
|
|
170
|
+
*
|
|
171
|
+
* React Native has no `transformOrigin` that Reanimated animates reliably on
|
|
172
|
+
* both platforms, so the origin is approximated: a panel below grows from its
|
|
173
|
+
* top edge, a panel above from its bottom, and a start-aligned one from its
|
|
174
|
+
* leading edge.
|
|
175
|
+
*/
|
|
176
|
+
export function resolveMenuOrigin({
|
|
177
|
+
placement,
|
|
178
|
+
align,
|
|
179
|
+
width,
|
|
180
|
+
height,
|
|
181
|
+
}: {
|
|
182
|
+
placement: MenuPlacement;
|
|
183
|
+
align: MenuPhysicalAlign;
|
|
184
|
+
width: number;
|
|
185
|
+
height: number;
|
|
186
|
+
}): { x: number; y: number } {
|
|
187
|
+
const shrink = 1 - MENU_ENTER_SCALE;
|
|
188
|
+
const y = ((placement === "bottom" ? -1 : 1) * shrink * height) / 2;
|
|
189
|
+
const x = align === "center" ? 0 : ((align === "left" ? -1 : 1) * shrink * width) / 2;
|
|
190
|
+
return { x, y };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The panel's width: the caller's `width`, or the trigger's floored at
|
|
195
|
+
* {@link MENU_DEFAULT_MIN_WIDTH}; then floored by `minWidth`; then capped by
|
|
196
|
+
* `maxWidth` — the room between the safe-area edges — so it never runs off screen.
|
|
197
|
+
*/
|
|
198
|
+
export function resolveMenuWidth({
|
|
199
|
+
width,
|
|
200
|
+
minWidth,
|
|
201
|
+
maxWidth,
|
|
202
|
+
triggerWidth,
|
|
203
|
+
}: {
|
|
204
|
+
width?: number;
|
|
205
|
+
minWidth?: number;
|
|
206
|
+
maxWidth?: number;
|
|
207
|
+
triggerWidth: number;
|
|
208
|
+
}): number {
|
|
209
|
+
const base = width ?? Math.max(triggerWidth, MENU_DEFAULT_MIN_WIDTH);
|
|
210
|
+
const floored = Math.max(base, minWidth ?? 0);
|
|
211
|
+
return maxWidth === undefined ? floored : Math.min(floored, maxWidth);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The group's next value when a radio row is chosen. A radio never clears, so re-choosing keeps it. */
|
|
215
|
+
export function resolveRadioNext(_current: string | undefined, value: string): string {
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The kinds of row a menu has. */
|
|
220
|
+
export type MenuRowKind = "item" | "checkbox" | "radio" | "sub-trigger";
|
|
221
|
+
|
|
222
|
+
const CLOSE_ON_SELECT: Record<MenuRowKind, boolean> = {
|
|
223
|
+
checkbox: false,
|
|
224
|
+
item: true,
|
|
225
|
+
radio: true,
|
|
226
|
+
"sub-trigger": false,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Whether choosing a row closes the menu: an item and a radio do, a checkbox does
|
|
231
|
+
* not (several are usually toggled in one visit), and a sub-trigger only expands.
|
|
232
|
+
* An explicit `isClosedOnSelect` wins.
|
|
233
|
+
*/
|
|
234
|
+
export function resolveCloseOnSelect(kind: MenuRowKind, explicit?: boolean): boolean {
|
|
235
|
+
return explicit ?? CLOSE_ON_SELECT[kind];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type MenuItemAccessibilityInput =
|
|
239
|
+
| { kind: "item"; isDisabled: boolean }
|
|
240
|
+
| { kind: "checkbox"; isDisabled: boolean; isChecked: boolean }
|
|
241
|
+
| { kind: "radio"; isDisabled: boolean; isSelected: boolean }
|
|
242
|
+
| { kind: "sub-trigger"; isDisabled: boolean; isExpanded: boolean };
|
|
243
|
+
|
|
244
|
+
export type MenuItemAccessibilityState = {
|
|
245
|
+
disabled: boolean;
|
|
246
|
+
checked?: boolean;
|
|
247
|
+
selected?: boolean;
|
|
248
|
+
expanded?: boolean;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** The `accessibilityState` a row announces — one source, so the matrix is testable. */
|
|
252
|
+
export function resolveMenuItemAccessibility(input: MenuItemAccessibilityInput): MenuItemAccessibilityState {
|
|
253
|
+
switch (input.kind) {
|
|
254
|
+
case "item":
|
|
255
|
+
return { disabled: input.isDisabled };
|
|
256
|
+
case "checkbox":
|
|
257
|
+
return { checked: input.isChecked, disabled: input.isDisabled };
|
|
258
|
+
case "radio":
|
|
259
|
+
return { disabled: input.isDisabled, selected: input.isSelected };
|
|
260
|
+
case "sub-trigger":
|
|
261
|
+
return { disabled: input.isDisabled, expanded: input.isExpanded };
|
|
262
|
+
default:
|
|
263
|
+
return { disabled: false };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Styling for every part of a menu.
|
|
269
|
+
*
|
|
270
|
+
* One slotted `tv()`, because the parts cannot import each other without closing
|
|
271
|
+
* a cycle (AGENTS.md rule 3) and all of them read the same row axes.
|
|
272
|
+
*
|
|
273
|
+
* **`background` is `bg-popover`, never `bg-overlay`.** `overlay` is the
|
|
274
|
+
* translucent scrim colour, and a panel painted in it is a see-through card.
|
|
275
|
+
* The scrim slot wears it instead.
|
|
276
|
+
*
|
|
277
|
+
* **`itemFill` is the row's pressed background**, an absolute layer whose
|
|
278
|
+
* opacity the press drives. There is no wash on top: the fill is the feedback.
|
|
279
|
+
*
|
|
280
|
+
* **The disabled fade lands on `item`**, a plain `Animated.View` that owns only
|
|
281
|
+
* `transform`, so a class opacity there is not overwritten.
|
|
282
|
+
*
|
|
283
|
+
* No slot worn by a `View` carries `text-*` (rule 1). Free of React Native
|
|
284
|
+
* imports so it stays reachable from `bun test`.
|
|
285
|
+
*/
|
|
286
|
+
export const menuVariants = tv({
|
|
287
|
+
slots: {
|
|
288
|
+
/**
|
|
289
|
+
* The positioned, animated layer. Casts the panel's shadow, so it carries no
|
|
290
|
+
* `overflow-hidden` — iOS clips a shadow to a clipping view's own bounds.
|
|
291
|
+
*/
|
|
292
|
+
panel: "absolute shadow-lg",
|
|
293
|
+
/** The panel's frame: corner, hairline and clip. Its surface is `background`. */
|
|
294
|
+
content: "overflow-hidden rounded-lg border border-border",
|
|
295
|
+
/** The panel's surface, drawn behind the scroller. */
|
|
296
|
+
background: "absolute inset-0 bg-popover",
|
|
297
|
+
/** The full-screen tint behind the panel, when `hasScrim`. */
|
|
298
|
+
scrim: "absolute inset-0 bg-overlay",
|
|
299
|
+
/** What `backdrop` is drawn in — full-screen, over the scrim, behind the panel. */
|
|
300
|
+
backdrop: "absolute inset-0",
|
|
301
|
+
/** The scroller's padding, around the rows. */
|
|
302
|
+
scroll: "p-1",
|
|
303
|
+
/** One row. */
|
|
304
|
+
item: "min-h-11 flex-row items-center gap-3 rounded-md px-3 py-2",
|
|
305
|
+
/** The row's pressed fill. Its opacity is an animated style. */
|
|
306
|
+
itemFill: "absolute inset-0 rounded-md bg-accent",
|
|
307
|
+
/** The label-and-description column. */
|
|
308
|
+
itemContent: "min-w-0 flex-1 justify-center gap-0.5",
|
|
309
|
+
itemLabel: "text-popover-foreground text-sm",
|
|
310
|
+
itemDescription: "text-muted-foreground text-xs",
|
|
311
|
+
itemShortcut: "ms-auto text-muted-foreground text-xs",
|
|
312
|
+
/** Edge length of a row's glyph — 18pt, the reserved icon column. */
|
|
313
|
+
itemIcon: "size-icon-md",
|
|
314
|
+
/** The section heading. */
|
|
315
|
+
label: "px-3 pt-2 pb-1 font-medium text-muted-foreground text-xs",
|
|
316
|
+
/** The box an indicator (check, dot, chevron) sits in — the icon column's footprint. */
|
|
317
|
+
indicator: "size-icon-md items-center justify-center",
|
|
318
|
+
/** The radio `dot` indicator. */
|
|
319
|
+
indicatorDot: "size-1.5 rounded-full bg-popover-foreground",
|
|
320
|
+
/** The rows a submenu discloses, stepped in from the trigger. */
|
|
321
|
+
subContent: "ps-3",
|
|
322
|
+
/** The submenu's measured clip. Its height is an animated style. */
|
|
323
|
+
subClip: "overflow-hidden",
|
|
324
|
+
/** The submenu's measured inner layer — out of flow so it measures its content alone. */
|
|
325
|
+
subInner: "absolute top-0 right-0 left-0",
|
|
326
|
+
/** The separator's spacing. */
|
|
327
|
+
separator: "my-1",
|
|
328
|
+
},
|
|
329
|
+
variants: {
|
|
330
|
+
variant: {
|
|
331
|
+
default: {},
|
|
332
|
+
destructive: {
|
|
333
|
+
itemFill: "bg-destructive-soft",
|
|
334
|
+
itemLabel: "text-destructive",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
isInset: {
|
|
338
|
+
true: { label: "ps-[42px]" },
|
|
339
|
+
false: {},
|
|
340
|
+
},
|
|
341
|
+
isDisabled: {
|
|
342
|
+
true: { item: "opacity-50" },
|
|
343
|
+
false: {},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
defaultVariants: {
|
|
347
|
+
variant: "default",
|
|
348
|
+
isInset: false,
|
|
349
|
+
isDisabled: false,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
export type MenuVariantProps = VariantProps<typeof menuVariants>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Meter
|
|
2
|
+
|
|
3
|
+
A measurement on a fixed scale, coloured by where it falls — storage used, a
|
|
4
|
+
battery's charge, a password's strength. Built on `Progress`: the same track, fill,
|
|
5
|
+
header and label, with a root that clamps the reading, judges it, and paints the
|
|
6
|
+
fill that judgement's colour. Adds `Meter.Segments` for a scale drawn as whole
|
|
7
|
+
blocks.
|
|
8
|
+
|
|
9
|
+
`import { Meter } from "@delacour/react-native-ui/meter";`
|
|
10
|
+
|
|
11
|
+
## Files
|
|
12
|
+
|
|
13
|
+
| File | What it holds |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `index.ts` | → `@delacour/react-native-ui/meter` |
|
|
16
|
+
| `meter.tsx` | Root + the `Object.assign` compound surface; publishes both contexts |
|
|
17
|
+
| `meter-header.tsx` | `Meter.Header` — `Progress.Header`, named for the meter |
|
|
18
|
+
| `meter-label.tsx` | `Meter.Label` — `Progress.Label`, named for the meter |
|
|
19
|
+
| `meter-output.tsx` | `Meter.Output`, the readout, whose function child is handed the region |
|
|
20
|
+
| `meter-track.tsx` | `Meter.Track` — `Progress.Track`, named for the meter |
|
|
21
|
+
| `meter-fill.tsx` | `Meter.Fill` — `Progress.Fill`, named for the meter |
|
|
22
|
+
| `meter-segments.tsx` | `Meter.Segments`, the blocks, and the internal `Meter.Segments.Segment` |
|
|
23
|
+
| `meter.context.tsx` | `MeterContext`, `useMeter()`, `useMeterContext()`, `useMeterPart()` |
|
|
24
|
+
| `meter.types.ts` | `MeterRenderProps`, shared by the context and the readout |
|
|
25
|
+
| `meter.variants.ts` | The blocks' `tv()` slots + the scale, region, threshold and segment resolvers |
|
|
26
|
+
| `meter.variants.test.ts` | |
|
|
27
|
+
|
|
28
|
+
## Built on Progress
|
|
29
|
+
|
|
30
|
+
- **The root publishes `ProgressContextValue` as well as its own context.**
|
|
31
|
+
`Progress.Track` and `Progress.Fill` read only `ratio`, `isIndeterminate`,
|
|
32
|
+
`color`, `size` and `trackSize` — the progress bar was written to be reused
|
|
33
|
+
this way — so `Meter.Track` and `Meter.Fill` *are* those parts, and the fill's
|
|
34
|
+
UI-thread transform, its seeded first frame and its hide-until-measured come
|
|
35
|
+
along without a line restated. `isIndeterminate` is always `false`.
|
|
36
|
+
- **Each reused part is a thin wrapper, not the progress part assigned
|
|
37
|
+
directly.** The wrapper calls `useMeterPart` first, so a `Meter.Fill` outside
|
|
38
|
+
a meter throws naming `Meter.Fill` rather than `Progress.Fill`, and DevTools
|
|
39
|
+
shows `DelacourUI.Meter.Fill` (rule 12).
|
|
40
|
+
- **The parts come through `../progress`, the index; the resolvers through
|
|
41
|
+
`../progress/progress.variants`, the leaf** (rule 3). Progress imports nothing
|
|
42
|
+
from here, so there is no cycle, and the registry records `progress` as a
|
|
43
|
+
dependency of `meter`.
|
|
44
|
+
- **Colours and sizes are the progress bar's tuples, re-exported, not
|
|
45
|
+
restated.** A test pins them equal, and another pins a block's height and
|
|
46
|
+
paint to the progress track's and fill's, so a segmented meter and a
|
|
47
|
+
continuous one in one card read as one instrument.
|
|
48
|
+
|
|
49
|
+
## Judging the reading
|
|
50
|
+
|
|
51
|
+
- **Two ways, never both, and the prop type says so.** `MeterProps` is a union:
|
|
52
|
+
`low` / `high` / `optimum` *or* `thresholds`. Passing both is a type error
|
|
53
|
+
rather than a precedence rule. `resolveMeterScale` turns the props into a
|
|
54
|
+
`MeterScale` — `plain`, `regions` or `thresholds` — and `resolveMeterColor`
|
|
55
|
+
switches on its `kind`.
|
|
56
|
+
- **Regions follow a gauge's three-part model.** `low` and `high` default to the
|
|
57
|
+
scale's ends and `optimum` to its middle; each is clamped into the scale and
|
|
58
|
+
`high` is never below `low`, so a boundary written the wrong way round is
|
|
59
|
+
pulled into order rather than inverting every region. `optimum` below `low`
|
|
60
|
+
makes the bottom good (a disk), above `high` the top (a battery), between them
|
|
61
|
+
the band (a temperature). The regions paint `success`, `warning`,
|
|
62
|
+
`destructive` — `METER_REGION_COLORS`, fixed, so a region reads the same in
|
|
63
|
+
every meter in an app — and `color` is ignored.
|
|
64
|
+
- **Thresholds: the highest `from` reached wins, in any listed order.** `from`
|
|
65
|
+
is in the reading's units, not a percentage — a threshold on a 0–256 GB meter
|
|
66
|
+
is written in gigabytes. A non-finite `from` is ignored. Below every threshold
|
|
67
|
+
`color` applies.
|
|
68
|
+
- **The clamped reading is what is judged.** A disk over its capacity is
|
|
69
|
+
`critical`, not off the scale.
|
|
70
|
+
- **Colour is never the only signal, and `valueLabel` takes a function for it.**
|
|
71
|
+
Nothing speaks the region on its own — the words are domain-specific ("Almost
|
|
72
|
+
full", "Weak") — so `valueLabel` may be a function handed the judged
|
|
73
|
+
`MeterReading`, and `resolveMeterValueLabel` calls it. A `Meter.Output`
|
|
74
|
+
function reading `region` is **not** enough: the output is hidden from
|
|
75
|
+
assistive technology, so its words are seen and never heard. The simulator
|
|
76
|
+
pass caught exactly that — the regions demo drew "Filling up · 82%" while
|
|
77
|
+
VoiceOver's value was "82%".
|
|
78
|
+
|
|
79
|
+
## The scale
|
|
80
|
+
|
|
81
|
+
- **`meterRange` makes the scale finite and ordered.** A non-finite bound takes
|
|
82
|
+
0 or 100; an inverted or empty scale collapses at its floor and reads empty.
|
|
83
|
+
`progressRatio` would already return 0 for it, but the accessibility value
|
|
84
|
+
needs `min ≤ now ≤ max` too.
|
|
85
|
+
- **`meterValue` clamps to the ends; `NaN` reads at the floor and an infinity at
|
|
86
|
+
the end it points to.** `progressRatio` alone returns 0 for `+Infinity`,
|
|
87
|
+
which would draw a full disk as empty.
|
|
88
|
+
- **The formatted readout uses the clamped reading**, so a non-percent format
|
|
89
|
+
never prints `300 GB` on a 256 GB scale.
|
|
90
|
+
|
|
91
|
+
## Segments
|
|
92
|
+
|
|
93
|
+
- **Whole blocks only.** `litSegments` floors, so four blocks say "three out of
|
|
94
|
+
four" where a bar says "about seventy percent" — a password is not seventy
|
|
95
|
+
percent strong. A small epsilon absorbs floating-point error, so `0.7 × 10`
|
|
96
|
+
lights seven.
|
|
97
|
+
- **Any reading above the floor lights at least one block.** Rounding down would
|
|
98
|
+
leave the first quarter of a four-block meter dark, and "a little" looking like
|
|
99
|
+
"none" is the reading a meter can least afford to get wrong.
|
|
100
|
+
- **`meterSegmentCount` rounds a fractional count down, clamps above
|
|
101
|
+
`METER_MAX_SEGMENTS` (100), and falls back to the continuous bar** for a count
|
|
102
|
+
under one or not finite, rather than drawing nothing.
|
|
103
|
+
- **A block's lit colour is a layer that fades its opacity**, not the block's own
|
|
104
|
+
background: an opacity animates on the UI thread, where a background would need
|
|
105
|
+
two token values interpolated in JavaScript. Each layer is seeded at its
|
|
106
|
+
current state, so a list of meters does not replay the lighting as rows mount,
|
|
107
|
+
and the timing keeps Reanimated's default reduce-motion policy — with the system
|
|
108
|
+
setting on, a block lights without a fade. Unlike the progress bar's loop,
|
|
109
|
+
nothing here has to keep moving to be read.
|
|
110
|
+
- **`segments` lives on the root, not on `Meter.Segments`.** The lit count is
|
|
111
|
+
part of the render props a readout reads, so the root has to know it.
|
|
112
|
+
`Meter.Segments` without a count renders nothing; `Meter.Track` with one still
|
|
113
|
+
draws the continuous bar.
|
|
114
|
+
|
|
115
|
+
## What it refuses
|
|
116
|
+
|
|
117
|
+
- **No `isDisabled`, `isInvalid`, `Field` or indeterminate state.** A meter
|
|
118
|
+
reports a reading and accepts no input; `resolveMeterAxes` takes one argument
|
|
119
|
+
and a test pins that. A reading that is not known yet is `Progress`'s
|
|
120
|
+
indeterminate loop, not a meter.
|
|
121
|
+
- **No `label` / `showValueLabel` props.** The anatomy is written out, as
|
|
122
|
+
`Progress`'s is — `Meter.Header` with a `Meter.Label` and a `Meter.Output` — and
|
|
123
|
+
a bare `<Meter value={68} />` still draws the scale, because a compound root
|
|
124
|
+
with no default children renders an empty `View` with no error.
|
|
125
|
+
|
|
126
|
+
## Accessibility
|
|
127
|
+
|
|
128
|
+
- **The root is the one accessible element**, `accessibilityRole="progressbar"`
|
|
129
|
+
— React Native has no meter role, and this is the one that publishes a range
|
|
130
|
+
and a value on both platforms. `resolveProgressAccessibility` decides the
|
|
131
|
+
spoken text: the percentage over 0–100, `168 of 256` otherwise, or
|
|
132
|
+
`valueLabel`. Thresholds, regions and segments never change it.
|
|
133
|
+
- **`Meter.Label` is the name, `Meter.Output` is hidden** — the root already
|
|
134
|
+
speaks the value, so a visible readout would be read twice. A meter with no
|
|
135
|
+
label needs an `accessibilityLabel`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export { Meter, type MeterProps } from "./meter";
|
|
2
|
+
export { type MeterContextValue, MeterProvider, useMeter, useMeterContext } from "./meter.context";
|
|
3
|
+
export type { MeterRenderChildren, MeterRenderProps } from "./meter.types";
|
|
4
|
+
export {
|
|
5
|
+
litSegments,
|
|
6
|
+
METER_COLORS,
|
|
7
|
+
METER_DEFAULT_COLOR,
|
|
8
|
+
METER_DEFAULT_SIZE,
|
|
9
|
+
METER_MAX_SEGMENTS,
|
|
10
|
+
METER_MAX_VALUE,
|
|
11
|
+
METER_MIN_VALUE,
|
|
12
|
+
METER_REGION_COLORS,
|
|
13
|
+
METER_REGIONS,
|
|
14
|
+
METER_SEGMENT_FADE_MS,
|
|
15
|
+
METER_SIZES,
|
|
16
|
+
type MeterAxes,
|
|
17
|
+
type MeterColor,
|
|
18
|
+
type MeterOwnAxes,
|
|
19
|
+
type MeterReading,
|
|
20
|
+
type MeterRegion,
|
|
21
|
+
type MeterScale,
|
|
22
|
+
type MeterScaleInput,
|
|
23
|
+
type MeterSize,
|
|
24
|
+
type MeterThreshold,
|
|
25
|
+
type MeterValueLabel,
|
|
26
|
+
type MeterVariantProps,
|
|
27
|
+
meterRange,
|
|
28
|
+
meterSegmentCount,
|
|
29
|
+
meterValue,
|
|
30
|
+
meterVariants,
|
|
31
|
+
resolveMeterAxes,
|
|
32
|
+
resolveMeterColor,
|
|
33
|
+
resolveMeterRegion,
|
|
34
|
+
resolveMeterScale,
|
|
35
|
+
resolveMeterValueLabel,
|
|
36
|
+
thresholdColor,
|
|
37
|
+
} from "./meter.variants";
|
|
38
|
+
export type { MeterFillProps } from "./meter-fill";
|
|
39
|
+
export type { MeterHeaderProps } from "./meter-header";
|
|
40
|
+
export type { MeterLabelProps } from "./meter-label";
|
|
41
|
+
export type { MeterOutputProps } from "./meter-output";
|
|
42
|
+
export type { MeterSegmentsProps } from "./meter-segments";
|
|
43
|
+
export type { MeterTrackProps } from "./meter-track";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Progress, type ProgressFillProps } from "../progress";
|
|
3
|
+
import { useMeterPart } from "./meter.context";
|
|
4
|
+
|
|
5
|
+
export type MeterFillProps = ProgressFillProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The painted bar, in the colour the reading was judged to be.
|
|
9
|
+
*
|
|
10
|
+
* The progress bar's own fill: a transform on the UI thread, one timing per new
|
|
11
|
+
* reading, landing on the value without travel under the system's reduce-motion
|
|
12
|
+
* setting. A change of colour is a class, so it lands on the render that judged
|
|
13
|
+
* it.
|
|
14
|
+
*/
|
|
15
|
+
export function MeterFill(props: MeterFillProps): ReactElement {
|
|
16
|
+
useMeterPart("Meter.Fill");
|
|
17
|
+
return <Progress.Fill {...props} />;
|
|
18
|
+
}
|
|
19
|
+
MeterFill.displayName = "DelacourUI.Meter.Fill";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Progress, type ProgressHeaderProps } from "../progress";
|
|
3
|
+
import { useMeterPart } from "./meter.context";
|
|
4
|
+
|
|
5
|
+
export type MeterHeaderProps = ProgressHeaderProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The row above the track: a label at the start, a readout at the end.
|
|
9
|
+
*
|
|
10
|
+
* The progress bar's own header — the meter publishes that bar's context, so the
|
|
11
|
+
* row, its gap and its type step are shared rather than restated.
|
|
12
|
+
*/
|
|
13
|
+
export function MeterHeader(props: MeterHeaderProps): ReactElement {
|
|
14
|
+
useMeterPart("Meter.Header");
|
|
15
|
+
return <Progress.Header {...props} />;
|
|
16
|
+
}
|
|
17
|
+
MeterHeader.displayName = "DelacourUI.Meter.Header";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Progress, type ProgressLabelProps } from "../progress";
|
|
3
|
+
import { useMeterPart } from "./meter.context";
|
|
4
|
+
|
|
5
|
+
export type MeterLabelProps = ProgressLabelProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What is being measured — "Storage", "Battery".
|
|
9
|
+
*
|
|
10
|
+
* Left visible to assistive technology: the root is one accessible element, and
|
|
11
|
+
* a label inside it is what VoiceOver and TalkBack read as that element's name.
|
|
12
|
+
* A meter without one announces a number with nothing attached to it.
|
|
13
|
+
*/
|
|
14
|
+
export function MeterLabel(props: MeterLabelProps): ReactElement {
|
|
15
|
+
useMeterPart("Meter.Label");
|
|
16
|
+
return <Progress.Label {...props} />;
|
|
17
|
+
}
|
|
18
|
+
MeterLabel.displayName = "DelacourUI.Meter.Label";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { PROGRESS_OUTPUT_TEXT_SIZE, progressVariants } from "../progress/progress.variants";
|
|
3
|
+
import { Text, type TextPresetProps } from "../text";
|
|
4
|
+
import { useMeterPart } from "./meter.context";
|
|
5
|
+
import type { MeterRenderChildren } from "./meter.types";
|
|
6
|
+
|
|
7
|
+
export type MeterOutputProps = Omit<TextPresetProps, "children"> & {
|
|
8
|
+
/** Custom content, or a function called with the meter's settled state. */
|
|
9
|
+
children?: MeterRenderChildren;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The reading, formatted — `68%` by default, or `valueLabel` when one is given.
|
|
14
|
+
*
|
|
15
|
+
* A function child is handed the meter's state, including the `region` it was
|
|
16
|
+
* judged to be in. Words it draws are seen but not heard — see below — so a
|
|
17
|
+
* judgement a screen reader must hear belongs in a `valueLabel` function.
|
|
18
|
+
*
|
|
19
|
+
* **Hidden from assistive technology on purpose.** The root already publishes
|
|
20
|
+
* the reading as `accessibilityValue`, so a readout left visible inside it would
|
|
21
|
+
* be read twice.
|
|
22
|
+
*/
|
|
23
|
+
export function MeterOutput({ children, className, size, ...props }: MeterOutputProps): ReactElement | null {
|
|
24
|
+
const { size: meterSize, renderProps } = useMeterPart("Meter.Output");
|
|
25
|
+
|
|
26
|
+
const content = typeof children === "function" ? children(renderProps) : (children ?? renderProps.formatted);
|
|
27
|
+
|
|
28
|
+
if (content === null || content === undefined) return null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Text.Label
|
|
32
|
+
accessibilityElementsHidden
|
|
33
|
+
className={progressVariants({ size: meterSize }).output({ className })}
|
|
34
|
+
importantForAccessibility="no-hide-descendants"
|
|
35
|
+
size={size ?? PROGRESS_OUTPUT_TEXT_SIZE[meterSize]}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{content}
|
|
39
|
+
</Text.Label>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
MeterOutput.displayName = "DelacourUI.Meter.Output";
|