@delacour/react-native-ui 0.1.0-alpha.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/package.json +49 -5
- package/src/components/accordion/AGENTS.md +21 -9
- package/src/components/accordion/accordion-content.tsx +7 -4
- package/src/components/accordion/accordion-item.tsx +15 -13
- package/src/components/accordion/accordion.context.tsx +11 -5
- package/src/components/accordion/accordion.variants.test.ts +64 -3
- package/src/components/accordion/accordion.variants.ts +33 -9
- package/src/components/alert/AGENTS.md +102 -0
- package/src/components/alert/alert-action.tsx +17 -0
- package/src/components/alert/alert-close-button.tsx +50 -0
- package/src/components/alert/alert-content.tsx +15 -0
- package/src/components/alert/alert-description.tsx +16 -0
- package/src/components/alert/alert-glyphs.ts +25 -0
- package/src/components/alert/alert-indicator.tsx +40 -0
- package/src/components/alert/alert-title.tsx +18 -0
- package/src/components/alert/alert.context.tsx +69 -0
- package/src/components/alert/alert.tsx +160 -0
- package/src/components/alert/alert.types.ts +10 -0
- package/src/components/alert/alert.variants.test.ts +218 -0
- package/src/components/alert/alert.variants.ts +180 -0
- package/src/components/alert/index.ts +23 -0
- package/src/components/avatar/AGENTS.md +106 -0
- package/src/components/avatar/avatar-badge.tsx +51 -0
- package/src/components/avatar/avatar-group.tsx +139 -0
- package/src/components/avatar/avatar.context.tsx +93 -0
- package/src/components/avatar/avatar.tsx +201 -0
- package/src/components/avatar/avatar.utils.test.ts +174 -0
- package/src/components/avatar/avatar.utils.ts +141 -0
- package/src/components/avatar/avatar.variants.test.ts +251 -0
- package/src/components/avatar/avatar.variants.ts +270 -0
- package/src/components/avatar/index.ts +39 -0
- package/src/components/badge/AGENTS.md +5 -0
- package/src/components/badge/badge.tsx +6 -1
- package/src/components/bottom-sheet/AGENTS.md +186 -221
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -207
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
- package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -120
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
- package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
- package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -52
- package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
- package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
- package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
- package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -70
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
- package/src/components/bottom-sheet/index.ts +39 -17
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
- package/src/components/button/AGENTS.md +45 -11
- package/src/components/button/button-group.tsx +44 -8
- package/src/components/button/button.context.tsx +10 -0
- package/src/components/button/button.tsx +2 -1
- package/src/components/button/button.variants.test.ts +154 -0
- package/src/components/button/button.variants.ts +71 -6
- package/src/components/button/index.ts +2 -0
- package/src/components/calendar/AGENTS.md +142 -0
- package/src/components/calendar/calendar-caption.tsx +116 -0
- package/src/components/calendar/calendar-day.tsx +103 -0
- package/src/components/calendar/calendar-grid.tsx +183 -0
- package/src/components/calendar/calendar-header.tsx +34 -0
- package/src/components/calendar/calendar-nav.tsx +51 -0
- package/src/components/calendar/calendar-picker.tsx +128 -0
- package/src/components/calendar/calendar-weekdays.tsx +48 -0
- package/src/components/calendar/calendar.context.tsx +108 -0
- package/src/components/calendar/calendar.date.test.ts +456 -0
- package/src/components/calendar/calendar.date.ts +584 -0
- package/src/components/calendar/calendar.tsx +389 -0
- package/src/components/calendar/calendar.types.ts +87 -0
- package/src/components/calendar/calendar.variants.test.ts +363 -0
- package/src/components/calendar/calendar.variants.ts +426 -0
- package/src/components/calendar/index.ts +96 -0
- package/src/components/card/AGENTS.md +88 -0
- package/src/components/card/card-action.tsx +16 -0
- package/src/components/card/card-content.tsx +12 -0
- package/src/components/card/card-description.tsx +12 -0
- package/src/components/card/card-footer.tsx +30 -0
- package/src/components/card/card-header.tsx +45 -0
- package/src/components/card/card-title.tsx +25 -0
- package/src/components/card/card.context.tsx +64 -0
- package/src/components/card/card.tsx +94 -0
- package/src/components/card/card.types.ts +13 -0
- package/src/components/card/card.variants.test.ts +240 -0
- package/src/components/card/card.variants.ts +177 -0
- package/src/components/card/index.ts +15 -0
- package/src/components/chart/AGENTS.md +9 -1
- package/src/components/chart/chart.tsx +8 -1
- package/src/components/chip/AGENTS.md +113 -0
- package/src/components/chip/chip-close-button.tsx +48 -0
- package/src/components/chip/chip-end-content.tsx +15 -0
- package/src/components/chip/chip-label.tsx +21 -0
- package/src/components/chip/chip-start-content.tsx +16 -0
- package/src/components/chip/chip.context.tsx +66 -0
- package/src/components/chip/chip.tsx +268 -0
- package/src/components/chip/chip.types.ts +10 -0
- package/src/components/chip/chip.variants.test.ts +335 -0
- package/src/components/chip/chip.variants.ts +321 -0
- package/src/components/chip/index.ts +24 -0
- package/src/components/collapsible/AGENTS.md +97 -0
- package/src/components/collapsible/collapsible-content.tsx +97 -0
- package/src/components/collapsible/collapsible-description.tsx +17 -0
- package/src/components/collapsible/collapsible-indicator.tsx +92 -0
- package/src/components/collapsible/collapsible-title.tsx +17 -0
- package/src/components/collapsible/collapsible-trigger.tsx +133 -0
- package/src/components/collapsible/collapsible.context.tsx +91 -0
- package/src/components/collapsible/collapsible.tsx +167 -0
- package/src/components/collapsible/collapsible.types.ts +9 -0
- package/src/components/collapsible/collapsible.variants.test.ts +338 -0
- package/src/components/collapsible/collapsible.variants.ts +228 -0
- package/src/components/collapsible/index.ts +32 -0
- package/src/components/context-menu/AGENTS.md +103 -0
- package/src/components/context-menu/context-menu-content.tsx +64 -0
- package/src/components/context-menu/context-menu-item.tsx +22 -0
- package/src/components/context-menu/context-menu-preview.tsx +60 -0
- package/src/components/context-menu/context-menu-trigger.tsx +160 -0
- package/src/components/context-menu/context-menu.context.tsx +91 -0
- package/src/components/context-menu/context-menu.tsx +90 -0
- package/src/components/context-menu/context-menu.variants.test.ts +266 -0
- package/src/components/context-menu/context-menu.variants.ts +147 -0
- package/src/components/context-menu/index.ts +18 -0
- package/src/components/dialog/AGENTS.md +97 -0
- package/src/components/dialog/dialog-body.tsx +21 -0
- package/src/components/dialog/dialog-close.tsx +82 -0
- package/src/components/dialog/dialog-content.tsx +153 -0
- package/src/components/dialog/dialog-description.tsx +23 -0
- package/src/components/dialog/dialog-footer.tsx +32 -0
- package/src/components/dialog/dialog-header.tsx +20 -0
- package/src/components/dialog/dialog-title.tsx +42 -0
- package/src/components/dialog/dialog-trigger.tsx +54 -0
- package/src/components/dialog/dialog.context.tsx +90 -0
- package/src/components/dialog/dialog.tsx +119 -0
- package/src/components/dialog/dialog.variants.test.ts +224 -0
- package/src/components/dialog/dialog.variants.ts +173 -0
- package/src/components/dialog/index.ts +30 -0
- package/src/components/drawer/AGENTS.md +123 -0
- package/src/components/drawer/drawer-body.tsx +46 -0
- package/src/components/drawer/drawer-close.tsx +76 -0
- package/src/components/drawer/drawer-content.tsx +186 -0
- package/src/components/drawer/drawer-description.tsx +23 -0
- package/src/components/drawer/drawer-footer.tsx +21 -0
- package/src/components/drawer/drawer-header.tsx +42 -0
- package/src/components/drawer/drawer-title.tsx +35 -0
- package/src/components/drawer/drawer-trigger.tsx +54 -0
- package/src/components/drawer/drawer.context.tsx +114 -0
- package/src/components/drawer/drawer.tsx +119 -0
- package/src/components/drawer/drawer.variants.test.ts +392 -0
- package/src/components/drawer/drawer.variants.ts +244 -0
- package/src/components/drawer/index.ts +41 -0
- package/src/components/drawer/use-drawer-pan.ts +108 -0
- package/src/components/empty-state/AGENTS.md +90 -0
- package/src/components/empty-state/empty-state-content.tsx +17 -0
- package/src/components/empty-state/empty-state-description.tsx +12 -0
- package/src/components/empty-state/empty-state-header.tsx +17 -0
- package/src/components/empty-state/empty-state-media.tsx +56 -0
- package/src/components/empty-state/empty-state-title.tsx +18 -0
- package/src/components/empty-state/empty-state.context.tsx +65 -0
- package/src/components/empty-state/empty-state.tsx +84 -0
- package/src/components/empty-state/empty-state.types.ts +14 -0
- package/src/components/empty-state/empty-state.variants.test.ts +245 -0
- package/src/components/empty-state/empty-state.variants.ts +132 -0
- package/src/components/empty-state/index.ts +20 -0
- package/src/components/fab/AGENTS.md +119 -0
- package/src/components/fab/fab-action.tsx +118 -0
- package/src/components/fab/fab-group.tsx +214 -0
- package/src/components/fab/fab-label.tsx +18 -0
- package/src/components/fab/fab.context.tsx +125 -0
- package/src/components/fab/fab.tsx +156 -0
- package/src/components/fab/fab.types.ts +17 -0
- package/src/components/fab/fab.variants.test.ts +274 -0
- package/src/components/fab/fab.variants.ts +234 -0
- package/src/components/fab/index.ts +37 -0
- package/src/components/feedback/AGENTS.md +95 -0
- package/src/components/feedback/feedback-action.tsx +16 -0
- package/src/components/feedback/feedback-cancel.tsx +34 -0
- package/src/components/feedback/feedback-close.tsx +21 -0
- package/src/components/feedback/feedback-content.tsx +24 -0
- package/src/components/feedback/feedback-field.tsx +70 -0
- package/src/components/feedback/feedback-footer.tsx +21 -0
- package/src/components/feedback/feedback-panel.tsx +54 -0
- package/src/components/feedback/feedback-submit.tsx +74 -0
- package/src/components/feedback/feedback-title.tsx +34 -0
- package/src/components/feedback/feedback-trigger.tsx +20 -0
- package/src/components/feedback/feedback.context.tsx +73 -0
- package/src/components/feedback/feedback.tsx +135 -0
- package/src/components/feedback/feedback.variants.test.ts +185 -0
- package/src/components/feedback/feedback.variants.ts +128 -0
- package/src/components/feedback/index.ts +24 -0
- package/src/components/field/AGENTS.md +19 -0
- package/src/components/field/field-label.tsx +19 -5
- package/src/components/field/field.context.tsx +17 -0
- package/src/components/field/field.tsx +15 -2
- package/src/components/field/field.variants.test.ts +24 -0
- package/src/components/field/field.variants.ts +20 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/item/AGENTS.md +92 -0
- package/src/components/item/index.ts +25 -0
- package/src/components/item/item-actions.tsx +28 -0
- package/src/components/item/item-content.tsx +15 -0
- package/src/components/item/item-description.tsx +12 -0
- package/src/components/item/item-footer.tsx +13 -0
- package/src/components/item/item-group.tsx +32 -0
- package/src/components/item/item-header.tsx +13 -0
- package/src/components/item/item-media.tsx +37 -0
- package/src/components/item/item-separator.tsx +13 -0
- package/src/components/item/item-title.tsx +17 -0
- package/src/components/item/item.context.tsx +66 -0
- package/src/components/item/item.tsx +218 -0
- package/src/components/item/item.types.ts +12 -0
- package/src/components/item/item.variants.test.ts +263 -0
- package/src/components/item/item.variants.ts +200 -0
- package/src/components/kpi/AGENTS.md +125 -0
- package/src/components/kpi/index.ts +45 -0
- package/src/components/kpi/kpi-action.tsx +12 -0
- package/src/components/kpi/kpi-content.tsx +35 -0
- package/src/components/kpi/kpi-footer.tsx +52 -0
- package/src/components/kpi/kpi-group.tsx +70 -0
- package/src/components/kpi/kpi-header.tsx +25 -0
- package/src/components/kpi/kpi-icon.tsx +28 -0
- package/src/components/kpi/kpi-sparkline.tsx +145 -0
- package/src/components/kpi/kpi-stat.tsx +20 -0
- package/src/components/kpi/kpi-title.tsx +19 -0
- package/src/components/kpi/kpi-trend.tsx +105 -0
- package/src/components/kpi/kpi-value.tsx +32 -0
- package/src/components/kpi/kpi.context.tsx +111 -0
- package/src/components/kpi/kpi.tsx +154 -0
- package/src/components/kpi/kpi.types.ts +9 -0
- package/src/components/kpi/kpi.variants.test.ts +428 -0
- package/src/components/kpi/kpi.variants.ts +347 -0
- package/src/components/label/AGENTS.md +89 -0
- package/src/components/label/index.ts +10 -0
- package/src/components/label/label.tsx +97 -0
- package/src/components/label/label.variants.test.ts +176 -0
- package/src/components/label/label.variants.ts +123 -0
- package/src/components/menu/AGENTS.md +176 -0
- package/src/components/menu/index.ts +44 -0
- package/src/components/menu/menu-background.tsx +17 -0
- package/src/components/menu/menu-checkbox-item.tsx +57 -0
- package/src/components/menu/menu-content.tsx +303 -0
- package/src/components/menu/menu-item.tsx +162 -0
- package/src/components/menu/menu-label.tsx +14 -0
- package/src/components/menu/menu-radio-group.tsx +50 -0
- package/src/components/menu/menu-radio-item.tsx +57 -0
- package/src/components/menu/menu-separator.tsx +11 -0
- package/src/components/menu/menu-sub-content.tsx +72 -0
- package/src/components/menu/menu-sub-trigger.tsx +50 -0
- package/src/components/menu/menu-sub.tsx +75 -0
- package/src/components/menu/menu-trigger.tsx +57 -0
- package/src/components/menu/menu.context.tsx +214 -0
- package/src/components/menu/menu.tsx +88 -0
- package/src/components/menu/menu.types.ts +33 -0
- package/src/components/menu/menu.variants.test.ts +400 -0
- package/src/components/menu/menu.variants.ts +353 -0
- package/src/components/meter/AGENTS.md +135 -0
- package/src/components/meter/index.ts +43 -0
- package/src/components/meter/meter-fill.tsx +19 -0
- package/src/components/meter/meter-header.tsx +17 -0
- package/src/components/meter/meter-label.tsx +18 -0
- package/src/components/meter/meter-output.tsx +42 -0
- package/src/components/meter/meter-segments.tsx +83 -0
- package/src/components/meter/meter-track.tsx +16 -0
- package/src/components/meter/meter.context.tsx +62 -0
- package/src/components/meter/meter.tsx +286 -0
- package/src/components/meter/meter.types.ts +30 -0
- package/src/components/meter/meter.variants.test.ts +430 -0
- package/src/components/meter/meter.variants.ts +347 -0
- package/src/components/overlay/AGENTS.md +89 -0
- package/src/components/overlay/index.ts +27 -0
- package/src/components/overlay/overlay-portal.tsx +84 -0
- package/src/components/overlay/overlay-presence.test.ts +46 -0
- package/src/components/overlay/overlay-presence.ts +49 -0
- package/src/components/overlay/overlay-registry.test.ts +96 -0
- package/src/components/overlay/overlay-registry.ts +99 -0
- package/src/components/overlay/overlay-scrim.tsx +49 -0
- package/src/components/overlay/overlay.context.tsx +48 -0
- package/src/components/overlay/overlay.tsx +118 -0
- package/src/components/overlay/overlay.variants.test.ts +37 -0
- package/src/components/overlay/overlay.variants.ts +33 -0
- package/src/components/overlay/use-overlay-back-handler.ts +34 -0
- package/src/components/overlay/use-overlay-presence.ts +87 -0
- package/src/components/popover/AGENTS.md +138 -0
- package/src/components/popover/index.ts +48 -0
- package/src/components/popover/popover-anchor.tsx +49 -0
- package/src/components/popover/popover-arrow.tsx +89 -0
- package/src/components/popover/popover-close.tsx +73 -0
- package/src/components/popover/popover-content.tsx +235 -0
- package/src/components/popover/popover-description.tsx +18 -0
- package/src/components/popover/popover-title.tsx +33 -0
- package/src/components/popover/popover-trigger.tsx +56 -0
- package/src/components/popover/popover.context.tsx +71 -0
- package/src/components/popover/popover.position.test.ts +354 -0
- package/src/components/popover/popover.position.ts +286 -0
- package/src/components/popover/popover.tsx +123 -0
- package/src/components/popover/popover.variants.test.ts +135 -0
- package/src/components/popover/popover.variants.ts +85 -0
- package/src/components/popover/use-anchor-measure.ts +76 -0
- package/src/components/popover/use-anchored-content.ts +219 -0
- package/src/components/pressable/AGENTS.md +11 -1
- package/src/components/pressable/pressable.tsx +7 -2
- package/src/components/pressable/pressable.variants.test.ts +29 -0
- package/src/components/pressable/pressable.variants.ts +17 -0
- package/src/components/progress/AGENTS.md +120 -0
- package/src/components/progress/index.ts +40 -0
- package/src/components/progress/progress-fill.tsx +152 -0
- package/src/components/progress/progress-header.tsx +19 -0
- package/src/components/progress/progress-label.tsx +27 -0
- package/src/components/progress/progress-output.tsx +47 -0
- package/src/components/progress/progress-track.tsx +38 -0
- package/src/components/progress/progress.context.tsx +79 -0
- package/src/components/progress/progress.tsx +171 -0
- package/src/components/progress/progress.types.ts +23 -0
- package/src/components/progress/progress.variants.test.ts +335 -0
- package/src/components/progress/progress.variants.ts +284 -0
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/provider/AGENTS.md +38 -25
- package/src/components/provider/provider.tsx +38 -11
- package/src/components/radio/AGENTS.md +8 -10
- package/src/components/radio/radio.variants.test.ts +5 -6
- package/src/components/radio/radio.variants.ts +6 -7
- package/src/components/rating/AGENTS.md +143 -0
- package/src/components/rating/index.ts +43 -0
- package/src/components/rating/rating-output.tsx +39 -0
- package/src/components/rating/rating-star.tsx +70 -0
- package/src/components/rating/rating-stars.tsx +219 -0
- package/src/components/rating/rating.context.tsx +79 -0
- package/src/components/rating/rating.tsx +163 -0
- package/src/components/rating/rating.variants.test.ts +347 -0
- package/src/components/rating/rating.variants.ts +354 -0
- package/src/components/screen/screen-navbar-back-button.tsx +1 -1
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
- package/src/components/skeleton/AGENTS.md +120 -0
- package/src/components/skeleton/index.ts +15 -0
- package/src/components/skeleton/skeleton-clock.ts +48 -0
- package/src/components/skeleton/skeleton-group.tsx +64 -0
- package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
- package/src/components/skeleton/skeleton.context.tsx +45 -0
- package/src/components/skeleton/skeleton.tsx +227 -0
- package/src/components/skeleton/skeleton.variants.test.ts +245 -0
- package/src/components/skeleton/skeleton.variants.ts +222 -0
- package/src/components/slide-button/AGENTS.md +122 -0
- package/src/components/slide-button/index.ts +43 -0
- package/src/components/slide-button/slide-button-label.tsx +70 -0
- package/src/components/slide-button/slide-button-thumb.tsx +93 -0
- package/src/components/slide-button/slide-button.context.tsx +76 -0
- package/src/components/slide-button/slide-button.tsx +457 -0
- package/src/components/slide-button/slide-button.variants.test.ts +288 -0
- package/src/components/slide-button/slide-button.variants.ts +324 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider-thumb.tsx +33 -1
- package/src/components/slider/slider.variants.test.ts +52 -0
- package/src/components/slider/slider.variants.ts +56 -2
- package/src/components/stack-card/AGENTS.md +169 -0
- package/src/components/stack-card/index.ts +37 -0
- package/src/components/stack-card/stack-card-action.tsx +66 -0
- package/src/components/stack-card/stack-card-actions.tsx +18 -0
- package/src/components/stack-card/stack-card-card.tsx +71 -0
- package/src/components/stack-card/stack-card-empty.tsx +30 -0
- package/src/components/stack-card/stack-card-slot.tsx +90 -0
- package/src/components/stack-card/stack-card-stamp.tsx +76 -0
- package/src/components/stack-card/stack-card.context.tsx +117 -0
- package/src/components/stack-card/stack-card.tsx +557 -0
- package/src/components/stack-card/stack-card.types.ts +26 -0
- package/src/components/stack-card/stack-card.variants.test.ts +355 -0
- package/src/components/stack-card/stack-card.variants.ts +360 -0
- package/src/components/steps/AGENTS.md +153 -0
- package/src/components/steps/index.ts +45 -0
- package/src/components/steps/steps-connector.tsx +31 -0
- package/src/components/steps/steps-description.tsx +26 -0
- package/src/components/steps/steps-indicator.tsx +106 -0
- package/src/components/steps/steps-item.tsx +225 -0
- package/src/components/steps/steps-panel.tsx +41 -0
- package/src/components/steps/steps-title.tsx +32 -0
- package/src/components/steps/steps-track.tsx +59 -0
- package/src/components/steps/steps.context.tsx +123 -0
- package/src/components/steps/steps.tsx +166 -0
- package/src/components/steps/steps.variants.test.ts +388 -0
- package/src/components/steps/steps.variants.ts +468 -0
- package/src/components/surface/AGENTS.md +72 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +54 -0
- package/src/components/surface/surface.tsx +70 -0
- package/src/components/surface/surface.variants.test.ts +180 -0
- package/src/components/surface/surface.variants.ts +133 -0
- package/src/components/swipe/AGENTS.md +140 -0
- package/src/components/swipe/index.ts +31 -0
- package/src/components/swipe/swipe-action.tsx +99 -0
- package/src/components/swipe/swipe-end.tsx +14 -0
- package/src/components/swipe/swipe-group.tsx +55 -0
- package/src/components/swipe/swipe-panel.tsx +55 -0
- package/src/components/swipe/swipe-start.tsx +15 -0
- package/src/components/swipe/swipe.context.tsx +135 -0
- package/src/components/swipe/swipe.tsx +393 -0
- package/src/components/swipe/swipe.types.ts +7 -0
- package/src/components/swipe/swipe.variants.test.ts +337 -0
- package/src/components/swipe/swipe.variants.ts +290 -0
- package/src/components/switch/switch.variants.ts +2 -2
- package/src/components/tabs/AGENTS.md +4 -3
- package/src/components/tabs/tabs.variants.test.ts +1 -1
- package/src/components/tabs/tabs.variants.ts +4 -6
- package/src/components/textarea/AGENTS.md +86 -0
- package/src/components/textarea/index.ts +16 -0
- package/src/components/textarea/textarea.tsx +157 -0
- package/src/components/textarea/textarea.variants.test.ts +219 -0
- package/src/components/textarea/textarea.variants.ts +171 -0
- package/src/components/toast/AGENTS.md +140 -0
- package/src/components/toast/index.ts +74 -0
- package/src/components/toast/toast-action.tsx +59 -0
- package/src/components/toast/toast-api.ts +32 -0
- package/src/components/toast/toast-close.tsx +44 -0
- package/src/components/toast/toast-content.tsx +11 -0
- package/src/components/toast/toast-description.tsx +11 -0
- package/src/components/toast/toast-indicator.tsx +34 -0
- package/src/components/toast/toast-item.tsx +308 -0
- package/src/components/toast/toast-title.tsx +13 -0
- package/src/components/toast/toast-viewport.tsx +135 -0
- package/src/components/toast/toast.context.tsx +69 -0
- package/src/components/toast/toast.store.test.ts +259 -0
- package/src/components/toast/toast.store.ts +338 -0
- package/src/components/toast/toast.timer.test.ts +64 -0
- package/src/components/toast/toast.timer.ts +51 -0
- package/src/components/toast/toast.tsx +132 -0
- package/src/components/toast/toast.variants.test.ts +393 -0
- package/src/components/toast/toast.variants.ts +304 -0
- package/src/components/toggle-button/AGENTS.md +106 -0
- package/src/components/toggle-button/index.ts +30 -0
- package/src/components/toggle-button/toggle-button-group.tsx +156 -0
- package/src/components/toggle-button/toggle-button-label.tsx +19 -0
- package/src/components/toggle-button/toggle-button.context.tsx +130 -0
- package/src/components/toggle-button/toggle-button.tsx +172 -0
- package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
- package/src/components/toggle-button/toggle-button.variants.ts +160 -0
- package/src/components/tooltip/AGENTS.md +133 -0
- package/src/components/tooltip/index.ts +28 -0
- package/src/components/tooltip/tooltip-arrow.tsx +40 -0
- package/src/components/tooltip/tooltip-content.tsx +179 -0
- package/src/components/tooltip/tooltip-description.tsx +19 -0
- package/src/components/tooltip/tooltip-text.tsx +21 -0
- package/src/components/tooltip/tooltip-title.tsx +20 -0
- package/src/components/tooltip/tooltip-trigger.tsx +103 -0
- package/src/components/tooltip/tooltip.context.tsx +68 -0
- package/src/components/tooltip/tooltip.tsx +213 -0
- package/src/components/tooltip/tooltip.variants.test.ts +231 -0
- package/src/components/tooltip/tooltip.variants.ts +177 -0
- package/src/hooks/use-calm-motion.tsx +31 -0
- package/src/lib/calm-motion.test.ts +17 -0
- package/src/lib/calm-motion.ts +23 -0
- package/src/lib/cn.test.ts +14 -0
- package/src/lib/cn.ts +2 -2
- package/src/no-gorhom.test.ts +54 -0
- package/src/styles/geometry.test.ts +1 -0
- package/src/styles/theme.css +4 -0
- package/src/styles/tokens.css +11 -0
- package/src/styles/tokens.test.ts +22 -0
- package/src/styles/tokens.ts +20 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { declaredTokens } from "../../styles/theme-tokens.test";
|
|
3
|
+
import { ICON_SIZE_TOKENS } from "../../styles/tokens";
|
|
4
|
+
import {
|
|
5
|
+
AVATAR_BADGE_PLACEMENTS,
|
|
6
|
+
AVATAR_COLORS,
|
|
7
|
+
AVATAR_FOREGROUND_TOKEN,
|
|
8
|
+
AVATAR_SIZE_POINTS,
|
|
9
|
+
AVATAR_SIZES,
|
|
10
|
+
AVATAR_VARIANTS,
|
|
11
|
+
avatarVariants,
|
|
12
|
+
resolveAvatarInteractive,
|
|
13
|
+
resolveAvatarOverlap,
|
|
14
|
+
resolveAvatarSize,
|
|
15
|
+
} from "./avatar.variants";
|
|
16
|
+
|
|
17
|
+
const LIGHT = declaredTokens("light");
|
|
18
|
+
const DARK = declaredTokens("dark");
|
|
19
|
+
|
|
20
|
+
/** The `size-N` step a class string sets its edge from, in points (Tailwind's 4pt spacing unit). */
|
|
21
|
+
function edgeOf(cls: string): number {
|
|
22
|
+
return Number(cls.match(/\bsize-(\d+)\b/)?.[1]) * 4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The `text-*` colour token a class string resolves to, minus the prefix. */
|
|
26
|
+
function colorToken(cls: string): string | undefined {
|
|
27
|
+
return cls.match(/\btext-((?:[a-z]+-)*(?:foreground|background))\b/)?.[1];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The `bg-*` token a class string fills with, minus the prefix. */
|
|
31
|
+
function fillToken(cls: string): string | undefined {
|
|
32
|
+
return cls.match(/\bbg-([a-z-]+)\b/)?.[1];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Position of a slot's `size-icon-*` token on the shared icon scale. */
|
|
36
|
+
function iconStep(cls: string): number {
|
|
37
|
+
const token = cls.match(/\bsize-(icon-[\w-]+)\b/)?.[1];
|
|
38
|
+
return ICON_SIZE_TOKENS.indexOf(token as (typeof ICON_SIZE_TOKENS)[number]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const TEXT_SCALE = ["text-xs", "text-sm", "text-base", "text-lg", "text-xl", "text-2xl"] as const;
|
|
42
|
+
function textStep(cls: string): number {
|
|
43
|
+
return TEXT_SCALE.findIndex((step) => new RegExp(`\\b${step}\\b`).test(cls));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("sizes", () => {
|
|
47
|
+
test("the face's class and AVATAR_SIZE_POINTS agree at every size", () => {
|
|
48
|
+
// The group computes its overlap in points; a class that drifted from the
|
|
49
|
+
// number would slide faces too far or not far enough.
|
|
50
|
+
for (const size of AVATAR_SIZES) {
|
|
51
|
+
expect(edgeOf(avatarVariants({ size }).root())).toBe(AVATAR_SIZE_POINTS[size]);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("the scale ascends", () => {
|
|
56
|
+
const points = AVATAR_SIZES.map((size) => AVATAR_SIZE_POINTS[size]);
|
|
57
|
+
expect([...points].sort((a, b) => a - b)).toEqual(points);
|
|
58
|
+
expect(new Set(points).size).toBe(points.length);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("the fallback text and glyph grow with the face", () => {
|
|
62
|
+
const text = AVATAR_SIZES.map((size) => textStep(avatarVariants({ size }).fallbackLabel()));
|
|
63
|
+
const icons = AVATAR_SIZES.map((size) => iconStep(avatarVariants({ size }).icon()));
|
|
64
|
+
for (const step of [...text, ...icons]) expect(step).toBeGreaterThanOrEqual(0);
|
|
65
|
+
expect([...text].sort((a, b) => a - b)).toEqual(text);
|
|
66
|
+
expect([...icons].sort((a, b) => a - b)).toEqual(icons);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("the overflow tile is the same edge as a face", () => {
|
|
70
|
+
for (const size of AVATAR_SIZES) {
|
|
71
|
+
const slots = avatarVariants({ size });
|
|
72
|
+
expect(edgeOf(slots.overflow())).toBe(edgeOf(slots.root()));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("colour matrix", () => {
|
|
78
|
+
test("every variant × colour cell is distinct", () => {
|
|
79
|
+
const seen = new Set<string>();
|
|
80
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
81
|
+
for (const color of AVATAR_COLORS) {
|
|
82
|
+
const slots = avatarVariants({ variant, color });
|
|
83
|
+
seen.add(`${slots.face()}|${slots.fallbackLabel()}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
expect(seen.size).toBe(AVATAR_VARIANTS.length * AVATAR_COLORS.length);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("every cell fills its face and colours its label", () => {
|
|
90
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
91
|
+
for (const color of AVATAR_COLORS) {
|
|
92
|
+
const slots = avatarVariants({ variant, color });
|
|
93
|
+
expect(fillToken(slots.face())).toBeDefined();
|
|
94
|
+
expect(colorToken(slots.fallbackLabel())).toBeDefined();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("AVATAR_FOREGROUND_TOKEN names the token the label resolves to", () => {
|
|
100
|
+
// A glyph fallback and a text fallback must be the same shade.
|
|
101
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
102
|
+
for (const color of AVATAR_COLORS) {
|
|
103
|
+
expect(AVATAR_FOREGROUND_TOKEN[variant][color]).toBe(
|
|
104
|
+
colorToken(avatarVariants({ variant, color }).fallbackLabel()) ?? ""
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("every token the matrix names is declared in both themes", () => {
|
|
111
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
112
|
+
for (const color of AVATAR_COLORS) {
|
|
113
|
+
const slots = avatarVariants({ variant, color });
|
|
114
|
+
for (const token of [fillToken(slots.face()), colorToken(slots.fallbackLabel())]) {
|
|
115
|
+
expect(LIGHT.has(token ?? "")).toBe(true);
|
|
116
|
+
expect(DARK.has(token ?? "")).toBe(true);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("structure", () => {
|
|
124
|
+
test("the face clips and the root does not", () => {
|
|
125
|
+
// A badge pinned to the corner hangs outside the circle; clipping the root
|
|
126
|
+
// would cut it in half.
|
|
127
|
+
const slots = avatarVariants();
|
|
128
|
+
expect(slots.face()).toContain("overflow-hidden");
|
|
129
|
+
expect(slots.face()).toContain("rounded-full");
|
|
130
|
+
expect(slots.root()).not.toContain("overflow-hidden");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("the root holds a fixed edge and never overrides its parent's alignment", () => {
|
|
134
|
+
// `self-start` pinned every avatar in an `items-end` or `items-center` row
|
|
135
|
+
// to its top. A fixed size is what keeps a column from stretching it.
|
|
136
|
+
for (const size of AVATAR_SIZES) {
|
|
137
|
+
const slots = avatarVariants({ size });
|
|
138
|
+
expect(slots.root()).not.toMatch(/\bself-/);
|
|
139
|
+
expect(slots.face()).toContain("size-full");
|
|
140
|
+
}
|
|
141
|
+
expect(avatarVariants().group()).not.toMatch(/\bself-/);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("no view slot carries a text colour", () => {
|
|
145
|
+
// A React Native View does not cascade colour to a Text descendant.
|
|
146
|
+
const slots = avatarVariants();
|
|
147
|
+
for (const slot of [slots.root(), slots.face(), slots.badge(), slots.group(), slots.groupItem()]) {
|
|
148
|
+
expect(colorToken(slot)).toBeUndefined();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a disabled avatar fades", () => {
|
|
153
|
+
expect(avatarVariants({ isDisabled: true }).root()).toContain("opacity-50");
|
|
154
|
+
expect(avatarVariants({ isDisabled: false }).root()).not.toContain("opacity-50");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("a group ring is the page background, so it reads on any surface", () => {
|
|
158
|
+
// The overflow tile is wrapped in the same item, so it wears the same ring
|
|
159
|
+
// and lines up with the faces rather than sitting four points smaller.
|
|
160
|
+
expect(avatarVariants().groupItem()).toContain("border-background");
|
|
161
|
+
expect(avatarVariants().overflow()).not.toContain("border-background");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("every badge placement pins to a different corner", () => {
|
|
165
|
+
const classes = AVATAR_BADGE_PLACEMENTS.map((placement) => avatarVariants({ placement }).badge());
|
|
166
|
+
expect(new Set(classes).size).toBe(AVATAR_BADGE_PLACEMENTS.length);
|
|
167
|
+
for (const cls of classes) expect(cls).toContain("absolute");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("the presence dot grows with the face", () => {
|
|
171
|
+
const dots = AVATAR_SIZES.map((size) =>
|
|
172
|
+
Number(
|
|
173
|
+
avatarVariants({ size })
|
|
174
|
+
.dot()
|
|
175
|
+
.match(/\bsize-([\d.]+)\b/)?.[1]
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
for (const dot of dots) expect(Number.isFinite(dot)).toBe(true);
|
|
179
|
+
expect([...dots].sort((a, b) => a - b)).toEqual(dots);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("the neutral edge", () => {
|
|
184
|
+
test("a default-colour fallback carries a hairline border in both variants", () => {
|
|
185
|
+
// Its fill sits a percent or two from the page in light, so without an
|
|
186
|
+
// edge a neutral face vanishes into the background.
|
|
187
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
188
|
+
expect(avatarVariants({ variant, color: "default", hasImage: false }).face()).toContain("border-border");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("the border goes once a photo is on top", () => {
|
|
193
|
+
for (const variant of AVATAR_VARIANTS) {
|
|
194
|
+
expect(avatarVariants({ variant, color: "default", hasImage: true }).face()).not.toMatch(/\bborder\b/);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("a coloured fallback needs no edge", () => {
|
|
199
|
+
for (const color of AVATAR_COLORS.filter((value) => value !== "default")) {
|
|
200
|
+
expect(avatarVariants({ color, hasImage: false }).face()).not.toMatch(/\bborder\b/);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("the overflow tile carries it too", () => {
|
|
205
|
+
expect(avatarVariants().overflow()).toContain("border-border");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("resolveAvatarSize", () => {
|
|
210
|
+
test("the avatar's own size wins", () => {
|
|
211
|
+
expect(resolveAvatarSize({ size: "sm", groupSize: "xl" })).toBe("sm");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("then the enclosing group's", () => {
|
|
215
|
+
expect(resolveAvatarSize({ groupSize: "lg" })).toBe("lg");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("then md", () => {
|
|
219
|
+
expect(resolveAvatarSize({})).toBe("md");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("resolveAvatarOverlap", () => {
|
|
224
|
+
test("defaults to a third of the face, rounded", () => {
|
|
225
|
+
for (const size of AVATAR_SIZES) {
|
|
226
|
+
expect(resolveAvatarOverlap({ size })).toBe(Math.round(AVATAR_SIZE_POINTS[size] / 3));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("an explicit overlap wins, and 0 closes the stack into a row", () => {
|
|
231
|
+
expect(resolveAvatarOverlap({ size: "md", overlap: 6 })).toBe(6);
|
|
232
|
+
expect(resolveAvatarOverlap({ size: "md", overlap: 0 })).toBe(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("never slides a face further than its own edge, nor backwards", () => {
|
|
236
|
+
expect(resolveAvatarOverlap({ size: "md", overlap: 999 })).toBe(AVATAR_SIZE_POINTS.md);
|
|
237
|
+
expect(resolveAvatarOverlap({ size: "md", overlap: -4 })).toBe(0);
|
|
238
|
+
expect(resolveAvatarOverlap({ size: "md", overlap: Number.NaN })).toBe(Math.round(AVATAR_SIZE_POINTS.md / 3));
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("resolveAvatarInteractive", () => {
|
|
243
|
+
test("is inert with no handler", () => {
|
|
244
|
+
expect(resolveAvatarInteractive({})).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("becomes pressable with either handler", () => {
|
|
248
|
+
expect(resolveAvatarInteractive({ onPress: () => {} })).toBe(true);
|
|
249
|
+
expect(resolveAvatarInteractive({ onLongPress: () => {} })).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { VariantProps } from "tailwind-variants";
|
|
2
|
+
import { tv } from "../../lib/tv";
|
|
3
|
+
|
|
4
|
+
/** How the fallback surface is painted. Orthogonal to {@link AVATAR_COLORS}. */
|
|
5
|
+
export const AVATAR_VARIANTS = ["soft", "solid"] as const;
|
|
6
|
+
|
|
7
|
+
/** What the fallback surface means. The same six a `Badge` takes, so the two pair. */
|
|
8
|
+
export const AVATAR_COLORS = ["default", "primary", "success", "warning", "destructive", "info"] as const;
|
|
9
|
+
|
|
10
|
+
export const AVATAR_SIZES = ["sm", "md", "lg", "xl"] as const;
|
|
11
|
+
|
|
12
|
+
/** Where an `Avatar.Badge` is pinned. */
|
|
13
|
+
export const AVATAR_BADGE_PLACEMENTS = ["top-right", "bottom-right"] as const;
|
|
14
|
+
|
|
15
|
+
export type AvatarVariant = (typeof AVATAR_VARIANTS)[number];
|
|
16
|
+
export type AvatarColor = (typeof AVATAR_COLORS)[number];
|
|
17
|
+
export type AvatarSize = (typeof AVATAR_SIZES)[number];
|
|
18
|
+
export type AvatarBadgePlacement = (typeof AVATAR_BADGE_PLACEMENTS)[number];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A face's edge in points, at each size.
|
|
22
|
+
*
|
|
23
|
+
* The one place a size is a number rather than a class, and it exists because
|
|
24
|
+
* `Avatar.Group` has to compute an overlap: a negative margin set at runtime
|
|
25
|
+
* cannot be a class (Tailwind's scanner is static). A test pins every entry to
|
|
26
|
+
* the `size-*` class the `face` slot resolves to, so the two cannot drift.
|
|
27
|
+
*/
|
|
28
|
+
export const AVATAR_SIZE_POINTS: Record<AvatarSize, number> = {
|
|
29
|
+
sm: 32,
|
|
30
|
+
md: 40,
|
|
31
|
+
lg: 48,
|
|
32
|
+
xl: 64,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Theme token that gives the fallback glyph its colour.
|
|
37
|
+
*
|
|
38
|
+
* The avatar's counterpart to `BADGE_FOREGROUND_TOKEN`: a colour that has to
|
|
39
|
+
* reach an SVG paint prop cannot be a class. Every entry names the token the
|
|
40
|
+
* `fallbackLabel` slot resolves to, so initials and the glyph that stands in for
|
|
41
|
+
* them are the same shade — a test asserts the pair.
|
|
42
|
+
*/
|
|
43
|
+
export const AVATAR_FOREGROUND_TOKEN: Record<AvatarVariant, Record<AvatarColor, string>> = {
|
|
44
|
+
soft: {
|
|
45
|
+
default: "muted-foreground",
|
|
46
|
+
primary: "tertiary-foreground",
|
|
47
|
+
success: "success-soft-foreground",
|
|
48
|
+
warning: "warning-soft-foreground",
|
|
49
|
+
destructive: "destructive-soft-foreground",
|
|
50
|
+
info: "info-soft-foreground",
|
|
51
|
+
},
|
|
52
|
+
solid: {
|
|
53
|
+
default: "secondary-foreground",
|
|
54
|
+
primary: "primary-foreground",
|
|
55
|
+
success: "success-foreground",
|
|
56
|
+
warning: "warning-foreground",
|
|
57
|
+
destructive: "destructive-foreground",
|
|
58
|
+
info: "info-foreground",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Styling for every part of an avatar, and of a group of them.
|
|
64
|
+
*
|
|
65
|
+
* **The root carries the edge, not `self-start`.** A fixed width and height is
|
|
66
|
+
* something Yoga's `stretch` never overrides, so an avatar in a column keeps its
|
|
67
|
+
* size without the escape hatch `Badge` needs — and, unlike `self-start`, it
|
|
68
|
+
* leaves the parent's own alignment alone. `self-start` pinned every avatar in
|
|
69
|
+
* an `items-end` or `items-center` row to the top of it: mixed sizes lost their
|
|
70
|
+
* baseline and a list row's avatar sat above its text. The face fills the root.
|
|
71
|
+
*
|
|
72
|
+
* **Two boxes, not one.** `root` is unclipped and `face` is the clipped circle
|
|
73
|
+
* inside it. An `Avatar.Badge` hangs over the circle's edge, and a single
|
|
74
|
+
* clipped box would cut it in half; a test asserts `overflow-hidden` is on the
|
|
75
|
+
* face and never on the root.
|
|
76
|
+
*
|
|
77
|
+
* **The fallback is always painted; the image goes on top.** So the fill and
|
|
78
|
+
* the initials show while the image loads and after it fails, and a slow or dead
|
|
79
|
+
* URL never leaves an empty circle.
|
|
80
|
+
*
|
|
81
|
+
* **A size is an edge, and it is a fixed one** — unlike a badge, whose size is
|
|
82
|
+
* padding. A face lines up against other faces in a group and against rows in a
|
|
83
|
+
* list, and must not grow with OS font scaling; the initials are one or two
|
|
84
|
+
* glyphs and are sized to fit the smallest step. {@link AVATAR_SIZE_POINTS} is
|
|
85
|
+
* the same scale as a number, for the group's overlap.
|
|
86
|
+
*
|
|
87
|
+
* **The ring in a group is the page background** — `border-background` on the
|
|
88
|
+
* wrapper around each face, never on the face itself, so a face in a group is the
|
|
89
|
+
* same edge as one outside it and the stack reads as separate people on any
|
|
90
|
+
* surface. The overflow tile sits in the same wrapper for the same reason.
|
|
91
|
+
*
|
|
92
|
+
* **A neutral face has an edge.** `muted` and `secondary` sit a percent or two
|
|
93
|
+
* from the page in light, so a `default` fallback and the `+N` tile draw a
|
|
94
|
+
* hairline `border-border` — inside the box, so no size moves. It goes while a
|
|
95
|
+
* photo is mounted (`hasImage`), because a photo carries its own edge and a
|
|
96
|
+
* grey line around a face reads as a frame.
|
|
97
|
+
*
|
|
98
|
+
* The neutral end of the colour matrix reuses the fills `Badge` does — `muted`
|
|
99
|
+
* and `tertiary` for `soft`, `secondary` for a `solid` default — so an avatar
|
|
100
|
+
* beside a badge of the same colour reads as one family.
|
|
101
|
+
*
|
|
102
|
+
* No view slot holds a `text-*` utility: a React Native `View` does not cascade
|
|
103
|
+
* colour to a `Text` descendant.
|
|
104
|
+
*
|
|
105
|
+
* Free of React Native imports so it stays unit-testable. See AGENTS.md.
|
|
106
|
+
*/
|
|
107
|
+
export const avatarVariants = tv({
|
|
108
|
+
slots: {
|
|
109
|
+
root: "relative",
|
|
110
|
+
face: "size-full items-center justify-center overflow-hidden rounded-full",
|
|
111
|
+
image: "absolute inset-0",
|
|
112
|
+
fallbackLabel: "text-center font-medium",
|
|
113
|
+
/** Edge length the fallback glyph inherits. */
|
|
114
|
+
icon: "",
|
|
115
|
+
badge: "absolute items-center justify-center",
|
|
116
|
+
dot: "rounded-full border-2 border-background",
|
|
117
|
+
group: "flex-row items-center",
|
|
118
|
+
groupItem: "rounded-full border-2 border-background",
|
|
119
|
+
overflow: "items-center justify-center overflow-hidden rounded-full border border-border bg-muted",
|
|
120
|
+
overflowLabel: "text-center font-medium text-muted-foreground",
|
|
121
|
+
},
|
|
122
|
+
variants: {
|
|
123
|
+
variant: {
|
|
124
|
+
soft: {},
|
|
125
|
+
solid: {},
|
|
126
|
+
},
|
|
127
|
+
// `color` alone paints only the presence dot; the face needs `variant`
|
|
128
|
+
// too, so its twelve cells live in `compoundVariants`.
|
|
129
|
+
color: {
|
|
130
|
+
default: { dot: "bg-muted-foreground" },
|
|
131
|
+
primary: { dot: "bg-primary" },
|
|
132
|
+
success: { dot: "bg-success" },
|
|
133
|
+
warning: { dot: "bg-warning" },
|
|
134
|
+
destructive: { dot: "bg-destructive" },
|
|
135
|
+
info: { dot: "bg-info" },
|
|
136
|
+
},
|
|
137
|
+
size: {
|
|
138
|
+
sm: {
|
|
139
|
+
root: "size-8",
|
|
140
|
+
fallbackLabel: "text-xs",
|
|
141
|
+
icon: "size-icon-sm",
|
|
142
|
+
dot: "size-2.5",
|
|
143
|
+
overflow: "size-8",
|
|
144
|
+
overflowLabel: "text-xs",
|
|
145
|
+
},
|
|
146
|
+
md: {
|
|
147
|
+
root: "size-10",
|
|
148
|
+
fallbackLabel: "text-sm",
|
|
149
|
+
icon: "size-icon-lg",
|
|
150
|
+
dot: "size-3",
|
|
151
|
+
overflow: "size-10",
|
|
152
|
+
overflowLabel: "text-sm",
|
|
153
|
+
},
|
|
154
|
+
lg: {
|
|
155
|
+
root: "size-12",
|
|
156
|
+
fallbackLabel: "text-base",
|
|
157
|
+
icon: "size-icon-xl",
|
|
158
|
+
dot: "size-3.5",
|
|
159
|
+
overflow: "size-12",
|
|
160
|
+
overflowLabel: "text-base",
|
|
161
|
+
},
|
|
162
|
+
xl: {
|
|
163
|
+
root: "size-16",
|
|
164
|
+
fallbackLabel: "text-xl",
|
|
165
|
+
icon: "size-icon-2xl",
|
|
166
|
+
dot: "size-4",
|
|
167
|
+
overflow: "size-16",
|
|
168
|
+
overflowLabel: "text-xl",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
placement: {
|
|
172
|
+
"top-right": { badge: "-top-1 -right-1" },
|
|
173
|
+
"bottom-right": { badge: "-bottom-0.5 -right-0.5" },
|
|
174
|
+
},
|
|
175
|
+
// Whether a photo is mounted over the fallback. Read only by the neutral
|
|
176
|
+
// edge below — a photo carries its own edge.
|
|
177
|
+
hasImage: { true: {}, false: {} },
|
|
178
|
+
// The empty `false` branch is load-bearing typing, not a placeholder.
|
|
179
|
+
// See the note in button.variants.ts.
|
|
180
|
+
isDisabled: { true: { root: "opacity-50" }, false: {} },
|
|
181
|
+
},
|
|
182
|
+
compoundVariants: [
|
|
183
|
+
{ variant: "soft", color: "default", class: { face: "bg-muted", fallbackLabel: "text-muted-foreground" } },
|
|
184
|
+
{ variant: "soft", color: "primary", class: { face: "bg-tertiary", fallbackLabel: "text-tertiary-foreground" } },
|
|
185
|
+
{
|
|
186
|
+
variant: "soft",
|
|
187
|
+
color: "success",
|
|
188
|
+
class: { face: "bg-success-soft", fallbackLabel: "text-success-soft-foreground" },
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
variant: "soft",
|
|
192
|
+
color: "warning",
|
|
193
|
+
class: { face: "bg-warning-soft", fallbackLabel: "text-warning-soft-foreground" },
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
variant: "soft",
|
|
197
|
+
color: "destructive",
|
|
198
|
+
class: { face: "bg-destructive-soft", fallbackLabel: "text-destructive-soft-foreground" },
|
|
199
|
+
},
|
|
200
|
+
{ variant: "soft", color: "info", class: { face: "bg-info-soft", fallbackLabel: "text-info-soft-foreground" } },
|
|
201
|
+
|
|
202
|
+
{
|
|
203
|
+
variant: "solid",
|
|
204
|
+
color: "default",
|
|
205
|
+
class: { face: "bg-secondary", fallbackLabel: "text-secondary-foreground" },
|
|
206
|
+
},
|
|
207
|
+
{ variant: "solid", color: "primary", class: { face: "bg-primary", fallbackLabel: "text-primary-foreground" } },
|
|
208
|
+
{ variant: "solid", color: "success", class: { face: "bg-success", fallbackLabel: "text-success-foreground" } },
|
|
209
|
+
{ variant: "solid", color: "warning", class: { face: "bg-warning", fallbackLabel: "text-warning-foreground" } },
|
|
210
|
+
{
|
|
211
|
+
variant: "solid",
|
|
212
|
+
color: "destructive",
|
|
213
|
+
class: { face: "bg-destructive", fallbackLabel: "text-destructive-foreground" },
|
|
214
|
+
},
|
|
215
|
+
{ variant: "solid", color: "info", class: { face: "bg-info", fallbackLabel: "text-info-foreground" } },
|
|
216
|
+
|
|
217
|
+
{ color: "default", hasImage: false, class: { face: "border border-border" } },
|
|
218
|
+
],
|
|
219
|
+
defaultVariants: {
|
|
220
|
+
variant: "soft",
|
|
221
|
+
color: "default",
|
|
222
|
+
size: "md",
|
|
223
|
+
placement: "top-right",
|
|
224
|
+
hasImage: false,
|
|
225
|
+
isDisabled: false,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* An avatar's size: its own, else its group's, else `md`.
|
|
231
|
+
*
|
|
232
|
+
* A group sets the size for every face in it, and a child that states its own
|
|
233
|
+
* still wins — the order a caller reading the JSX would expect.
|
|
234
|
+
*/
|
|
235
|
+
export function resolveAvatarSize({ size, groupSize }: { size?: AvatarSize; groupSize?: AvatarSize }): AvatarSize {
|
|
236
|
+
return size ?? groupSize ?? "md";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Points each face in a group slides under the one before it.
|
|
241
|
+
*
|
|
242
|
+
* Defaults to a third of the face's edge, which leaves enough of each face to
|
|
243
|
+
* recognise. An explicit value wins — `0` closes the stack into a plain row —
|
|
244
|
+
* and is clamped between nothing and a whole face, so a typo cannot stack every
|
|
245
|
+
* face on the first or push them apart.
|
|
246
|
+
*/
|
|
247
|
+
export function resolveAvatarOverlap({ size, overlap }: { size: AvatarSize; overlap?: number }): number {
|
|
248
|
+
const edge = AVATAR_SIZE_POINTS[size];
|
|
249
|
+
if (overlap === undefined || Number.isNaN(overlap)) return Math.round(edge / 3);
|
|
250
|
+
return Math.min(edge, Math.max(0, overlap));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Whether the root should render as a `Pressable` rather than a plain `View`.
|
|
255
|
+
*
|
|
256
|
+
* The rule `Badge` follows, for the reason it does: an avatar is content until a
|
|
257
|
+
* caller gives it something to do, and a list of fifty should not mount fifty
|
|
258
|
+
* gesture detectors or announce fifty buttons with no action.
|
|
259
|
+
*/
|
|
260
|
+
export function resolveAvatarInteractive({
|
|
261
|
+
onPress,
|
|
262
|
+
onLongPress,
|
|
263
|
+
}: {
|
|
264
|
+
onPress?: () => void;
|
|
265
|
+
onLongPress?: () => void;
|
|
266
|
+
}): boolean {
|
|
267
|
+
return onPress !== undefined || onLongPress !== undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export type AvatarVariantProps = VariantProps<typeof avatarVariants>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export { Avatar, type AvatarProps } from "./avatar";
|
|
2
|
+
export {
|
|
3
|
+
type AvatarContextValue,
|
|
4
|
+
type AvatarGroupContextValue,
|
|
5
|
+
AvatarProvider,
|
|
6
|
+
useAvatar,
|
|
7
|
+
useAvatarContext,
|
|
8
|
+
useAvatarGroupContext,
|
|
9
|
+
} from "./avatar.context";
|
|
10
|
+
export {
|
|
11
|
+
AVATAR_OVERFLOW_DISPLAY_MAX,
|
|
12
|
+
type AvatarSource,
|
|
13
|
+
type AvatarSourceEntry,
|
|
14
|
+
resolveAvatarAccessibilityLabel,
|
|
15
|
+
resolveAvatarGroup,
|
|
16
|
+
resolveAvatarInitials,
|
|
17
|
+
resolveAvatarOverflowLabel,
|
|
18
|
+
resolveAvatarShowsImage,
|
|
19
|
+
resolveAvatarSourceKey,
|
|
20
|
+
} from "./avatar.utils";
|
|
21
|
+
export {
|
|
22
|
+
AVATAR_BADGE_PLACEMENTS,
|
|
23
|
+
AVATAR_COLORS,
|
|
24
|
+
AVATAR_FOREGROUND_TOKEN,
|
|
25
|
+
AVATAR_SIZE_POINTS,
|
|
26
|
+
AVATAR_SIZES,
|
|
27
|
+
AVATAR_VARIANTS,
|
|
28
|
+
type AvatarBadgePlacement,
|
|
29
|
+
type AvatarColor,
|
|
30
|
+
type AvatarSize,
|
|
31
|
+
type AvatarVariant,
|
|
32
|
+
type AvatarVariantProps,
|
|
33
|
+
avatarVariants,
|
|
34
|
+
resolveAvatarInteractive,
|
|
35
|
+
resolveAvatarOverlap,
|
|
36
|
+
resolveAvatarSize,
|
|
37
|
+
} from "./avatar.variants";
|
|
38
|
+
export type { AvatarBadgeProps } from "./avatar-badge";
|
|
39
|
+
export type { AvatarGroupProps } from "./avatar-group";
|
|
@@ -46,6 +46,11 @@ A compact label for status, category or count. Compound root plus `Badge.Label`,
|
|
|
46
46
|
with `fade` rather than the root's `scale` — a spring on a glyph that small
|
|
47
47
|
reads as a jitter. Reach for the part by hand only to place it somewhere other
|
|
48
48
|
than last.
|
|
49
|
+
- **`closeTestID` reaches the composed close control.** The root's own `testID`
|
|
50
|
+
names the badge; the close part is a separate pressable with no id of its own
|
|
51
|
+
unless the root forwards one, so a flow or test could only find it by its label.
|
|
52
|
+
`closeTestID` is named like `closeAccessibilityLabel`, the other prop the root
|
|
53
|
+
passes through to it.
|
|
49
54
|
- **A size is padding, never a height.** `Text` respects OS font scaling, so a
|
|
50
55
|
fixed height clips the label at a large accessibility step instead of growing
|
|
51
56
|
with it — and unlike `h-button-*` or `h-navbar-row`, a badge lines up against
|
|
@@ -28,6 +28,8 @@ export type BadgeProps = Omit<PressableProps, "asChild" | "busy" | "children" |
|
|
|
28
28
|
onClose?: () => void;
|
|
29
29
|
/** Name a screen reader gives the dismiss control. Defaults to `Remove`. */
|
|
30
30
|
closeAccessibilityLabel?: string;
|
|
31
|
+
/** `testID` for the dismiss control, so a test or automation can press it. */
|
|
32
|
+
closeTestID?: string;
|
|
31
33
|
children?: ReactNode;
|
|
32
34
|
};
|
|
33
35
|
|
|
@@ -38,6 +40,7 @@ function BadgeRoot({
|
|
|
38
40
|
isDisabled = false,
|
|
39
41
|
onClose,
|
|
40
42
|
closeAccessibilityLabel,
|
|
43
|
+
closeTestID,
|
|
41
44
|
onPress,
|
|
42
45
|
onLongPress,
|
|
43
46
|
haptic,
|
|
@@ -70,7 +73,9 @@ function BadgeRoot({
|
|
|
70
73
|
<IconDefaultsProvider value={iconDefaults}>
|
|
71
74
|
<TextClassProvider value={slots.label()}>
|
|
72
75
|
{content}
|
|
73
|
-
{onClose ?
|
|
76
|
+
{onClose ? (
|
|
77
|
+
<BadgeCloseButton accessibilityLabel={closeAccessibilityLabel} onPress={onClose} testID={closeTestID} />
|
|
78
|
+
) : null}
|
|
74
79
|
</TextClassProvider>
|
|
75
80
|
</IconDefaultsProvider>
|
|
76
81
|
);
|