@delacour/react-native-ui 0.1.0-alpha.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +48 -4
- package/src/components/accordion/AGENTS.md +21 -9
- package/src/components/accordion/accordion-content.tsx +7 -4
- package/src/components/accordion/accordion-item.tsx +15 -13
- package/src/components/accordion/accordion.context.tsx +11 -5
- package/src/components/accordion/accordion.variants.test.ts +64 -3
- package/src/components/accordion/accordion.variants.ts +33 -9
- package/src/components/alert/AGENTS.md +102 -0
- package/src/components/alert/alert-action.tsx +17 -0
- package/src/components/alert/alert-close-button.tsx +50 -0
- package/src/components/alert/alert-content.tsx +15 -0
- package/src/components/alert/alert-description.tsx +16 -0
- package/src/components/alert/alert-glyphs.ts +25 -0
- package/src/components/alert/alert-indicator.tsx +40 -0
- package/src/components/alert/alert-title.tsx +18 -0
- package/src/components/alert/alert.context.tsx +69 -0
- package/src/components/alert/alert.tsx +160 -0
- package/src/components/alert/alert.types.ts +10 -0
- package/src/components/alert/alert.variants.test.ts +218 -0
- package/src/components/alert/alert.variants.ts +180 -0
- package/src/components/alert/index.ts +23 -0
- package/src/components/avatar/AGENTS.md +106 -0
- package/src/components/avatar/avatar-badge.tsx +51 -0
- package/src/components/avatar/avatar-group.tsx +139 -0
- package/src/components/avatar/avatar.context.tsx +93 -0
- package/src/components/avatar/avatar.tsx +201 -0
- package/src/components/avatar/avatar.utils.test.ts +174 -0
- package/src/components/avatar/avatar.utils.ts +141 -0
- package/src/components/avatar/avatar.variants.test.ts +251 -0
- package/src/components/avatar/avatar.variants.ts +270 -0
- package/src/components/avatar/index.ts +39 -0
- package/src/components/badge/AGENTS.md +5 -0
- package/src/components/badge/badge.tsx +6 -1
- package/src/components/bottom-sheet/AGENTS.md +186 -231
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
- package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
- package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
- package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
- package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
- package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
- package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
- package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
- package/src/components/bottom-sheet/index.ts +39 -17
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
- package/src/components/button/AGENTS.md +45 -11
- package/src/components/button/button-group.tsx +44 -8
- package/src/components/button/button.context.tsx +10 -0
- package/src/components/button/button.tsx +2 -1
- package/src/components/button/button.variants.test.ts +154 -0
- package/src/components/button/button.variants.ts +71 -6
- package/src/components/button/index.ts +2 -0
- package/src/components/calendar/AGENTS.md +142 -0
- package/src/components/calendar/calendar-caption.tsx +116 -0
- package/src/components/calendar/calendar-day.tsx +103 -0
- package/src/components/calendar/calendar-grid.tsx +183 -0
- package/src/components/calendar/calendar-header.tsx +34 -0
- package/src/components/calendar/calendar-nav.tsx +51 -0
- package/src/components/calendar/calendar-picker.tsx +128 -0
- package/src/components/calendar/calendar-weekdays.tsx +48 -0
- package/src/components/calendar/calendar.context.tsx +108 -0
- package/src/components/calendar/calendar.date.test.ts +456 -0
- package/src/components/calendar/calendar.date.ts +584 -0
- package/src/components/calendar/calendar.tsx +389 -0
- package/src/components/calendar/calendar.types.ts +87 -0
- package/src/components/calendar/calendar.variants.test.ts +363 -0
- package/src/components/calendar/calendar.variants.ts +426 -0
- package/src/components/calendar/index.ts +96 -0
- package/src/components/card/AGENTS.md +88 -0
- package/src/components/card/card-action.tsx +16 -0
- package/src/components/card/card-content.tsx +12 -0
- package/src/components/card/card-description.tsx +12 -0
- package/src/components/card/card-footer.tsx +30 -0
- package/src/components/card/card-header.tsx +45 -0
- package/src/components/card/card-title.tsx +25 -0
- package/src/components/card/card.context.tsx +64 -0
- package/src/components/card/card.tsx +94 -0
- package/src/components/card/card.types.ts +13 -0
- package/src/components/card/card.variants.test.ts +240 -0
- package/src/components/card/card.variants.ts +177 -0
- package/src/components/card/index.ts +15 -0
- package/src/components/chart/AGENTS.md +8 -0
- package/src/components/chart/chart.tsx +8 -1
- package/src/components/chip/AGENTS.md +113 -0
- package/src/components/chip/chip-close-button.tsx +48 -0
- package/src/components/chip/chip-end-content.tsx +15 -0
- package/src/components/chip/chip-label.tsx +21 -0
- package/src/components/chip/chip-start-content.tsx +16 -0
- package/src/components/chip/chip.context.tsx +66 -0
- package/src/components/chip/chip.tsx +268 -0
- package/src/components/chip/chip.types.ts +10 -0
- package/src/components/chip/chip.variants.test.ts +335 -0
- package/src/components/chip/chip.variants.ts +321 -0
- package/src/components/chip/index.ts +24 -0
- package/src/components/collapsible/AGENTS.md +97 -0
- package/src/components/collapsible/collapsible-content.tsx +97 -0
- package/src/components/collapsible/collapsible-description.tsx +17 -0
- package/src/components/collapsible/collapsible-indicator.tsx +92 -0
- package/src/components/collapsible/collapsible-title.tsx +17 -0
- package/src/components/collapsible/collapsible-trigger.tsx +133 -0
- package/src/components/collapsible/collapsible.context.tsx +91 -0
- package/src/components/collapsible/collapsible.tsx +167 -0
- package/src/components/collapsible/collapsible.types.ts +9 -0
- package/src/components/collapsible/collapsible.variants.test.ts +338 -0
- package/src/components/collapsible/collapsible.variants.ts +228 -0
- package/src/components/collapsible/index.ts +32 -0
- package/src/components/context-menu/AGENTS.md +103 -0
- package/src/components/context-menu/context-menu-content.tsx +64 -0
- package/src/components/context-menu/context-menu-item.tsx +22 -0
- package/src/components/context-menu/context-menu-preview.tsx +60 -0
- package/src/components/context-menu/context-menu-trigger.tsx +160 -0
- package/src/components/context-menu/context-menu.context.tsx +91 -0
- package/src/components/context-menu/context-menu.tsx +90 -0
- package/src/components/context-menu/context-menu.variants.test.ts +266 -0
- package/src/components/context-menu/context-menu.variants.ts +147 -0
- package/src/components/context-menu/index.ts +18 -0
- package/src/components/dialog/AGENTS.md +97 -0
- package/src/components/dialog/dialog-body.tsx +21 -0
- package/src/components/dialog/dialog-close.tsx +82 -0
- package/src/components/dialog/dialog-content.tsx +153 -0
- package/src/components/dialog/dialog-description.tsx +23 -0
- package/src/components/dialog/dialog-footer.tsx +32 -0
- package/src/components/dialog/dialog-header.tsx +20 -0
- package/src/components/dialog/dialog-title.tsx +42 -0
- package/src/components/dialog/dialog-trigger.tsx +54 -0
- package/src/components/dialog/dialog.context.tsx +90 -0
- package/src/components/dialog/dialog.tsx +119 -0
- package/src/components/dialog/dialog.variants.test.ts +224 -0
- package/src/components/dialog/dialog.variants.ts +173 -0
- package/src/components/dialog/index.ts +30 -0
- package/src/components/drawer/AGENTS.md +123 -0
- package/src/components/drawer/drawer-body.tsx +46 -0
- package/src/components/drawer/drawer-close.tsx +76 -0
- package/src/components/drawer/drawer-content.tsx +186 -0
- package/src/components/drawer/drawer-description.tsx +23 -0
- package/src/components/drawer/drawer-footer.tsx +21 -0
- package/src/components/drawer/drawer-header.tsx +42 -0
- package/src/components/drawer/drawer-title.tsx +35 -0
- package/src/components/drawer/drawer-trigger.tsx +54 -0
- package/src/components/drawer/drawer.context.tsx +114 -0
- package/src/components/drawer/drawer.tsx +119 -0
- package/src/components/drawer/drawer.variants.test.ts +392 -0
- package/src/components/drawer/drawer.variants.ts +244 -0
- package/src/components/drawer/index.ts +41 -0
- package/src/components/drawer/use-drawer-pan.ts +108 -0
- package/src/components/empty-state/AGENTS.md +90 -0
- package/src/components/empty-state/empty-state-content.tsx +17 -0
- package/src/components/empty-state/empty-state-description.tsx +12 -0
- package/src/components/empty-state/empty-state-header.tsx +17 -0
- package/src/components/empty-state/empty-state-media.tsx +56 -0
- package/src/components/empty-state/empty-state-title.tsx +18 -0
- package/src/components/empty-state/empty-state.context.tsx +65 -0
- package/src/components/empty-state/empty-state.tsx +84 -0
- package/src/components/empty-state/empty-state.types.ts +14 -0
- package/src/components/empty-state/empty-state.variants.test.ts +245 -0
- package/src/components/empty-state/empty-state.variants.ts +132 -0
- package/src/components/empty-state/index.ts +20 -0
- package/src/components/fab/AGENTS.md +119 -0
- package/src/components/fab/fab-action.tsx +118 -0
- package/src/components/fab/fab-group.tsx +214 -0
- package/src/components/fab/fab-label.tsx +18 -0
- package/src/components/fab/fab.context.tsx +125 -0
- package/src/components/fab/fab.tsx +156 -0
- package/src/components/fab/fab.types.ts +17 -0
- package/src/components/fab/fab.variants.test.ts +274 -0
- package/src/components/fab/fab.variants.ts +234 -0
- package/src/components/fab/index.ts +37 -0
- package/src/components/feedback/AGENTS.md +95 -0
- package/src/components/feedback/feedback-action.tsx +16 -0
- package/src/components/feedback/feedback-cancel.tsx +34 -0
- package/src/components/feedback/feedback-close.tsx +21 -0
- package/src/components/feedback/feedback-content.tsx +24 -0
- package/src/components/feedback/feedback-field.tsx +70 -0
- package/src/components/feedback/feedback-footer.tsx +21 -0
- package/src/components/feedback/feedback-panel.tsx +54 -0
- package/src/components/feedback/feedback-submit.tsx +74 -0
- package/src/components/feedback/feedback-title.tsx +34 -0
- package/src/components/feedback/feedback-trigger.tsx +20 -0
- package/src/components/feedback/feedback.context.tsx +73 -0
- package/src/components/feedback/feedback.tsx +135 -0
- package/src/components/feedback/feedback.variants.test.ts +185 -0
- package/src/components/feedback/feedback.variants.ts +128 -0
- package/src/components/feedback/index.ts +24 -0
- package/src/components/field/AGENTS.md +19 -0
- package/src/components/field/field-label.tsx +19 -5
- package/src/components/field/field.context.tsx +17 -0
- package/src/components/field/field.tsx +15 -2
- package/src/components/field/field.variants.test.ts +24 -0
- package/src/components/field/field.variants.ts +20 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/item/AGENTS.md +92 -0
- package/src/components/item/index.ts +25 -0
- package/src/components/item/item-actions.tsx +28 -0
- package/src/components/item/item-content.tsx +15 -0
- package/src/components/item/item-description.tsx +12 -0
- package/src/components/item/item-footer.tsx +13 -0
- package/src/components/item/item-group.tsx +32 -0
- package/src/components/item/item-header.tsx +13 -0
- package/src/components/item/item-media.tsx +37 -0
- package/src/components/item/item-separator.tsx +13 -0
- package/src/components/item/item-title.tsx +17 -0
- package/src/components/item/item.context.tsx +66 -0
- package/src/components/item/item.tsx +218 -0
- package/src/components/item/item.types.ts +12 -0
- package/src/components/item/item.variants.test.ts +263 -0
- package/src/components/item/item.variants.ts +200 -0
- package/src/components/kpi/AGENTS.md +125 -0
- package/src/components/kpi/index.ts +45 -0
- package/src/components/kpi/kpi-action.tsx +12 -0
- package/src/components/kpi/kpi-content.tsx +35 -0
- package/src/components/kpi/kpi-footer.tsx +52 -0
- package/src/components/kpi/kpi-group.tsx +70 -0
- package/src/components/kpi/kpi-header.tsx +25 -0
- package/src/components/kpi/kpi-icon.tsx +28 -0
- package/src/components/kpi/kpi-sparkline.tsx +145 -0
- package/src/components/kpi/kpi-stat.tsx +20 -0
- package/src/components/kpi/kpi-title.tsx +19 -0
- package/src/components/kpi/kpi-trend.tsx +105 -0
- package/src/components/kpi/kpi-value.tsx +32 -0
- package/src/components/kpi/kpi.context.tsx +111 -0
- package/src/components/kpi/kpi.tsx +154 -0
- package/src/components/kpi/kpi.types.ts +9 -0
- package/src/components/kpi/kpi.variants.test.ts +428 -0
- package/src/components/kpi/kpi.variants.ts +347 -0
- package/src/components/label/AGENTS.md +89 -0
- package/src/components/label/index.ts +10 -0
- package/src/components/label/label.tsx +97 -0
- package/src/components/label/label.variants.test.ts +176 -0
- package/src/components/label/label.variants.ts +123 -0
- package/src/components/menu/AGENTS.md +176 -0
- package/src/components/menu/index.ts +44 -0
- package/src/components/menu/menu-background.tsx +17 -0
- package/src/components/menu/menu-checkbox-item.tsx +57 -0
- package/src/components/menu/menu-content.tsx +303 -0
- package/src/components/menu/menu-item.tsx +162 -0
- package/src/components/menu/menu-label.tsx +14 -0
- package/src/components/menu/menu-radio-group.tsx +50 -0
- package/src/components/menu/menu-radio-item.tsx +57 -0
- package/src/components/menu/menu-separator.tsx +11 -0
- package/src/components/menu/menu-sub-content.tsx +72 -0
- package/src/components/menu/menu-sub-trigger.tsx +50 -0
- package/src/components/menu/menu-sub.tsx +75 -0
- package/src/components/menu/menu-trigger.tsx +57 -0
- package/src/components/menu/menu.context.tsx +214 -0
- package/src/components/menu/menu.tsx +88 -0
- package/src/components/menu/menu.types.ts +33 -0
- package/src/components/menu/menu.variants.test.ts +400 -0
- package/src/components/menu/menu.variants.ts +353 -0
- package/src/components/meter/AGENTS.md +135 -0
- package/src/components/meter/index.ts +43 -0
- package/src/components/meter/meter-fill.tsx +19 -0
- package/src/components/meter/meter-header.tsx +17 -0
- package/src/components/meter/meter-label.tsx +18 -0
- package/src/components/meter/meter-output.tsx +42 -0
- package/src/components/meter/meter-segments.tsx +83 -0
- package/src/components/meter/meter-track.tsx +16 -0
- package/src/components/meter/meter.context.tsx +62 -0
- package/src/components/meter/meter.tsx +286 -0
- package/src/components/meter/meter.types.ts +30 -0
- package/src/components/meter/meter.variants.test.ts +430 -0
- package/src/components/meter/meter.variants.ts +347 -0
- package/src/components/overlay/AGENTS.md +89 -0
- package/src/components/overlay/index.ts +27 -0
- package/src/components/overlay/overlay-portal.tsx +84 -0
- package/src/components/overlay/overlay-presence.test.ts +46 -0
- package/src/components/overlay/overlay-presence.ts +49 -0
- package/src/components/overlay/overlay-registry.test.ts +96 -0
- package/src/components/overlay/overlay-registry.ts +99 -0
- package/src/components/overlay/overlay-scrim.tsx +49 -0
- package/src/components/overlay/overlay.context.tsx +48 -0
- package/src/components/overlay/overlay.tsx +118 -0
- package/src/components/overlay/overlay.variants.test.ts +37 -0
- package/src/components/overlay/overlay.variants.ts +33 -0
- package/src/components/overlay/use-overlay-back-handler.ts +34 -0
- package/src/components/overlay/use-overlay-presence.ts +87 -0
- package/src/components/popover/AGENTS.md +138 -0
- package/src/components/popover/index.ts +48 -0
- package/src/components/popover/popover-anchor.tsx +49 -0
- package/src/components/popover/popover-arrow.tsx +89 -0
- package/src/components/popover/popover-close.tsx +73 -0
- package/src/components/popover/popover-content.tsx +235 -0
- package/src/components/popover/popover-description.tsx +18 -0
- package/src/components/popover/popover-title.tsx +33 -0
- package/src/components/popover/popover-trigger.tsx +56 -0
- package/src/components/popover/popover.context.tsx +71 -0
- package/src/components/popover/popover.position.test.ts +354 -0
- package/src/components/popover/popover.position.ts +286 -0
- package/src/components/popover/popover.tsx +123 -0
- package/src/components/popover/popover.variants.test.ts +135 -0
- package/src/components/popover/popover.variants.ts +85 -0
- package/src/components/popover/use-anchor-measure.ts +76 -0
- package/src/components/popover/use-anchored-content.ts +219 -0
- package/src/components/pressable/AGENTS.md +11 -1
- package/src/components/pressable/pressable.tsx +7 -2
- package/src/components/pressable/pressable.variants.test.ts +29 -0
- package/src/components/pressable/pressable.variants.ts +17 -0
- package/src/components/progress/AGENTS.md +120 -0
- package/src/components/progress/index.ts +40 -0
- package/src/components/progress/progress-fill.tsx +152 -0
- package/src/components/progress/progress-header.tsx +19 -0
- package/src/components/progress/progress-label.tsx +27 -0
- package/src/components/progress/progress-output.tsx +47 -0
- package/src/components/progress/progress-track.tsx +38 -0
- package/src/components/progress/progress.context.tsx +79 -0
- package/src/components/progress/progress.tsx +171 -0
- package/src/components/progress/progress.types.ts +23 -0
- package/src/components/progress/progress.variants.test.ts +335 -0
- package/src/components/progress/progress.variants.ts +284 -0
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/provider/AGENTS.md +38 -25
- package/src/components/provider/provider.tsx +38 -11
- package/src/components/radio/AGENTS.md +8 -10
- package/src/components/radio/radio.variants.test.ts +5 -6
- package/src/components/radio/radio.variants.ts +6 -7
- package/src/components/rating/AGENTS.md +143 -0
- package/src/components/rating/index.ts +43 -0
- package/src/components/rating/rating-output.tsx +39 -0
- package/src/components/rating/rating-star.tsx +70 -0
- package/src/components/rating/rating-stars.tsx +219 -0
- package/src/components/rating/rating.context.tsx +79 -0
- package/src/components/rating/rating.tsx +163 -0
- package/src/components/rating/rating.variants.test.ts +347 -0
- package/src/components/rating/rating.variants.ts +354 -0
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
- package/src/components/skeleton/AGENTS.md +120 -0
- package/src/components/skeleton/index.ts +15 -0
- package/src/components/skeleton/skeleton-clock.ts +48 -0
- package/src/components/skeleton/skeleton-group.tsx +64 -0
- package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
- package/src/components/skeleton/skeleton.context.tsx +45 -0
- package/src/components/skeleton/skeleton.tsx +227 -0
- package/src/components/skeleton/skeleton.variants.test.ts +245 -0
- package/src/components/skeleton/skeleton.variants.ts +222 -0
- package/src/components/slide-button/AGENTS.md +122 -0
- package/src/components/slide-button/index.ts +43 -0
- package/src/components/slide-button/slide-button-label.tsx +70 -0
- package/src/components/slide-button/slide-button-thumb.tsx +93 -0
- package/src/components/slide-button/slide-button.context.tsx +76 -0
- package/src/components/slide-button/slide-button.tsx +457 -0
- package/src/components/slide-button/slide-button.variants.test.ts +288 -0
- package/src/components/slide-button/slide-button.variants.ts +324 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider-thumb.tsx +33 -1
- package/src/components/slider/slider.variants.test.ts +52 -0
- package/src/components/slider/slider.variants.ts +56 -2
- package/src/components/stack-card/AGENTS.md +169 -0
- package/src/components/stack-card/index.ts +37 -0
- package/src/components/stack-card/stack-card-action.tsx +66 -0
- package/src/components/stack-card/stack-card-actions.tsx +18 -0
- package/src/components/stack-card/stack-card-card.tsx +71 -0
- package/src/components/stack-card/stack-card-empty.tsx +30 -0
- package/src/components/stack-card/stack-card-slot.tsx +90 -0
- package/src/components/stack-card/stack-card-stamp.tsx +76 -0
- package/src/components/stack-card/stack-card.context.tsx +117 -0
- package/src/components/stack-card/stack-card.tsx +557 -0
- package/src/components/stack-card/stack-card.types.ts +26 -0
- package/src/components/stack-card/stack-card.variants.test.ts +355 -0
- package/src/components/stack-card/stack-card.variants.ts +360 -0
- package/src/components/steps/AGENTS.md +153 -0
- package/src/components/steps/index.ts +45 -0
- package/src/components/steps/steps-connector.tsx +31 -0
- package/src/components/steps/steps-description.tsx +26 -0
- package/src/components/steps/steps-indicator.tsx +106 -0
- package/src/components/steps/steps-item.tsx +225 -0
- package/src/components/steps/steps-panel.tsx +41 -0
- package/src/components/steps/steps-title.tsx +32 -0
- package/src/components/steps/steps-track.tsx +59 -0
- package/src/components/steps/steps.context.tsx +123 -0
- package/src/components/steps/steps.tsx +166 -0
- package/src/components/steps/steps.variants.test.ts +388 -0
- package/src/components/steps/steps.variants.ts +468 -0
- package/src/components/surface/AGENTS.md +72 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +54 -0
- package/src/components/surface/surface.tsx +70 -0
- package/src/components/surface/surface.variants.test.ts +180 -0
- package/src/components/surface/surface.variants.ts +133 -0
- package/src/components/swipe/AGENTS.md +140 -0
- package/src/components/swipe/index.ts +31 -0
- package/src/components/swipe/swipe-action.tsx +99 -0
- package/src/components/swipe/swipe-end.tsx +14 -0
- package/src/components/swipe/swipe-group.tsx +55 -0
- package/src/components/swipe/swipe-panel.tsx +55 -0
- package/src/components/swipe/swipe-start.tsx +15 -0
- package/src/components/swipe/swipe.context.tsx +135 -0
- package/src/components/swipe/swipe.tsx +393 -0
- package/src/components/swipe/swipe.types.ts +7 -0
- package/src/components/swipe/swipe.variants.test.ts +337 -0
- package/src/components/swipe/swipe.variants.ts +290 -0
- package/src/components/switch/switch.variants.ts +2 -2
- package/src/components/tabs/AGENTS.md +4 -3
- package/src/components/tabs/tabs.variants.test.ts +1 -1
- package/src/components/tabs/tabs.variants.ts +4 -6
- package/src/components/textarea/AGENTS.md +86 -0
- package/src/components/textarea/index.ts +16 -0
- package/src/components/textarea/textarea.tsx +157 -0
- package/src/components/textarea/textarea.variants.test.ts +219 -0
- package/src/components/textarea/textarea.variants.ts +171 -0
- package/src/components/toast/AGENTS.md +140 -0
- package/src/components/toast/index.ts +74 -0
- package/src/components/toast/toast-action.tsx +59 -0
- package/src/components/toast/toast-api.ts +32 -0
- package/src/components/toast/toast-close.tsx +44 -0
- package/src/components/toast/toast-content.tsx +11 -0
- package/src/components/toast/toast-description.tsx +11 -0
- package/src/components/toast/toast-indicator.tsx +34 -0
- package/src/components/toast/toast-item.tsx +308 -0
- package/src/components/toast/toast-title.tsx +13 -0
- package/src/components/toast/toast-viewport.tsx +135 -0
- package/src/components/toast/toast.context.tsx +69 -0
- package/src/components/toast/toast.store.test.ts +259 -0
- package/src/components/toast/toast.store.ts +338 -0
- package/src/components/toast/toast.timer.test.ts +64 -0
- package/src/components/toast/toast.timer.ts +51 -0
- package/src/components/toast/toast.tsx +132 -0
- package/src/components/toast/toast.variants.test.ts +393 -0
- package/src/components/toast/toast.variants.ts +304 -0
- package/src/components/toggle-button/AGENTS.md +106 -0
- package/src/components/toggle-button/index.ts +30 -0
- package/src/components/toggle-button/toggle-button-group.tsx +156 -0
- package/src/components/toggle-button/toggle-button-label.tsx +19 -0
- package/src/components/toggle-button/toggle-button.context.tsx +130 -0
- package/src/components/toggle-button/toggle-button.tsx +172 -0
- package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
- package/src/components/toggle-button/toggle-button.variants.ts +160 -0
- package/src/components/tooltip/AGENTS.md +133 -0
- package/src/components/tooltip/index.ts +28 -0
- package/src/components/tooltip/tooltip-arrow.tsx +40 -0
- package/src/components/tooltip/tooltip-content.tsx +179 -0
- package/src/components/tooltip/tooltip-description.tsx +19 -0
- package/src/components/tooltip/tooltip-text.tsx +21 -0
- package/src/components/tooltip/tooltip-title.tsx +20 -0
- package/src/components/tooltip/tooltip-trigger.tsx +103 -0
- package/src/components/tooltip/tooltip.context.tsx +68 -0
- package/src/components/tooltip/tooltip.tsx +213 -0
- package/src/components/tooltip/tooltip.variants.test.ts +231 -0
- package/src/components/tooltip/tooltip.variants.ts +177 -0
- package/src/hooks/use-calm-motion.tsx +31 -0
- package/src/lib/calm-motion.test.ts +17 -0
- package/src/lib/calm-motion.ts +23 -0
- package/src/lib/cn.test.ts +14 -0
- package/src/lib/cn.ts +2 -2
- package/src/no-gorhom.test.ts +54 -0
- package/src/styles/geometry.test.ts +1 -0
- package/src/styles/theme.css +4 -0
- package/src/styles/tokens.css +11 -0
- package/src/styles/tokens.test.ts +22 -0
- package/src/styles/tokens.ts +20 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
|
@@ -87,6 +87,14 @@ knows nothing about tokens.
|
|
|
87
87
|
tailwind-merge is what lets a caller's `className="aspect-video h-auto"`
|
|
88
88
|
cleanly win when they want that instead.
|
|
89
89
|
|
|
90
|
+
- **`frameClassName` resizes the frame, not the root.** The canvas fills the
|
|
91
|
+
frame, and the frame carries the size's height, so a `className` on the root
|
|
92
|
+
cannot shorten the plot — the frame would overflow it. `frameClassName` is
|
|
93
|
+
merged after the size's `h-chart-*`, and the tokens are registered with
|
|
94
|
+
tailwind-merge, so `h-16` cleanly replaces it. `Kpi.Sparkline` is the reason
|
|
95
|
+
it exists: a sparkline is a chart with no axes a third the height of the
|
|
96
|
+
smallest one.
|
|
97
|
+
|
|
90
98
|
- **The tooltip is a React Native view even though it floats over the canvas.**
|
|
91
99
|
It wants `popover`, `border`, the radius scale and the type scale, none of
|
|
92
100
|
which exist in Skia, and it is the part a caller most wants to restyle —
|
|
@@ -72,6 +72,12 @@ export type ChartProps = {
|
|
|
72
72
|
/** How the scrub coexists with a scrolling parent. Defaults to holding. */
|
|
73
73
|
scrubConfig?: ScrubConfig;
|
|
74
74
|
className?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Merged onto the frame — the view the canvas fills — after the size's
|
|
77
|
+
* height. `frameClassName="h-16"` is a sparkline; the three `size` heights
|
|
78
|
+
* are for a chart someone reads with axes.
|
|
79
|
+
*/
|
|
80
|
+
frameClassName?: string;
|
|
75
81
|
/** Named on the frame, so a capture flow or a test can find the plot. */
|
|
76
82
|
testID?: string;
|
|
77
83
|
children?: ReactNode;
|
|
@@ -106,6 +112,7 @@ function ChartRoot({
|
|
|
106
112
|
orientation = "vertical",
|
|
107
113
|
scrubConfig,
|
|
108
114
|
className,
|
|
115
|
+
frameClassName,
|
|
109
116
|
testID,
|
|
110
117
|
children,
|
|
111
118
|
}: ChartProps): ReactElement {
|
|
@@ -231,7 +238,7 @@ function ChartRoot({
|
|
|
231
238
|
return (
|
|
232
239
|
<ChartProvider value={value}>
|
|
233
240
|
<View className={slots.root({ className })}>
|
|
234
|
-
<View className={slots.frame()} onLayout={onLayout} testID={testID}>
|
|
241
|
+
<View className={slots.frame({ className: frameClassName })} onLayout={onLayout} testID={testID}>
|
|
235
242
|
<CartesianChart
|
|
236
243
|
curve={curve}
|
|
237
244
|
data={data}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Chip
|
|
2
|
+
|
|
3
|
+
An interactive pill — a filter that is on or off, a tag, or a token that can be
|
|
4
|
+
removed. Compound root plus `Chip.Label`, `Chip.StartContent`, `Chip.EndContent`
|
|
5
|
+
and `Chip.CloseButton`.
|
|
6
|
+
|
|
7
|
+
`import { Chip } from "@delacour/react-native-ui/chip";`
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | What it holds |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `index.ts` | → `@delacour/react-native-ui/chip` |
|
|
14
|
+
| `chip.tsx` | Root + the `Object.assign` compound surface |
|
|
15
|
+
| `chip-label.tsx` | `Chip.Label` |
|
|
16
|
+
| `chip-start-content.tsx` | `Chip.StartContent` |
|
|
17
|
+
| `chip-end-content.tsx` | `Chip.EndContent` |
|
|
18
|
+
| `chip-close-button.tsx` | `Chip.CloseButton`, the remove pressable |
|
|
19
|
+
| `chip.context.tsx` | `ChipContext`, `useChip()`, `useChipContext()`, `useChipPart()` |
|
|
20
|
+
| `chip.types.ts` | Prop types shared by two or more parts |
|
|
21
|
+
| `chip.variants.ts` | Pure `tv()` slots, the foreground map, hit slops, `resolveChipMode` / `resolveChipSurface` / `resolveChipForegroundToken` — no RN imports |
|
|
22
|
+
| `chip.variants.test.ts` | |
|
|
23
|
+
|
|
24
|
+
## Design
|
|
25
|
+
|
|
26
|
+
- **What a chip does is read from its props, never from a `type` flag.**
|
|
27
|
+
`resolveChipMode` returns `static`, `button` or `toggle`. No handler is a tag,
|
|
28
|
+
rendered as a plain `View` — a detector under every tag in a list of fifty
|
|
29
|
+
would announce each as a button with no action, the same reason
|
|
30
|
+
[`Badge`](../badge/AGENTS.md) stays inert. `onPress` or `onLongPress` is a
|
|
31
|
+
button. **Any** selection prop — `isSelected` even when `false`,
|
|
32
|
+
`defaultSelected`, `onSelectedChange` — is a toggle, and it outranks a bare
|
|
33
|
+
press handler, because a chip with an on state that is announced as a plain
|
|
34
|
+
button leaves a screen-reader user unable to tell which chips are on. A
|
|
35
|
+
toggle still calls `onPress` after flipping.
|
|
36
|
+
- **Selection is announced as `selected`, with the `button` role.** That is the
|
|
37
|
+
native idiom for a filter on both platforms — VoiceOver reads "Selected",
|
|
38
|
+
TalkBack "selected" — and it matches what a system segmented filter reports.
|
|
39
|
+
`checkbox` would promise a box that is not drawn. A caller's own
|
|
40
|
+
`accessibilityState` is merged under it, and `Pressable` merges `disabled` on
|
|
41
|
+
top.
|
|
42
|
+
- **Controlled or uncontrolled, through `useControllableState`**, the same hook
|
|
43
|
+
and the same prop shape as [`Checkbox`](../checkbox/AGENTS.md):
|
|
44
|
+
`isSelected` / `defaultSelected` / `onSelectedChange`. A toggle ticks a
|
|
45
|
+
`selection` haptic by default; a button plays none, as a badge does. Pass
|
|
46
|
+
`haptic` to override either.
|
|
47
|
+
- **Tone is Badge's, not a copy of it.** `CHIP_COLORS` and `CHIP_SIZES` *are*
|
|
48
|
+
`BADGE_COLORS` and `BADGE_SIZES`, and the resting entries of
|
|
49
|
+
`CHIP_FOREGROUND_TOKEN` are Badge's own objects. The class strings in
|
|
50
|
+
`chipVariants` are restated (Tailwind's scanner is static, so a class has to
|
|
51
|
+
appear literally somewhere), and a test pins every resting cell's `bg-*`,
|
|
52
|
+
`border-*` and label colour to the badge of the same variant and colour. A
|
|
53
|
+
soft `success` chip beside a soft `success` badge is one shade by
|
|
54
|
+
construction.
|
|
55
|
+
- **Two resting variants, `soft` and `outline`.** `solid` is what selection
|
|
56
|
+
paints, so a resting solid chip would read as already on; `ghost` has neither
|
|
57
|
+
fill nor border, so there is no edge to aim at. `soft` is the default.
|
|
58
|
+
- **Selection is a surface, not a modifier.** `CHIP_SURFACES` is the two resting
|
|
59
|
+
variants plus `selected`, and a selected chip looks the same whichever it rests
|
|
60
|
+
on — a solid fill and matching border in its colour. The `isSelected`
|
|
61
|
+
compound variants sit after the resting ones, so tailwind-merge lets them win.
|
|
62
|
+
- **A selected `default` chip inverts** to `bg-foreground` / `text-background`.
|
|
63
|
+
Badge's solid default is `secondary`, which this theme sets a hair from
|
|
64
|
+
`muted` — the soft default — so a selected default chip would look unselected.
|
|
65
|
+
A test asserts all eighteen surface-and-colour cells are distinct.
|
|
66
|
+
- **The border is reserved in every state.** A chip toggles in place inside a
|
|
67
|
+
wrapping row; a box that grew two points on selection would reflow every chip
|
|
68
|
+
after it. `overflow-hidden` keeps the pressed fade inside the capsule.
|
|
69
|
+
- **Roomier than a badge, and still never a height.** A chip is aimed at and a
|
|
70
|
+
badge is only read, so each size pads at least as generously as the badge of
|
|
71
|
+
that size — a test asserts it. There is no `h-*`, so the label grows with OS
|
|
72
|
+
font scaling. The icon step is Badge's, so a glyph moves between the two
|
|
73
|
+
without changing size.
|
|
74
|
+
- **Hit slop is vertical and capped at four points.** Chips wrap with `gap-2`;
|
|
75
|
+
slop past half that gap reaches into the neighbouring row and makes a tap
|
|
76
|
+
between two chips ambiguous. `CHIP_HIT_SLOP` is never horizontal for the same
|
|
77
|
+
reason. The close glyph gets `CHIP_CLOSE_HIT_SLOP` on every side instead — it
|
|
78
|
+
is 14–18 points, and its slop only reaches into its own chip's padding.
|
|
79
|
+
- **`onClose` is its own pressable, never a mode of the root.** The remove
|
|
80
|
+
control is a `Chip.CloseButton` composed in last, so its tap is claimed by the
|
|
81
|
+
inner detector and never also toggles the chip. It presses with `fade` —
|
|
82
|
+
a spring on a glyph that small reads as a jitter — and inherits the current
|
|
83
|
+
surface's colour, so it turns with the label on selection.
|
|
84
|
+
- **On a pressable chip, removal is an accessibility action, not a second
|
|
85
|
+
element.** iOS folds every descendant of an accessible view into one element,
|
|
86
|
+
so a close control inside a button or toggle chip was unreachable by swipe and
|
|
87
|
+
its label was read as part of the chip's — "React Native, Remove React
|
|
88
|
+
Native". `resolveChipCloseExposure` decides: a static chip leaves the close
|
|
89
|
+
control as its own element; a pressable one hides it
|
|
90
|
+
(`accessibilityElementsHidden`, `no-hide-descendants`) and adds a `remove`
|
|
91
|
+
action, labelled by `closeAccessibilityLabel`, that calls `onClose` —
|
|
92
|
+
VoiceOver's Actions rotor, TalkBack's actions menu. A caller's own
|
|
93
|
+
`accessibilityActions` are kept beside it.
|
|
94
|
+
- **`closeTestID` reaches the composed close control.** The root's own `testID`
|
|
95
|
+
names the chip; the close part is a separate pressable with no id of its own
|
|
96
|
+
unless the root forwards one, so a flow or test could only find it by its label.
|
|
97
|
+
`closeTestID` is named like `closeAccessibilityLabel`, the other prop the root
|
|
98
|
+
passes through to it.
|
|
99
|
+
- **`self-start` is Badge's, and beside a taller sibling in a row it top-aligns
|
|
100
|
+
the chip.** It is what keeps a chip content-sized in a column, which is the
|
|
101
|
+
common case; in a `flex-row items-center` holding something taller — a
|
|
102
|
+
`Switch` — pass `className="self-center"`, which tailwind-merge lets win. The
|
|
103
|
+
playground's `controlled-and-uncontrolled` demo does exactly this.
|
|
104
|
+
- **Icons are composed, never passed as props.** The root wraps its subtree in an
|
|
105
|
+
`IconDefaultsProvider` and a `TextClassProvider` resolved from the *current*
|
|
106
|
+
surface, so a bare `<Icon>` or `<Text>` follows the chip into and out of
|
|
107
|
+
selection. `useChip()` exposes `isSelected` for a custom child that needs more.
|
|
108
|
+
- **String children** are wrapped in a `Chip.Label` automatically, consecutive
|
|
109
|
+
strings collapsing into one — the same rule as [`Badge`](../badge/AGENTS.md).
|
|
110
|
+
- **No group, yet.** Single- and multi-select filter rows are composed from
|
|
111
|
+
controlled chips (the playground's `filter-row` demo). A `Chip.Group` owning
|
|
112
|
+
the value, as `Checkbox.Group` does, is the obvious next step once a second
|
|
113
|
+
caller needs it.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { IconCrossSmall } from "../../icons/central";
|
|
3
|
+
import { Icon } from "../icon";
|
|
4
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
5
|
+
import { useChipPart } from "./chip.context";
|
|
6
|
+
import { CHIP_CLOSE_HIT_SLOP, chipVariants } from "./chip.variants";
|
|
7
|
+
|
|
8
|
+
export type ChipCloseButtonProps = Omit<PressableProps, "asChild" | "busy" | "children" | "disabled">;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The chip's trailing remove control.
|
|
12
|
+
*
|
|
13
|
+
* A pressable of its own rather than a mode of the root, which is what keeps a
|
|
14
|
+
* removal from also toggling the chip or firing its `onPress`: the two gestures
|
|
15
|
+
* belong to two detectors, and the inner one claims the tap.
|
|
16
|
+
*
|
|
17
|
+
* The root composes one in whenever `onClose` is set, so reach for this by hand
|
|
18
|
+
* only to place it somewhere other than last.
|
|
19
|
+
*
|
|
20
|
+
* The glyph is left bare: it inherits the chip's icon size and its current
|
|
21
|
+
* surface's colour from the root's `IconDefaultsProvider`, so it turns with the
|
|
22
|
+
* label when the chip is selected. `CHIP_CLOSE_HIT_SLOP` grows a 14–18 point
|
|
23
|
+
* glyph into something a thumb can find.
|
|
24
|
+
*/
|
|
25
|
+
export function ChipCloseButton({
|
|
26
|
+
accessibilityLabel = "Remove",
|
|
27
|
+
className,
|
|
28
|
+
feedback = "fade",
|
|
29
|
+
hitSlop,
|
|
30
|
+
...props
|
|
31
|
+
}: ChipCloseButtonProps): ReactElement {
|
|
32
|
+
const { size, isDisabled } = useChipPart("Chip.CloseButton");
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Pressable
|
|
36
|
+
accessibilityLabel={accessibilityLabel}
|
|
37
|
+
accessibilityRole="button"
|
|
38
|
+
className={chipVariants({ size }).closeButton({ className })}
|
|
39
|
+
disabled={isDisabled}
|
|
40
|
+
feedback={feedback}
|
|
41
|
+
hitSlop={hitSlop ?? CHIP_CLOSE_HIT_SLOP[size]}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<Icon icon={IconCrossSmall} />
|
|
45
|
+
</Pressable>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
ChipCloseButton.displayName = "DelacourUI.Chip.CloseButton";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import type { ChipSlotProps } from "./chip.types";
|
|
4
|
+
import { chipVariants } from "./chip.variants";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A centred wrapper for trailing content that is not an `Icon` — a count.
|
|
8
|
+
*
|
|
9
|
+
* An `Icon` needs no wrapper: it inherits the chip's icon size and its
|
|
10
|
+
* surface's colour from the root's `IconDefaultsProvider`.
|
|
11
|
+
*/
|
|
12
|
+
export function ChipEndContent({ className, ...props }: ChipSlotProps): ReactElement {
|
|
13
|
+
return <View className={chipVariants().endContent({ className })} {...props} />;
|
|
14
|
+
}
|
|
15
|
+
ChipEndContent.displayName = "DelacourUI.Chip.EndContent";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { TextProps } from "react-native";
|
|
3
|
+
import { Text } from "../text";
|
|
4
|
+
import { useChipPart } from "./chip.context";
|
|
5
|
+
import { chipVariants } from "./chip.variants";
|
|
6
|
+
|
|
7
|
+
export type ChipLabelProps = TextProps & { className?: string };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The chip's text.
|
|
11
|
+
*
|
|
12
|
+
* Reads the chip's variant, colour and selected state itself, so an
|
|
13
|
+
* icon-plus-text chip changes colour on selection without the caller threading
|
|
14
|
+
* anything through: a React Native `View` does not cascade colour to a `Text`
|
|
15
|
+
* descendant, so a colour set on the root would be lost.
|
|
16
|
+
*/
|
|
17
|
+
export function ChipLabel({ className, ...props }: ChipLabelProps): ReactElement {
|
|
18
|
+
const { variant, color, size, isSelected } = useChipPart("Chip.Label");
|
|
19
|
+
return <Text className={chipVariants({ color, isSelected, size, variant }).label({ className })} {...props} />;
|
|
20
|
+
}
|
|
21
|
+
ChipLabel.displayName = "DelacourUI.Chip.Label";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import type { ChipSlotProps } from "./chip.types";
|
|
4
|
+
import { chipVariants } from "./chip.variants";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A centred wrapper for leading content that is not an `Icon` — an avatar, a
|
|
8
|
+
* status dot.
|
|
9
|
+
*
|
|
10
|
+
* An `Icon` needs no wrapper: it inherits the chip's icon size and its
|
|
11
|
+
* surface's colour from the root's `IconDefaultsProvider`.
|
|
12
|
+
*/
|
|
13
|
+
export function ChipStartContent({ className, ...props }: ChipSlotProps): ReactElement {
|
|
14
|
+
return <View className={chipVariants().startContent({ className })} {...props} />;
|
|
15
|
+
}
|
|
16
|
+
ChipStartContent.displayName = "DelacourUI.Chip.StartContent";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { ChipColor, ChipSize, ChipVariant } from "./chip.variants";
|
|
3
|
+
|
|
4
|
+
export type ChipContextValue = {
|
|
5
|
+
/** Size of the chip. */
|
|
6
|
+
size: ChipSize;
|
|
7
|
+
/** How the chip is painted while unselected. */
|
|
8
|
+
variant: ChipVariant;
|
|
9
|
+
/** What the chip means. */
|
|
10
|
+
color: ChipColor;
|
|
11
|
+
/** Whether the chip is selected. Always `false` for a chip that cannot be. */
|
|
12
|
+
isSelected: boolean;
|
|
13
|
+
/** Whether the chip is disabled. */
|
|
14
|
+
isDisabled: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ChipContext = createContext<ChipContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Supplies the enclosing chip's variant, colour, size and state to its subtree.
|
|
21
|
+
*
|
|
22
|
+
* Lives in its own module, importing nothing but `chip.variants`, so a part can
|
|
23
|
+
* read it without importing `./chip`. That import would close a cycle, and Metro
|
|
24
|
+
* serves a partially initialised module for a cycle — leaving the context
|
|
25
|
+
* `undefined` at import time and red-boxing the app on a cold start.
|
|
26
|
+
*/
|
|
27
|
+
export function ChipProvider({ value, children }: { value: ChipContextValue; children: ReactNode }): ReactElement {
|
|
28
|
+
return <ChipContext value={value}>{children}</ChipContext>;
|
|
29
|
+
}
|
|
30
|
+
ChipProvider.displayName = "DelacourUI.Chip.Provider";
|
|
31
|
+
|
|
32
|
+
/** The enclosing chip's context, or null outside a `<Chip>`. */
|
|
33
|
+
export function useChipContext(): ChipContextValue | null {
|
|
34
|
+
return use(ChipContext);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reads the enclosing chip's variant, colour, size and state.
|
|
39
|
+
*
|
|
40
|
+
* Lets a custom child restyle itself when the chip is selected without the chip
|
|
41
|
+
* passing props down through every slot. Throws outside a `<Chip>` — use
|
|
42
|
+
* {@link useChipContext} where the enclosing chip is optional.
|
|
43
|
+
*/
|
|
44
|
+
export function useChip(): ChipContextValue {
|
|
45
|
+
const context = useChipContext();
|
|
46
|
+
if (!context) {
|
|
47
|
+
throw new Error("useChip must be called inside a <Chip>.");
|
|
48
|
+
}
|
|
49
|
+
return context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The enclosing chip's context, for a compound part that cannot work without
|
|
54
|
+
* one.
|
|
55
|
+
*
|
|
56
|
+
* Internal: deliberately not re-exported from `index.ts`. A caller outside the
|
|
57
|
+
* library wants {@link useChip}, whose error message names the hook rather than
|
|
58
|
+
* a part.
|
|
59
|
+
*/
|
|
60
|
+
export function useChipPart(component: string): ChipContextValue {
|
|
61
|
+
const context = useChipContext();
|
|
62
|
+
if (!context) {
|
|
63
|
+
throw new Error(`${component} must be rendered inside a <Chip>.`);
|
|
64
|
+
}
|
|
65
|
+
return context;
|
|
66
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Children, type ReactElement, type ReactNode, useCallback, useMemo } from "react";
|
|
2
|
+
import { type AccessibilityActionEvent, View } from "react-native";
|
|
3
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
4
|
+
import { IconDefaultsProvider } from "../icon";
|
|
5
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
6
|
+
import { TextClassProvider } from "../text/text.context";
|
|
7
|
+
import { type ChipContextValue, ChipProvider } from "./chip.context";
|
|
8
|
+
import {
|
|
9
|
+
CHIP_HIT_SLOP,
|
|
10
|
+
type ChipColor,
|
|
11
|
+
type ChipSize,
|
|
12
|
+
type ChipVariant,
|
|
13
|
+
chipVariants,
|
|
14
|
+
resolveChipCloseExposure,
|
|
15
|
+
resolveChipForegroundToken,
|
|
16
|
+
resolveChipMode,
|
|
17
|
+
} from "./chip.variants";
|
|
18
|
+
import { ChipCloseButton } from "./chip-close-button";
|
|
19
|
+
import { ChipEndContent } from "./chip-end-content";
|
|
20
|
+
import { ChipLabel } from "./chip-label";
|
|
21
|
+
import { ChipStartContent } from "./chip-start-content";
|
|
22
|
+
|
|
23
|
+
export type ChipProps = Omit<PressableProps, "asChild" | "busy" | "children" | "disabled"> & {
|
|
24
|
+
/** How the chip is painted while unselected. */
|
|
25
|
+
variant?: ChipVariant;
|
|
26
|
+
/** What the chip means. Also the fill it takes when selected. */
|
|
27
|
+
color?: ChipColor;
|
|
28
|
+
size?: ChipSize;
|
|
29
|
+
/**
|
|
30
|
+
* Controlled selected state. Setting it — even to `false` — makes the chip a
|
|
31
|
+
* toggle, announced with its state; a tap reports the flip through
|
|
32
|
+
* `onSelectedChange`.
|
|
33
|
+
*/
|
|
34
|
+
isSelected?: boolean;
|
|
35
|
+
/** Starting selected state while uncontrolled. Also makes the chip a toggle. */
|
|
36
|
+
defaultSelected?: boolean;
|
|
37
|
+
onSelectedChange?: (isSelected: boolean) => void;
|
|
38
|
+
isDisabled?: boolean;
|
|
39
|
+
/** Composes a trailing remove control in. Its press never toggles the chip or reaches `onPress`. */
|
|
40
|
+
onClose?: () => void;
|
|
41
|
+
/** Name a screen reader gives the remove control. Defaults to `Remove`. */
|
|
42
|
+
closeAccessibilityLabel?: string;
|
|
43
|
+
/** `testID` for the remove control, so a test or automation can press it. */
|
|
44
|
+
closeTestID?: string;
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function ChipRoot({
|
|
49
|
+
variant = "soft",
|
|
50
|
+
color = "default",
|
|
51
|
+
size = "md",
|
|
52
|
+
isSelected,
|
|
53
|
+
defaultSelected,
|
|
54
|
+
onSelectedChange,
|
|
55
|
+
isDisabled = false,
|
|
56
|
+
onClose,
|
|
57
|
+
closeAccessibilityLabel,
|
|
58
|
+
closeTestID,
|
|
59
|
+
onPress,
|
|
60
|
+
onLongPress,
|
|
61
|
+
haptic,
|
|
62
|
+
feedback = "scale",
|
|
63
|
+
pressedScale,
|
|
64
|
+
pressedOpacity,
|
|
65
|
+
accessibilityState,
|
|
66
|
+
accessibilityActions,
|
|
67
|
+
onAccessibilityAction,
|
|
68
|
+
className,
|
|
69
|
+
children,
|
|
70
|
+
...props
|
|
71
|
+
}: ChipProps): ReactElement {
|
|
72
|
+
const mode = resolveChipMode({ defaultSelected, isSelected, onLongPress, onPress, onSelectedChange });
|
|
73
|
+
const closeExposure = resolveChipCloseExposure({ hasClose: onClose !== undefined, mode });
|
|
74
|
+
|
|
75
|
+
const [ownSelected, setSelected] = useControllableState({
|
|
76
|
+
defaultValue: defaultSelected ?? false,
|
|
77
|
+
onChange: onSelectedChange,
|
|
78
|
+
value: isSelected,
|
|
79
|
+
});
|
|
80
|
+
const selected = mode === "toggle" && ownSelected;
|
|
81
|
+
|
|
82
|
+
const handlePress = useCallback(() => {
|
|
83
|
+
if (mode === "toggle") setSelected(!selected);
|
|
84
|
+
onPress?.();
|
|
85
|
+
}, [mode, onPress, selected, setSelected]);
|
|
86
|
+
|
|
87
|
+
const context = useMemo<ChipContextValue>(
|
|
88
|
+
() => ({ color, isDisabled, isSelected: selected, size, variant }),
|
|
89
|
+
[color, isDisabled, selected, size, variant]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const slots = chipVariants({ color, isDisabled, isSelected: selected, size, variant });
|
|
93
|
+
|
|
94
|
+
// Icons composed into the chip adopt these unless told otherwise, and turn
|
|
95
|
+
// with the label when the chip is selected.
|
|
96
|
+
const iconClassName = slots.icon();
|
|
97
|
+
const foregroundToken = resolveChipForegroundToken({ color, isSelected: selected, variant });
|
|
98
|
+
const iconDefaults = useMemo(
|
|
99
|
+
() => ({ className: iconClassName, color: foregroundToken }),
|
|
100
|
+
[iconClassName, foregroundToken]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const content = useMemo(() => wrapTextChildren(children), [children]);
|
|
104
|
+
const rootClassName = slots.root({ className });
|
|
105
|
+
|
|
106
|
+
// Inside a pressable chip the close control is folded into the chip's own
|
|
107
|
+
// accessibility element, so it is hidden there and offered as the `remove`
|
|
108
|
+
// action below instead — see `resolveChipCloseExposure`.
|
|
109
|
+
const closeHidden = closeExposure === "action";
|
|
110
|
+
const inner = (
|
|
111
|
+
<IconDefaultsProvider value={iconDefaults}>
|
|
112
|
+
<TextClassProvider value={slots.label()}>
|
|
113
|
+
{content}
|
|
114
|
+
{onClose ? (
|
|
115
|
+
<ChipCloseButton
|
|
116
|
+
accessibilityElementsHidden={closeHidden}
|
|
117
|
+
accessible={!closeHidden}
|
|
118
|
+
accessibilityLabel={closeAccessibilityLabel}
|
|
119
|
+
importantForAccessibility={closeHidden ? "no-hide-descendants" : "auto"}
|
|
120
|
+
onPress={onClose}
|
|
121
|
+
testID={closeTestID}
|
|
122
|
+
/>
|
|
123
|
+
) : null}
|
|
124
|
+
</TextClassProvider>
|
|
125
|
+
</IconDefaultsProvider>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const actions =
|
|
129
|
+
closeExposure === "action"
|
|
130
|
+
? [...(accessibilityActions ?? []), { label: closeAccessibilityLabel ?? "Remove", name: "remove" }]
|
|
131
|
+
: accessibilityActions;
|
|
132
|
+
|
|
133
|
+
const handleAccessibilityAction = (event: AccessibilityActionEvent) => {
|
|
134
|
+
if (closeExposure === "action" && event.nativeEvent.actionName === "remove" && !isDisabled) {
|
|
135
|
+
onClose?.();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
onAccessibilityAction?.(event);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// A chip with nothing to do stays a plain box — see `resolveChipMode`.
|
|
142
|
+
if (mode === "static") {
|
|
143
|
+
return (
|
|
144
|
+
<ChipProvider value={context}>
|
|
145
|
+
<View
|
|
146
|
+
accessibilityActions={accessibilityActions}
|
|
147
|
+
className={rootClassName}
|
|
148
|
+
onAccessibilityAction={onAccessibilityAction}
|
|
149
|
+
{...props}
|
|
150
|
+
>
|
|
151
|
+
{inner}
|
|
152
|
+
</View>
|
|
153
|
+
</ChipProvider>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A toggle reports `selected`, which VoiceOver reads as "Selected" and
|
|
158
|
+
// TalkBack as "selected" — the native idiom for a filter — and a plain
|
|
159
|
+
// button reports nothing extra. Pressable merges `disabled` in on top.
|
|
160
|
+
const state = mode === "toggle" ? { ...accessibilityState, selected } : accessibilityState;
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<ChipProvider value={context}>
|
|
164
|
+
<Pressable
|
|
165
|
+
accessibilityActions={actions}
|
|
166
|
+
accessibilityRole="button"
|
|
167
|
+
accessibilityState={state}
|
|
168
|
+
className={rootClassName}
|
|
169
|
+
disabled={isDisabled}
|
|
170
|
+
feedback={feedback}
|
|
171
|
+
haptic={haptic ?? (mode === "toggle" ? "selection" : false)}
|
|
172
|
+
hitSlop={CHIP_HIT_SLOP[size]}
|
|
173
|
+
onAccessibilityAction={handleAccessibilityAction}
|
|
174
|
+
onLongPress={onLongPress}
|
|
175
|
+
onPress={handlePress}
|
|
176
|
+
pressedOpacity={pressedOpacity}
|
|
177
|
+
pressedScale={pressedScale}
|
|
178
|
+
{...props}
|
|
179
|
+
>
|
|
180
|
+
{inner}
|
|
181
|
+
</Pressable>
|
|
182
|
+
</ChipProvider>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Wraps bare text children in a `Chip.Label`.
|
|
188
|
+
*
|
|
189
|
+
* React Native cannot render a string outside a `<Text>`, so `<Chip>Design</Chip>`
|
|
190
|
+
* would otherwise crash. Consecutive strings and numbers are collected into a
|
|
191
|
+
* single label rather than one each — `{count} open` is one piece of text, and
|
|
192
|
+
* wrapping the parts separately would space them apart by the chip's gap.
|
|
193
|
+
*/
|
|
194
|
+
function wrapTextChildren(children: ReactNode): ReactNode {
|
|
195
|
+
const items = Children.toArray(children);
|
|
196
|
+
const output: ReactNode[] = [];
|
|
197
|
+
let run: (string | number)[] = [];
|
|
198
|
+
|
|
199
|
+
const flushRun = () => {
|
|
200
|
+
if (run.length === 0) return;
|
|
201
|
+
output.push(<ChipLabel key={`label-${output.length}`}>{run.join("")}</ChipLabel>);
|
|
202
|
+
run = [];
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
for (const child of items) {
|
|
206
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
207
|
+
run.push(child);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
flushRun();
|
|
211
|
+
output.push(child);
|
|
212
|
+
}
|
|
213
|
+
flushRun();
|
|
214
|
+
|
|
215
|
+
return output;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* An interactive pill — a filter that is on or off, a tag, or a token that can
|
|
220
|
+
* be removed.
|
|
221
|
+
*
|
|
222
|
+
* What it does is decided by the props it is given, never by a `type` flag:
|
|
223
|
+
*
|
|
224
|
+
* - **Nothing** — a tag. A plain `View`, not announced as a control.
|
|
225
|
+
* - **`onPress` / `onLongPress`** — a button.
|
|
226
|
+
* - **`isSelected`, `defaultSelected` or `onSelectedChange`** — a filter. A tap
|
|
227
|
+
* flips the state, a `selection` haptic ticks, and a screen reader hears
|
|
228
|
+
* whether it is selected. Controlled or uncontrolled, like `Checkbox`.
|
|
229
|
+
* - **`onClose`** — adds a trailing remove control, a pressable of its own, so
|
|
230
|
+
* removing a chip never also toggles it. Combines with any of the above; on
|
|
231
|
+
* a pressable chip a screen reader reaches it as a `remove` action.
|
|
232
|
+
*
|
|
233
|
+
* Shares Badge's `color` and `size` axes and its tones: an unselected chip is
|
|
234
|
+
* painted exactly as the badge of the same `variant` and `color`. Selection
|
|
235
|
+
* replaces that with a solid fill in the chip's colour, and a selected
|
|
236
|
+
* `default` chip inverts. The border is reserved in every state, so selecting a
|
|
237
|
+
* chip never reflows the row it sits in.
|
|
238
|
+
*
|
|
239
|
+
* Icons are composed in, not passed as props. Anything in the subtree inherits
|
|
240
|
+
* the chip's icon size and its current surface's colour, and follows it when
|
|
241
|
+
* the chip is selected.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* <Chip color="success">Shipped</Chip>
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* const [open, setOpen] = useState(false);
|
|
248
|
+
* <Chip isSelected={open} onSelectedChange={setOpen}>
|
|
249
|
+
* <Icon icon={IconFilter} />
|
|
250
|
+
* <Chip.Label>Open only</Chip.Label>
|
|
251
|
+
* </Chip>
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* <Chip onClose={() => remove(tag)} variant="outline">
|
|
255
|
+
* {tag}
|
|
256
|
+
* </Chip>
|
|
257
|
+
*/
|
|
258
|
+
export const Chip = Object.assign(ChipRoot, {
|
|
259
|
+
/** The chip's text. Picks its colour and type scale from the chip's variant, colour, size and selection. */
|
|
260
|
+
Label: ChipLabel,
|
|
261
|
+
/** A centred wrapper for leading content that is not an `Icon` — an avatar, a status dot. */
|
|
262
|
+
StartContent: ChipStartContent,
|
|
263
|
+
/** A centred wrapper for trailing content that is not an `Icon` — a count. */
|
|
264
|
+
EndContent: ChipEndContent,
|
|
265
|
+
/** The trailing remove control. Composed in automatically whenever `onClose` is set. */
|
|
266
|
+
CloseButton: ChipCloseButton,
|
|
267
|
+
displayName: "DelacourUI.Chip",
|
|
268
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape of a chip's leading and trailing slots.
|
|
5
|
+
*
|
|
6
|
+
* Shared by `Chip.StartContent` and `Chip.EndContent`, which are identical apart
|
|
7
|
+
* from where the root places them, so it lives in a leaf rather than in one of
|
|
8
|
+
* the two files arbitrarily.
|
|
9
|
+
*/
|
|
10
|
+
export type ChipSlotProps = ViewProps & { className?: string };
|