@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,50 @@
|
|
|
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 { useAlertPart } from "./alert.context";
|
|
6
|
+
import { alertVariants } from "./alert.variants";
|
|
7
|
+
|
|
8
|
+
export type AlertCloseButtonProps = Omit<PressableProps, "asChild" | "busy" | "children">;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The alert's trailing dismiss control.
|
|
12
|
+
*
|
|
13
|
+
* The root composes one in whenever `isDismissible` is set, so reach for this
|
|
14
|
+
* by hand only to place it somewhere else. Pressing it calls the alert's
|
|
15
|
+
* `dismiss` and then the caller's own `onPress`.
|
|
16
|
+
*
|
|
17
|
+
* The glyph is drawn in `muted-foreground` rather than the status colour: the
|
|
18
|
+
* control is about the alert, not part of what it says, and a red cross beside
|
|
19
|
+
* a red title reads as a second warning. `fade` rather than `scale` — a spring
|
|
20
|
+
* on a glyph this small reads as a jitter. `hitSlop` lifts the target to the
|
|
21
|
+
* 44-point minimum the glyph alone falls short of.
|
|
22
|
+
*/
|
|
23
|
+
export function AlertCloseButton({
|
|
24
|
+
accessibilityLabel = "Dismiss",
|
|
25
|
+
className,
|
|
26
|
+
feedback = "fade",
|
|
27
|
+
hitSlop = 10,
|
|
28
|
+
onPress,
|
|
29
|
+
...props
|
|
30
|
+
}: AlertCloseButtonProps): ReactElement {
|
|
31
|
+
const { size, dismiss } = useAlertPart("Alert.CloseButton");
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Pressable
|
|
35
|
+
accessibilityLabel={accessibilityLabel}
|
|
36
|
+
accessibilityRole="button"
|
|
37
|
+
className={alertVariants({ size }).closeButton({ className })}
|
|
38
|
+
feedback={feedback}
|
|
39
|
+
hitSlop={hitSlop}
|
|
40
|
+
onPress={() => {
|
|
41
|
+
dismiss();
|
|
42
|
+
onPress?.();
|
|
43
|
+
}}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<Icon color="muted-foreground" icon={IconCrossSmall} />
|
|
47
|
+
</Pressable>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
AlertCloseButton.displayName = "DelacourUI.Alert.CloseButton";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import type { AlertSlotProps } from "./alert.types";
|
|
4
|
+
import { alertVariants } from "./alert.variants";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The column holding the title, the description and any action.
|
|
8
|
+
*
|
|
9
|
+
* `flex-1` with `min-w-0` is what keeps a long description wrapping inside the
|
|
10
|
+
* alert instead of pushing the close control off its edge.
|
|
11
|
+
*/
|
|
12
|
+
export function AlertContent({ className, ...props }: AlertSlotProps): ReactElement {
|
|
13
|
+
return <View className={alertVariants().content({ className })} {...props} />;
|
|
14
|
+
}
|
|
15
|
+
AlertContent.displayName = "DelacourUI.Alert.Content";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { useAlertPart } from "./alert.context";
|
|
4
|
+
import { alertVariants } from "./alert.variants";
|
|
5
|
+
|
|
6
|
+
export type AlertDescriptionProps = TextPresetProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The alert's body text — always muted, whatever the status, so a long
|
|
10
|
+
* explanation under a red title never shouts.
|
|
11
|
+
*/
|
|
12
|
+
export function AlertDescription({ className, ...props }: AlertDescriptionProps): ReactElement {
|
|
13
|
+
const { size } = useAlertPart("Alert.Description");
|
|
14
|
+
return <Text className={alertVariants({ size }).description({ className })} {...props} />;
|
|
15
|
+
}
|
|
16
|
+
AlertDescription.displayName = "DelacourUI.Alert.Description";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IconCircleCheck, IconCircleInfo, IconExclamationCircle, IconExclamationTriangle } from "../../icons/central";
|
|
2
|
+
import type { IconComponent } from "../icon";
|
|
3
|
+
import type { AlertStatus } from "./alert.variants";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The glyph each status draws.
|
|
7
|
+
*
|
|
8
|
+
* `warning` and `destructive` differ in shape as well as colour — a triangle
|
|
9
|
+
* against a circle — so the two stay distinguishable to anyone who cannot tell
|
|
10
|
+
* amber from red. `default` shares `info`'s glyph: a neutral note is still a
|
|
11
|
+
* note, and a status of its own would need a meaning it does not have.
|
|
12
|
+
*
|
|
13
|
+
* A leaf of its own rather than a constant in `alert-indicator.tsx`: `Toast`
|
|
14
|
+
* draws the same glyph per status, so the two read as one family, and it may
|
|
15
|
+
* import a leaf across folders where it may not import a part (package rule 3).
|
|
16
|
+
* Not in `alert.variants.ts` because the glyphs are React Native SVG components,
|
|
17
|
+
* and that file must stay importable from `bun test`.
|
|
18
|
+
*/
|
|
19
|
+
export const ALERT_GLYPHS: Record<AlertStatus, IconComponent> = {
|
|
20
|
+
default: IconCircleInfo,
|
|
21
|
+
info: IconCircleInfo,
|
|
22
|
+
success: IconCircleCheck,
|
|
23
|
+
warning: IconExclamationTriangle,
|
|
24
|
+
destructive: IconExclamationCircle,
|
|
25
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { Icon } from "../icon";
|
|
4
|
+
import { useAlertPart } from "./alert.context";
|
|
5
|
+
import { alertVariants } from "./alert.variants";
|
|
6
|
+
import { ALERT_GLYPHS } from "./alert-glyphs";
|
|
7
|
+
|
|
8
|
+
export type AlertIndicatorProps = ViewProps & {
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Replaces the status glyph — a `Spinner`, an `Icon` of your own. It inherits the alert's icon size and colour. */
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The alert's leading glyph, picked from its status.
|
|
16
|
+
*
|
|
17
|
+
* Carries the title's line height as its own height and centres the glyph in
|
|
18
|
+
* it, so the glyph sits level with the title's first line however far the
|
|
19
|
+
* description wraps.
|
|
20
|
+
*
|
|
21
|
+
* Hidden from assistive technology. The title says what happened, and a screen
|
|
22
|
+
* reader announcing "image" before it adds nothing. Children replace the glyph
|
|
23
|
+
* and inherit the alert's icon size and colour, so `<Spinner />` or
|
|
24
|
+
* `<Icon icon={IconCloud} />` needs nothing else.
|
|
25
|
+
*/
|
|
26
|
+
export function AlertIndicator({ className, children, ...props }: AlertIndicatorProps): ReactElement {
|
|
27
|
+
const { status, size } = useAlertPart("Alert.Indicator");
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View
|
|
31
|
+
accessibilityElementsHidden
|
|
32
|
+
className={alertVariants({ size }).indicator({ className })}
|
|
33
|
+
importantForAccessibility="no-hide-descendants"
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{children ?? <Icon icon={ALERT_GLYPHS[status]} />}
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
AlertIndicator.displayName = "DelacourUI.Alert.Indicator";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { useAlertPart } from "./alert.context";
|
|
4
|
+
import { alertVariants } from "./alert.variants";
|
|
5
|
+
|
|
6
|
+
export type AlertTitleProps = TextPresetProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The alert's heading, coloured by its status.
|
|
10
|
+
*
|
|
11
|
+
* The colour is the same token the indicator's glyph reads, so the two are
|
|
12
|
+
* always one shade — a test pins the pair.
|
|
13
|
+
*/
|
|
14
|
+
export function AlertTitle({ className, ...props }: AlertTitleProps): ReactElement {
|
|
15
|
+
const { status, size } = useAlertPart("Alert.Title");
|
|
16
|
+
return <Text className={alertVariants({ size, status }).title({ className })} {...props} />;
|
|
17
|
+
}
|
|
18
|
+
AlertTitle.displayName = "DelacourUI.Alert.Title";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { AlertSize, AlertStatus, AlertVariant } from "./alert.variants";
|
|
3
|
+
|
|
4
|
+
export type AlertContextValue = {
|
|
5
|
+
/** What the alert says about the thing it describes. */
|
|
6
|
+
status: AlertStatus;
|
|
7
|
+
/** How the alert's surface is painted. */
|
|
8
|
+
variant: AlertVariant;
|
|
9
|
+
/** Size of the alert. */
|
|
10
|
+
size: AlertSize;
|
|
11
|
+
/**
|
|
12
|
+
* Closes the alert — hides it when uncontrolled, and reports `false` through
|
|
13
|
+
* `onOpenChange` either way. Lets an action inside the alert ("Got it")
|
|
14
|
+
* dismiss it without the caller threading a setter down.
|
|
15
|
+
*/
|
|
16
|
+
dismiss: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const AlertContext = createContext<AlertContextValue | null>(null);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Supplies the enclosing alert's status, variant, size and `dismiss` to its
|
|
23
|
+
* subtree.
|
|
24
|
+
*
|
|
25
|
+
* Lives in its own module, importing nothing but `alert.variants`, so a part
|
|
26
|
+
* can read it without importing `./alert`. That import would close a cycle, and
|
|
27
|
+
* Metro serves a partially initialised module for a cycle — leaving the context
|
|
28
|
+
* `undefined` at import time and red-boxing the app on a cold start.
|
|
29
|
+
*/
|
|
30
|
+
export function AlertProvider({ value, children }: { value: AlertContextValue; children: ReactNode }): ReactElement {
|
|
31
|
+
return <AlertContext value={value}>{children}</AlertContext>;
|
|
32
|
+
}
|
|
33
|
+
AlertProvider.displayName = "DelacourUI.Alert.Provider";
|
|
34
|
+
|
|
35
|
+
/** The enclosing alert's context, or null outside an `<Alert>`. */
|
|
36
|
+
export function useAlertContext(): AlertContextValue | null {
|
|
37
|
+
return use(AlertContext);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reads the enclosing alert's status, variant, size and `dismiss`.
|
|
42
|
+
*
|
|
43
|
+
* Lets a custom child match the alert — or close it — without the alert passing
|
|
44
|
+
* props down. Throws outside an `<Alert>`; use {@link useAlertContext} where the
|
|
45
|
+
* enclosing alert is optional.
|
|
46
|
+
*/
|
|
47
|
+
export function useAlert(): AlertContextValue {
|
|
48
|
+
const context = useAlertContext();
|
|
49
|
+
if (!context) {
|
|
50
|
+
throw new Error("useAlert must be called inside an <Alert>.");
|
|
51
|
+
}
|
|
52
|
+
return context;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The enclosing alert's context, for a compound part that cannot work without
|
|
57
|
+
* one.
|
|
58
|
+
*
|
|
59
|
+
* Internal: deliberately not re-exported from `index.ts`. A caller outside the
|
|
60
|
+
* library wants {@link useAlert}, whose error message names the hook rather than
|
|
61
|
+
* a part.
|
|
62
|
+
*/
|
|
63
|
+
export function useAlertPart(component: string): AlertContextValue {
|
|
64
|
+
const context = useAlertContext();
|
|
65
|
+
if (!context) {
|
|
66
|
+
throw new Error(`${component} must be rendered inside an <Alert>.`);
|
|
67
|
+
}
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useMemo } from "react";
|
|
2
|
+
import Animated, { FadeOut } from "react-native-reanimated";
|
|
3
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
4
|
+
import { IconDefaultsProvider } from "../icon";
|
|
5
|
+
import { Surface, type SurfaceProps } from "../surface";
|
|
6
|
+
import { type AlertContextValue, AlertProvider } from "./alert.context";
|
|
7
|
+
import {
|
|
8
|
+
ALERT_FOREGROUND_TOKEN,
|
|
9
|
+
ALERT_SURFACE_PADDING,
|
|
10
|
+
type AlertSize,
|
|
11
|
+
type AlertStatus,
|
|
12
|
+
type AlertVariant,
|
|
13
|
+
alertVariants,
|
|
14
|
+
resolveAlertLiveRegion,
|
|
15
|
+
resolveAlertSurfaceVariant,
|
|
16
|
+
} from "./alert.variants";
|
|
17
|
+
import { AlertAction } from "./alert-action";
|
|
18
|
+
import { AlertCloseButton } from "./alert-close-button";
|
|
19
|
+
import { AlertContent } from "./alert-content";
|
|
20
|
+
import { AlertDescription } from "./alert-description";
|
|
21
|
+
import { AlertIndicator } from "./alert-indicator";
|
|
22
|
+
import { AlertTitle } from "./alert-title";
|
|
23
|
+
|
|
24
|
+
/** A dismissed alert fades rather than vanishing, so the layout below it has a beat to follow. */
|
|
25
|
+
const EXITING = FadeOut.duration(150);
|
|
26
|
+
|
|
27
|
+
export type AlertProps = Omit<SurfaceProps, "variant" | "padding"> & {
|
|
28
|
+
/** What the alert says: `default`, `info`, `success`, `warning` or `destructive`. Picks the glyph and the colour. */
|
|
29
|
+
status?: AlertStatus;
|
|
30
|
+
/** `soft` washes the surface in the status's colour; `surface` keeps the neutral fill and colours only the glyph and title. */
|
|
31
|
+
variant?: AlertVariant;
|
|
32
|
+
/** Padding, gap, type scale and glyph size together. */
|
|
33
|
+
size?: AlertSize;
|
|
34
|
+
/** Composes a trailing dismiss control in. */
|
|
35
|
+
isDismissible?: boolean;
|
|
36
|
+
/** Whether the alert is shown. Pass it to control the alert; omit it and the alert hides itself when dismissed. */
|
|
37
|
+
isOpen?: boolean;
|
|
38
|
+
/** Whether an uncontrolled alert starts shown. */
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
/** Called with `false` when the alert is dismissed, controlled or not. */
|
|
41
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
42
|
+
/** Name a screen reader gives the dismiss control. Defaults to `Dismiss`. */
|
|
43
|
+
closeAccessibilityLabel?: string;
|
|
44
|
+
/** `testID` for the dismiss control, so a test or automation can press it. */
|
|
45
|
+
closeTestID?: string;
|
|
46
|
+
children?: ReactNode;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function AlertRoot({
|
|
50
|
+
status = "default",
|
|
51
|
+
variant = "soft",
|
|
52
|
+
size = "md",
|
|
53
|
+
isDismissible = false,
|
|
54
|
+
isOpen,
|
|
55
|
+
defaultOpen = true,
|
|
56
|
+
onOpenChange,
|
|
57
|
+
closeAccessibilityLabel,
|
|
58
|
+
closeTestID,
|
|
59
|
+
className,
|
|
60
|
+
children,
|
|
61
|
+
...props
|
|
62
|
+
}: AlertProps): ReactElement | null {
|
|
63
|
+
const [open, setOpen] = useControllableState({ defaultValue: defaultOpen, onChange: onOpenChange, value: isOpen });
|
|
64
|
+
const dismiss = useCallback(() => setOpen(false), [setOpen]);
|
|
65
|
+
|
|
66
|
+
const context = useMemo<AlertContextValue>(
|
|
67
|
+
() => ({ dismiss, size, status, variant }),
|
|
68
|
+
[dismiss, size, status, variant]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const slots = alertVariants({ size, status, variant });
|
|
72
|
+
|
|
73
|
+
// A glyph composed anywhere inside — the indicator's own, a `Spinner`, an
|
|
74
|
+
// `Icon` in an action — adopts the alert's step and its status's colour.
|
|
75
|
+
const iconClassName = slots.icon();
|
|
76
|
+
const iconColor = ALERT_FOREGROUND_TOKEN[status];
|
|
77
|
+
const iconDefaults = useMemo(() => ({ className: iconClassName, color: iconColor }), [iconClassName, iconColor]);
|
|
78
|
+
|
|
79
|
+
if (!open) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<AlertProvider value={context}>
|
|
85
|
+
<Animated.View exiting={EXITING}>
|
|
86
|
+
<Surface
|
|
87
|
+
accessibilityLiveRegion={resolveAlertLiveRegion(status)}
|
|
88
|
+
accessibilityRole="alert"
|
|
89
|
+
className={slots.root({ className })}
|
|
90
|
+
padding={ALERT_SURFACE_PADDING[size]}
|
|
91
|
+
variant={resolveAlertSurfaceVariant({ status, variant })}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
<IconDefaultsProvider value={iconDefaults}>
|
|
95
|
+
{children}
|
|
96
|
+
{isDismissible ? (
|
|
97
|
+
<AlertCloseButton accessibilityLabel={closeAccessibilityLabel} testID={closeTestID} />
|
|
98
|
+
) : null}
|
|
99
|
+
</IconDefaultsProvider>
|
|
100
|
+
</Surface>
|
|
101
|
+
</Animated.View>
|
|
102
|
+
</AlertProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A status message — a glyph, a title, a description and, optionally, an
|
|
108
|
+
* action — drawn on a `Surface`.
|
|
109
|
+
*
|
|
110
|
+
* `status` says what the message means: `default`, `info`, `success`,
|
|
111
|
+
* `warning` or `destructive`. It picks the indicator's glyph and colours the
|
|
112
|
+
* glyph and the title from one token; the description stays muted whatever the
|
|
113
|
+
* status. `variant` says how loudly: `soft` washes the surface in the status's
|
|
114
|
+
* soft fill, `surface` keeps the neutral fill and lets the glyph and title carry
|
|
115
|
+
* the colour. A neutral or `surface` alert takes its fill from the surface
|
|
116
|
+
* ladder, so one inside a card steps off the card instead of vanishing into it.
|
|
117
|
+
*
|
|
118
|
+
* `isDismissible` composes a close control in. Uncontrolled, the alert hides
|
|
119
|
+
* itself when dismissed; pass `isOpen` and `onOpenChange` to own that state.
|
|
120
|
+
* `useAlert().dismiss` closes it from inside, for a "Got it" action.
|
|
121
|
+
*
|
|
122
|
+
* Announced as an `alert`, and as a live region on Android — assertive for a
|
|
123
|
+
* warning or a failure, polite for anything else.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* <Alert status="warning">
|
|
127
|
+
* <Alert.Indicator />
|
|
128
|
+
* <Alert.Content>
|
|
129
|
+
* <Alert.Title>Card expiring</Alert.Title>
|
|
130
|
+
* <Alert.Description>The card ending 4242 expires next month.</Alert.Description>
|
|
131
|
+
* </Alert.Content>
|
|
132
|
+
* </Alert>
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* <Alert isDismissible status="destructive">
|
|
136
|
+
* <Alert.Indicator />
|
|
137
|
+
* <Alert.Content>
|
|
138
|
+
* <Alert.Title>Payment failed</Alert.Title>
|
|
139
|
+
* <Alert.Description>Your bank declined the charge.</Alert.Description>
|
|
140
|
+
* <Alert.Action>
|
|
141
|
+
* <Button onPress={retry} size="sm">Try again</Button>
|
|
142
|
+
* </Alert.Action>
|
|
143
|
+
* </Alert.Content>
|
|
144
|
+
* </Alert>
|
|
145
|
+
*/
|
|
146
|
+
export const Alert = Object.assign(AlertRoot, {
|
|
147
|
+
/** The leading status glyph, picked from `status`. Children replace it. */
|
|
148
|
+
Indicator: AlertIndicator,
|
|
149
|
+
/** The column holding the title, description and action; takes the remaining width. */
|
|
150
|
+
Content: AlertContent,
|
|
151
|
+
/** The heading, coloured by the status. */
|
|
152
|
+
Title: AlertTitle,
|
|
153
|
+
/** The body text, always muted. */
|
|
154
|
+
Description: AlertDescription,
|
|
155
|
+
/** A wrapping row of buttons under the description. */
|
|
156
|
+
Action: AlertAction,
|
|
157
|
+
/** The trailing dismiss control. Composed in automatically whenever `isDismissible` is set. */
|
|
158
|
+
CloseButton: AlertCloseButton,
|
|
159
|
+
displayName: "DelacourUI.Alert",
|
|
160
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape of an alert's layout parts.
|
|
5
|
+
*
|
|
6
|
+
* Shared by `Alert.Content` and `Alert.Action`, which are both a styled `View`
|
|
7
|
+
* and nothing else, so it lives in a leaf rather than in one of the two files
|
|
8
|
+
* arbitrarily.
|
|
9
|
+
*/
|
|
10
|
+
export type AlertSlotProps = ViewProps & { className?: string };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
ALERT_FOREGROUND_TOKEN,
|
|
6
|
+
ALERT_SIZES,
|
|
7
|
+
ALERT_STATUSES,
|
|
8
|
+
ALERT_SURFACE_PADDING,
|
|
9
|
+
ALERT_TINTED_STATUSES,
|
|
10
|
+
ALERT_VARIANTS,
|
|
11
|
+
alertVariants,
|
|
12
|
+
resolveAlertLiveRegion,
|
|
13
|
+
resolveAlertSurfaceVariant,
|
|
14
|
+
resolveAlertTinted,
|
|
15
|
+
} from "./alert.variants";
|
|
16
|
+
|
|
17
|
+
/** Every root class string the variant function can produce, one per combination. */
|
|
18
|
+
function everyRoot(): string[] {
|
|
19
|
+
return ALERT_VARIANTS.flatMap((variant) =>
|
|
20
|
+
ALERT_STATUSES.flatMap((status) => ALERT_SIZES.map((size) => alertVariants({ size, status, variant }).root()))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("alertVariants root slot", () => {
|
|
25
|
+
test("lays the indicator, content and close control out in a row, aligned to the top", () => {
|
|
26
|
+
for (const cls of everyRoot()) {
|
|
27
|
+
expect(cls).toContain("flex-row");
|
|
28
|
+
expect(cls).toContain("items-start");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("tints a soft alert with its status's soft fill", () => {
|
|
33
|
+
expect(alertVariants({ status: "info", variant: "soft" }).root()).toContain("bg-info-soft");
|
|
34
|
+
expect(alertVariants({ status: "success", variant: "soft" }).root()).toContain("bg-success-soft");
|
|
35
|
+
expect(alertVariants({ status: "warning", variant: "soft" }).root()).toContain("bg-warning-soft");
|
|
36
|
+
expect(alertVariants({ status: "destructive", variant: "soft" }).root()).toContain("bg-destructive-soft");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// A tinted fill is its own edge. A grey card hairline around a red wash
|
|
40
|
+
// reads as two components stacked, not one.
|
|
41
|
+
test("a tinted alert hides the surface's hairline", () => {
|
|
42
|
+
for (const status of ALERT_TINTED_STATUSES) {
|
|
43
|
+
expect(alertVariants({ status, variant: "soft" }).root()).toContain("border-transparent");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// The fill is the Surface's: a neutral alert, or any alert on the surface
|
|
48
|
+
// variant, names no background of its own, so the ladder decides it.
|
|
49
|
+
test("paints no fill of its own where the surface ladder decides", () => {
|
|
50
|
+
for (const status of ALERT_STATUSES) {
|
|
51
|
+
expect(alertVariants({ status, variant: "surface" }).root()).not.toMatch(/\bbg-/);
|
|
52
|
+
}
|
|
53
|
+
expect(alertVariants({ status: "default", variant: "soft" }).root()).not.toMatch(/\bbg-/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("gives every tinted status a distinct surface", () => {
|
|
57
|
+
const seen = new Set(ALERT_TINTED_STATUSES.map((status) => alertVariants({ status, variant: "soft" }).root()));
|
|
58
|
+
expect(seen.size).toBe(ALERT_TINTED_STATUSES.length);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("steps its gap up with size", () => {
|
|
62
|
+
expect(alertVariants({ size: "sm" }).root()).toContain("gap-2");
|
|
63
|
+
expect(alertVariants({ size: "md" }).root()).toContain("gap-3");
|
|
64
|
+
expect(alertVariants({ size: "lg" }).root()).toContain("gap-3.5");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Rule 1: a React Native View does not cascade colour to a Text descendant.
|
|
68
|
+
test("carries no text colour on the root", () => {
|
|
69
|
+
for (const cls of everyRoot()) {
|
|
70
|
+
expect(cls).not.toMatch(/\btext-/);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Surface owns the padding; a second one here would fight it through the merge.
|
|
75
|
+
test("sets no padding of its own", () => {
|
|
76
|
+
for (const cls of everyRoot()) {
|
|
77
|
+
expect(cls).not.toMatch(/(^|\s)p[xy]?-/);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("merges an incoming className last", () => {
|
|
82
|
+
expect(alertVariants().root({ className: "mt-4" })).toContain("mt-4");
|
|
83
|
+
expect(alertVariants({ status: "info" }).root({ className: "bg-card" })).not.toContain("bg-info-soft");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("alertVariants text slots", () => {
|
|
88
|
+
test("the title is coloured by the status, from the same token the icon reads", () => {
|
|
89
|
+
for (const status of ALERT_STATUSES) {
|
|
90
|
+
expect(alertVariants({ status }).title()).toContain(`text-${ALERT_FOREGROUND_TOKEN[status]}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("the description is always muted", () => {
|
|
95
|
+
for (const status of ALERT_STATUSES) {
|
|
96
|
+
expect(alertVariants({ status }).description()).toContain("text-muted-foreground");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("the title is heavier than the description", () => {
|
|
101
|
+
expect(alertVariants().title()).toContain("font-semibold");
|
|
102
|
+
expect(alertVariants().description()).not.toContain("font-semibold");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("steps both text sizes up with the alert", () => {
|
|
106
|
+
expect(alertVariants({ size: "sm" }).title()).toContain("text-sm");
|
|
107
|
+
expect(alertVariants({ size: "md" }).title()).toContain("text-base");
|
|
108
|
+
expect(alertVariants({ size: "lg" }).title()).toContain("text-lg");
|
|
109
|
+
expect(alertVariants({ size: "sm" }).description()).toContain("text-xs");
|
|
110
|
+
expect(alertVariants({ size: "md" }).description()).toContain("text-sm");
|
|
111
|
+
expect(alertVariants({ size: "lg" }).description()).toContain("text-base");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// A fixed height clips wrapped text at a large accessibility step.
|
|
115
|
+
test("no slot carries a fixed height", () => {
|
|
116
|
+
for (const size of ALERT_SIZES) {
|
|
117
|
+
const slots = alertVariants({ size });
|
|
118
|
+
for (const cls of [slots.root(), slots.content(), slots.title(), slots.description(), slots.action()]) {
|
|
119
|
+
expect(cls).not.toMatch(/(^|\s)h-/);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("alertVariants layout slots", () => {
|
|
126
|
+
// Without flex-1 a long description pushes the close control off the edge.
|
|
127
|
+
test("the content takes the remaining width", () => {
|
|
128
|
+
expect(alertVariants().content()).toContain("flex-1");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the action row wraps rather than overflowing", () => {
|
|
132
|
+
expect(alertVariants().action()).toContain("flex-row");
|
|
133
|
+
expect(alertVariants().action()).toContain("flex-wrap");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("the icon indexes the shared icon scale at every size", () => {
|
|
137
|
+
expect(alertVariants({ size: "sm" }).icon()).toBe("size-icon-sm");
|
|
138
|
+
expect(alertVariants({ size: "md" }).icon()).toBe("size-icon-md");
|
|
139
|
+
expect(alertVariants({ size: "lg" }).icon()).toBe("size-icon-lg");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// The glyph sits level with the title's first line rather than with the top
|
|
143
|
+
// of the box, so the indicator carries the line height as its own height.
|
|
144
|
+
test("the indicator is as tall as the title's first line", () => {
|
|
145
|
+
expect(alertVariants({ size: "sm" }).indicator()).toContain("h-5");
|
|
146
|
+
expect(alertVariants({ size: "md" }).indicator()).toContain("h-6");
|
|
147
|
+
expect(alertVariants({ size: "lg" }).indicator()).toContain("h-7");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("ALERT_FOREGROUND_TOKEN", () => {
|
|
152
|
+
test("a status names its soft foreground; the neutral alert names the page's", () => {
|
|
153
|
+
expect(ALERT_FOREGROUND_TOKEN).toEqual({
|
|
154
|
+
default: "foreground",
|
|
155
|
+
info: "info-soft-foreground",
|
|
156
|
+
success: "success-soft-foreground",
|
|
157
|
+
warning: "warning-soft-foreground",
|
|
158
|
+
destructive: "destructive-soft-foreground",
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// A token no theme declares compiles to nothing and the icon falls back to
|
|
163
|
+
// whatever colour it inherited.
|
|
164
|
+
test("every token is declared in both themes and aliased", () => {
|
|
165
|
+
const css = readFileSync(join(import.meta.dir, "../../styles/theme.css"), "utf8");
|
|
166
|
+
for (const token of Object.values(ALERT_FOREGROUND_TOKEN)) {
|
|
167
|
+
expect(css).toContain(`--color-${token}: var(--${token})`);
|
|
168
|
+
expect(css.split(`--${token}:`).length - 1).toBeGreaterThanOrEqual(2);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("ALERT_SURFACE_PADDING", () => {
|
|
174
|
+
test("maps each size onto the surface's own padding step", () => {
|
|
175
|
+
expect(ALERT_SURFACE_PADDING).toEqual({ sm: "sm", md: "md", lg: "lg" });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("resolveAlertTinted", () => {
|
|
180
|
+
test("only a soft alert with a status is tinted", () => {
|
|
181
|
+
for (const status of ALERT_TINTED_STATUSES) {
|
|
182
|
+
expect(resolveAlertTinted({ status, variant: "soft" })).toBe(true);
|
|
183
|
+
expect(resolveAlertTinted({ status, variant: "surface" })).toBe(false);
|
|
184
|
+
}
|
|
185
|
+
expect(resolveAlertTinted({ status: "default", variant: "soft" })).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("resolveAlertSurfaceVariant", () => {
|
|
190
|
+
// A tinted alert paints over a card-shaped surface, so what nests in it
|
|
191
|
+
// steps from `default` like anything else on a card.
|
|
192
|
+
test("a tinted alert pins the surface to the card", () => {
|
|
193
|
+
for (const status of ALERT_TINTED_STATUSES) {
|
|
194
|
+
expect(resolveAlertSurfaceVariant({ status, variant: "soft" })).toBe("default");
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Left undefined, the surface steps from whatever it sits in, so an alert
|
|
199
|
+
// inside a card never vanishes into the card.
|
|
200
|
+
test("an untinted alert leaves the fill to the ladder", () => {
|
|
201
|
+
expect(resolveAlertSurfaceVariant({ status: "default", variant: "soft" })).toBeUndefined();
|
|
202
|
+
for (const status of ALERT_STATUSES) {
|
|
203
|
+
expect(resolveAlertSurfaceVariant({ status, variant: "surface" })).toBeUndefined();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("resolveAlertLiveRegion", () => {
|
|
209
|
+
// Android announces a live region when its content changes. A failure
|
|
210
|
+
// interrupts; news waits its turn.
|
|
211
|
+
test("a warning or a failure interrupts, anything else waits", () => {
|
|
212
|
+
expect(resolveAlertLiveRegion("destructive")).toBe("assertive");
|
|
213
|
+
expect(resolveAlertLiveRegion("warning")).toBe("assertive");
|
|
214
|
+
expect(resolveAlertLiveRegion("default")).toBe("polite");
|
|
215
|
+
expect(resolveAlertLiveRegion("info")).toBe("polite");
|
|
216
|
+
expect(resolveAlertLiveRegion("success")).toBe("polite");
|
|
217
|
+
});
|
|
218
|
+
});
|