@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,62 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
5
|
+
import { type CustomClassGroupId, TW_MERGE_CONFIG } from "../styles/tokens";
|
|
6
|
+
import { cn } from "./cn";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `cn` against the tailwind-merge it replaced, on this library's own classes.
|
|
10
|
+
*
|
|
11
|
+
* `cn` promises tailwind-merge's output for every input; this holds it to that
|
|
12
|
+
* on the strings that matter here — every class literal in a `*.variants.ts`,
|
|
13
|
+
* merged pairwise with a caller-style override, through the same
|
|
14
|
+
* `TW_MERGE_CONFIG`. tailwind-merge stays installed as `tailwind-variants`'
|
|
15
|
+
* peer, which is what makes the reference available.
|
|
16
|
+
*/
|
|
17
|
+
const reference = extendTailwindMerge<CustomClassGroupId>(TW_MERGE_CONFIG);
|
|
18
|
+
|
|
19
|
+
function variantFiles(dir: string): string[] {
|
|
20
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
21
|
+
const path = join(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) return variantFiles(path);
|
|
23
|
+
return entry.name.endsWith(".variants.ts") ? [path] : [];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const literals = [
|
|
28
|
+
...new Set(
|
|
29
|
+
variantFiles(join(import.meta.dir, "../components")).flatMap((file) =>
|
|
30
|
+
[...readFileSync(file, "utf8").matchAll(/"([a-z0-9!:[\]/.\-_% ]+)"/g)]
|
|
31
|
+
.map((match) => match[1] ?? "")
|
|
32
|
+
.filter((value) => value.includes("-"))
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const OVERRIDES = [
|
|
38
|
+
"p-4",
|
|
39
|
+
"h-12",
|
|
40
|
+
"rounded-none",
|
|
41
|
+
"bg-destructive",
|
|
42
|
+
"text-sm",
|
|
43
|
+
"text-primary",
|
|
44
|
+
"opacity-50",
|
|
45
|
+
"h-button-sm",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
describe("cn matches tailwind-merge on the library's classes", () => {
|
|
49
|
+
test("found a real corpus", () => {
|
|
50
|
+
expect(literals.length).toBeGreaterThan(100);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("every literal alone", () => {
|
|
54
|
+
for (const value of literals) expect(cn(value)).toBe(reference(value));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("every literal under a caller override", () => {
|
|
58
|
+
for (const value of literals) {
|
|
59
|
+
for (const override of OVERRIDES) expect(cn(value, override)).toBe(reference(value, override));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
package/src/lib/cn.test.ts
CHANGED
|
@@ -134,3 +134,17 @@ describe("cn with the semantic size tokens", () => {
|
|
|
134
134
|
expect(cn("border-input", "text-input-md")).toBe("border-input text-input-md");
|
|
135
135
|
});
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
describe("cn with Uniwind's corner-curve utilities", () => {
|
|
139
|
+
// `border-continuous` is a Uniwind utility, not a Tailwind one, and a bare
|
|
140
|
+
// tailwind-merge files it under border *colour* — so it and `border-border`
|
|
141
|
+
// were judged to conflict and one of them was silently dropped.
|
|
142
|
+
test("a corner curve and a border colour both survive", () => {
|
|
143
|
+
expect(cn("border-continuous", "border-border")).toBe("border-continuous border-border");
|
|
144
|
+
expect(cn("border-border", "border-continuous")).toBe("border-border border-continuous");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("one corner curve replaces the other", () => {
|
|
148
|
+
expect(cn("border-continuous", "border-circular")).toBe("border-circular");
|
|
149
|
+
});
|
|
150
|
+
});
|
package/src/lib/cn.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { ClassValue } from "cn";
|
|
2
|
+
import { createCn } from "cn/config";
|
|
3
3
|
import { TW_MERGE_CONFIG } from "../styles/tokens";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Class joining and conflict resolution, taught the semantic size tokens from `tokens.css`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Built on `cn` (shadcn-ui/cn), which replaces `clsx` + `tailwind-merge` with one
|
|
9
|
+
* engine: the same join semantics, the same merge output, and much cheaper on the
|
|
10
|
+
* repeated calls a render loop makes. `createCn` takes tailwind-merge's own
|
|
11
|
+
* `{ extend }` shape, so `TW_MERGE_CONFIG` is passed unchanged.
|
|
12
|
+
*
|
|
13
|
+
* Registering the tokens is load-bearing rather than tidiness. The merger only
|
|
9
14
|
* treats two classes as conflicting when it recognises both as members of the
|
|
10
15
|
* same group, and `button-md` is not a value it knows. Left unregistered,
|
|
11
16
|
* `cn("h-button-md", "h-12")` returns *both* classes: they each resolve to a
|
|
@@ -15,7 +20,7 @@ import { TW_MERGE_CONFIG } from "../styles/tokens";
|
|
|
15
20
|
*
|
|
16
21
|
* `tv()` needs the same treatment for its own merger — see `lib/tv.ts`.
|
|
17
22
|
*/
|
|
18
|
-
const
|
|
23
|
+
const merge = createCn(TW_MERGE_CONFIG);
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* Merges class names and resolves Tailwind conflicts so the last utility wins.
|
|
@@ -25,5 +30,5 @@ const twMerge = extendTailwindMerge(TW_MERGE_CONFIG);
|
|
|
25
30
|
* `className` through this before handing it to a component.
|
|
26
31
|
*/
|
|
27
32
|
export function cn(...inputs: ClassValue[]): string {
|
|
28
|
-
return
|
|
33
|
+
return merge(...inputs);
|
|
29
34
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
|
|
5
|
+
const SRC = import.meta.dirname;
|
|
6
|
+
const PACKAGE_JSON = join(SRC, "..", "package.json");
|
|
7
|
+
|
|
8
|
+
/** The sheet library `BottomSheet` used to wrap. */
|
|
9
|
+
const FORBIDDEN = "@gorhom/";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Nothing in this package names the sheet library it used to wrap.
|
|
13
|
+
*
|
|
14
|
+
* `BottomSheet` is built on `@delacour/react-native-bottom-sheet` now, and the
|
|
15
|
+
* old library left as a peer with it. A stray import would make every consumer
|
|
16
|
+
* install a package nobody uses; a stray mention in a doc would send a reader
|
|
17
|
+
* to a workaround that no longer applies. Both read the same to `grep`, so this
|
|
18
|
+
* reads the whole tree as text, source and docs alike.
|
|
19
|
+
*
|
|
20
|
+
* Like `docs.test.ts`, this imports nothing from the tree — `bun test` cannot
|
|
21
|
+
* parse React Native's Flow-typed source, and it does not need to.
|
|
22
|
+
*/
|
|
23
|
+
function sourceFiles(dir: string): string[] {
|
|
24
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
25
|
+
const path = join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) return sourceFiles(path);
|
|
27
|
+
return /\.(tsx?|md|css)$/.test(entry.name) && entry.name !== "no-gorhom.test.ts" ? [path] : [];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FILES = sourceFiles(SRC);
|
|
32
|
+
|
|
33
|
+
describe("no gorhom", () => {
|
|
34
|
+
// An empty walk would let the assertion below pass on nothing.
|
|
35
|
+
test("finds the source tree", () => {
|
|
36
|
+
expect(FILES.length).toBeGreaterThan(50);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("no source file or doc mentions the old sheet library", () => {
|
|
40
|
+
const offenders = FILES.filter((path) => readFileSync(path, "utf-8").includes(FORBIDDEN)).map((path) =>
|
|
41
|
+
relative(SRC, path)
|
|
42
|
+
);
|
|
43
|
+
expect(offenders).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("it is not a dependency or peer of the package", () => {
|
|
47
|
+
const pkg = JSON.parse(readFileSync(PACKAGE_JSON, "utf-8")) as Record<string, Record<string, string> | undefined>;
|
|
48
|
+
const declared = ["dependencies", "peerDependencies", "devDependencies", "optionalDependencies"].flatMap((field) =>
|
|
49
|
+
Object.keys(pkg[field] ?? {})
|
|
50
|
+
);
|
|
51
|
+
expect(declared.filter((name) => name.startsWith(FORBIDDEN))).toEqual([]);
|
|
52
|
+
expect(Object.keys(pkg.peerDependenciesMeta ?? {}).filter((name) => name.startsWith(FORBIDDEN))).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -83,6 +83,7 @@ describe("the geometry block", () => {
|
|
|
83
83
|
"--spacing-navbar-row",
|
|
84
84
|
"--spacing-screen-gutter",
|
|
85
85
|
...(["sm", "md", "lg"] as const).map((step) => `--spacing-chart-${step}`),
|
|
86
|
+
...(["sm", "md", "lg"] as const).map((step) => `--spacing-fab-${step}`),
|
|
86
87
|
];
|
|
87
88
|
|
|
88
89
|
for (const name of required) {
|
package/src/styles/theme.css
CHANGED
package/src/styles/tokens.css
CHANGED
|
@@ -106,6 +106,50 @@
|
|
|
106
106
|
--spacing-chart-sm: 160px;
|
|
107
107
|
--spacing-chart-md: 220px;
|
|
108
108
|
--spacing-chart-lg: 280px;
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* The kicker: the tiny uppercase mono label that sits over a group, a
|
|
112
|
+
* screen or a field. Ten points at 0.2em tracking, named as a pair so
|
|
113
|
+
* `Text variant="kicker"` and a hand-written `text-kicker tracking-kicker`
|
|
114
|
+
* agree. Nothing reads it unless asked, so the default look is unchanged.
|
|
115
|
+
*/
|
|
116
|
+
--text-kicker: 10px;
|
|
117
|
+
--tracking-kicker: 0.2em;
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* The etched material: a one-pixel highlight set into a surface's edge plus
|
|
121
|
+
* a very soft drop. Light puts the highlight on the bottom edge as a
|
|
122
|
+
* darkening, dark puts it on the top edge as a lightening, so a card reads
|
|
123
|
+
* as lit from above in both. They are two tokens because one `shadow-*`
|
|
124
|
+
* utility sets one `box-shadow` and a scheme flip needs `dark:`.
|
|
125
|
+
*
|
|
126
|
+
* `etched-primary` is the primary button's own: a white inset top edge at
|
|
127
|
+
* 16% that is the same in both schemes, because the fill under it is the
|
|
128
|
+
* dark one in light and invisible against the pale one in dark.
|
|
129
|
+
*
|
|
130
|
+
* `focus` is the three-point halo an etched field draws round its ring
|
|
131
|
+
* border. A neutral alpha rather than the ring colour: `color-mix` inside a
|
|
132
|
+
* `box-shadow` does not survive Uniwind's compile and draws nothing, and
|
|
133
|
+
* the ring is a neutral in every base ramp anyway.
|
|
134
|
+
*
|
|
135
|
+
* Opt-in only: no default component reads any of these.
|
|
136
|
+
*/
|
|
137
|
+
--shadow-etched: inset 0 -1px 0 0 rgb(0 0 0 / 4%), 0 1px 2px 0 rgb(0 0 0 / 5%);
|
|
138
|
+
--shadow-etched-dark: inset 0 1px 0 0 rgb(255 255 255 / 6%), 0 1px 2px 0 rgb(0 0 0 / 30%);
|
|
139
|
+
--shadow-etched-primary: inset 0 1px 0 0 rgb(255 255 255 / 16%), 0 1px 2px 0 rgb(0 0 0 / 5%);
|
|
140
|
+
--shadow-focus: 0 0 0 3px rgb(0 0 0 / 10%);
|
|
141
|
+
--shadow-focus-dark: 0 0 0 3px rgb(255 255 255 / 14%);
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
* A floating action button's footprint — the circle's edge, and the
|
|
145
|
+
* height of an extended one. A scale of its own rather than
|
|
146
|
+
* `--spacing-button-*`: a fab sits over content rather than in a row of
|
|
147
|
+
* controls, so it is a step larger, and retuning a toolbar's density must
|
|
148
|
+
* not move the one thing floating over the list.
|
|
149
|
+
*/
|
|
150
|
+
--spacing-fab-sm: 44px;
|
|
151
|
+
--spacing-fab-md: 56px;
|
|
152
|
+
--spacing-fab-lg: 64px;
|
|
109
153
|
}
|
|
110
154
|
|
|
111
155
|
/*
|
|
@@ -6,10 +6,14 @@ import {
|
|
|
6
6
|
BUTTON_SIZE_TOKENS,
|
|
7
7
|
BUTTON_TEXT_TOKENS,
|
|
8
8
|
CHART_SIZE_TOKENS,
|
|
9
|
+
ETCHED_SHADOW_TOKENS,
|
|
10
|
+
FAB_SIZE_TOKENS,
|
|
9
11
|
ICON_SIZE_TOKENS,
|
|
10
12
|
INPUT_SIZE_TOKENS,
|
|
11
13
|
INPUT_TEXT_TOKENS,
|
|
14
|
+
KICKER_TOKEN,
|
|
12
15
|
SCREEN_SIZE_TOKENS,
|
|
16
|
+
TW_MERGE_CONFIG,
|
|
13
17
|
} from "./tokens";
|
|
14
18
|
|
|
15
19
|
const CSS = readFileSync(join(import.meta.dirname, "tokens.css"), "utf-8");
|
|
@@ -44,6 +48,7 @@ describe("tokens.ts and tokens.css", () => {
|
|
|
44
48
|
const registered = [
|
|
45
49
|
...BUTTON_SIZE_TOKENS,
|
|
46
50
|
...CHART_SIZE_TOKENS,
|
|
51
|
+
...FAB_SIZE_TOKENS,
|
|
47
52
|
...ICON_SIZE_TOKENS,
|
|
48
53
|
...INPUT_SIZE_TOKENS,
|
|
49
54
|
...SCREEN_SIZE_TOKENS,
|
|
@@ -179,3 +184,54 @@ describe("the input scale", () => {
|
|
|
179
184
|
}
|
|
180
185
|
});
|
|
181
186
|
});
|
|
187
|
+
|
|
188
|
+
describe("the etched material", () => {
|
|
189
|
+
test("declares every shadow the registry names, and no other", () => {
|
|
190
|
+
const declared = [...CSS.matchAll(/--shadow-([\w-]+):/g)].map((match) => match[1]).sort();
|
|
191
|
+
expect(declared).toEqual([...ETCHED_SHADOW_TOKENS].sort());
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// An inset layer is what makes it a highlight rather than a drop shadow, and
|
|
195
|
+
// the soft outer layer is what lifts the edge off the page.
|
|
196
|
+
test("light and dark edges sit on opposite sides of the surface", () => {
|
|
197
|
+
expect(CSS).toMatch(/--shadow-etched:\s*inset 0 -1px/);
|
|
198
|
+
expect(CSS).toMatch(/--shadow-etched-dark:\s*inset 0 1px/);
|
|
199
|
+
expect(CSS).toMatch(/--shadow-etched-primary:\s*inset 0 1px 0 0 rgb\(255 255 255 \/ 16%\)/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("registers its shadows so a caller's shadow-* can replace them", () => {
|
|
203
|
+
expect(TW_MERGE_CONFIG.extend.theme.shadow).toEqual([...ETCHED_SHADOW_TOKENS]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("the kicker type style", () => {
|
|
208
|
+
test("is ten points, tracked at 0.2em", () => {
|
|
209
|
+
expect(px("text-kicker")).toBe(10);
|
|
210
|
+
expect(CSS).toMatch(/--tracking-kicker:\s*0\.2em;/);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("registers both tokens so text-kicker is read as a size, not a colour", () => {
|
|
214
|
+
expect(TW_MERGE_CONFIG.extend.theme.text).toContain(KICKER_TOKEN);
|
|
215
|
+
expect(TW_MERGE_CONFIG.extend.theme.tracking).toEqual([KICKER_TOKEN]);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("the fab scale", () => {
|
|
220
|
+
test("ascends in the order the registry lists it", () => {
|
|
221
|
+
const values = FAB_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
|
|
222
|
+
expect(values).toEqual([...values].sort((a, b) => a - b));
|
|
223
|
+
expect(new Set(values).size).toBe(FAB_SIZE_TOKENS.length);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// The smallest fab is still a touch target, which the platform guidelines
|
|
227
|
+
// put at 44pt — the same floor the medium button sits on.
|
|
228
|
+
test("never drops below a touch target", () => {
|
|
229
|
+
expect(px("spacing-fab-sm")).toBeGreaterThanOrEqual(44);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// A fab's glyph indexes the shared icon scale: sm → icon-md, md and lg → icon-lg.
|
|
233
|
+
test("holds its glyph with room around it", () => {
|
|
234
|
+
expect(px("spacing-icon-md")).toBeLessThan(px("spacing-fab-sm") / 2);
|
|
235
|
+
expect(px("spacing-icon-lg")).toBeLessThan(px("spacing-fab-md") / 2);
|
|
236
|
+
});
|
|
237
|
+
});
|
package/src/styles/tokens.ts
CHANGED
|
@@ -59,6 +59,15 @@ export const INPUT_TEXT_TOKENS = ["input-sm", "input-md", "input-lg"] as const;
|
|
|
59
59
|
*/
|
|
60
60
|
export const CHART_SIZE_TOKENS = ["chart-sm", "chart-md", "chart-lg"] as const;
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Suffixes in the `--spacing-*` namespace: `size-fab-md`, `h-fab-md`.
|
|
64
|
+
*
|
|
65
|
+
* A floating action button's footprint — the circle's edge, or an extended
|
|
66
|
+
* one's height. Its own scale rather than the button's, so a fab can be
|
|
67
|
+
* retuned without moving every control in a form.
|
|
68
|
+
*/
|
|
69
|
+
export const FAB_SIZE_TOKENS = ["fab-sm", "fab-md", "fab-lg"] as const;
|
|
70
|
+
|
|
62
71
|
/**
|
|
63
72
|
* The tailwind-merge extension both mergers in this package are built from.
|
|
64
73
|
*
|
|
@@ -68,19 +77,38 @@ export const CHART_SIZE_TOKENS = ["chart-sm", "chart-md", "chart-lg"] as const;
|
|
|
68
77
|
* `text-button-md` as an unrecognised utility — which is how a size token ends
|
|
69
78
|
* up colliding with a colour and silently dropping it. One config, read by
|
|
70
79
|
* `lib/cn.ts` and `lib/tv.ts`, so they cannot drift apart.
|
|
80
|
+
*
|
|
81
|
+
* `border-curve` is Uniwind's, not Tailwind's: `border-continuous` sets iOS's
|
|
82
|
+
* `borderCurve`. Unregistered, tailwind-merge reads it as a border *colour*,
|
|
83
|
+
* and `border-continuous border-border` loses one of the two.
|
|
71
84
|
*/
|
|
85
|
+
/** Names of the shadows the etched material declares in `tokens.css`. */
|
|
86
|
+
export const ETCHED_SHADOW_TOKENS = ["etched", "etched-dark", "etched-primary", "focus", "focus-dark"] as const;
|
|
87
|
+
|
|
88
|
+
/** The size and tracking token behind `Text variant="kicker"`; one name for both. */
|
|
89
|
+
export const KICKER_TOKEN = "kicker";
|
|
90
|
+
|
|
72
91
|
export const TW_MERGE_CONFIG = {
|
|
73
92
|
extend: {
|
|
93
|
+
classGroups: {
|
|
94
|
+
"border-curve": ["border-continuous", "border-circular"],
|
|
95
|
+
},
|
|
74
96
|
theme: {
|
|
75
97
|
radius: [...BUTTON_RADIUS_TOKENS],
|
|
76
98
|
spacing: [
|
|
77
99
|
...BUTTON_SIZE_TOKENS,
|
|
78
100
|
...CHART_SIZE_TOKENS,
|
|
101
|
+
...FAB_SIZE_TOKENS,
|
|
79
102
|
...ICON_SIZE_TOKENS,
|
|
80
103
|
...INPUT_SIZE_TOKENS,
|
|
81
104
|
...SCREEN_SIZE_TOKENS,
|
|
82
105
|
],
|
|
83
|
-
|
|
106
|
+
shadow: [...ETCHED_SHADOW_TOKENS],
|
|
107
|
+
text: [...BUTTON_TEXT_TOKENS, ...INPUT_TEXT_TOKENS, KICKER_TOKEN],
|
|
108
|
+
tracking: [KICKER_TOKEN],
|
|
84
109
|
},
|
|
85
110
|
},
|
|
86
111
|
} as const;
|
|
112
|
+
|
|
113
|
+
/** Class groups `TW_MERGE_CONFIG` adds, which tailwind-merge's extension has to be told about by name. */
|
|
114
|
+
export type CustomClassGroupId = keyof typeof TW_MERGE_CONFIG.extend.classGroups;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Children, isValidElement, type ReactElement, type ReactNode, useMemo } from "react";
|
|
2
|
-
import { BottomSheetPortalProvider } from "./bottom-sheet.context";
|
|
3
|
-
import { BottomSheetOverlay } from "./bottom-sheet-overlay";
|
|
4
|
-
|
|
5
|
-
export type BottomSheetPortalProps = {
|
|
6
|
-
children?: ReactNode;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Everything that draws above the app once the sheet is open.
|
|
11
|
-
*
|
|
12
|
-
* It renders no view of its own, and it does not portal anything either —
|
|
13
|
-
* `BottomSheetModal` already hosts itself in the `BottomSheetModalProvider` that
|
|
14
|
-
* `DelacourProvider` mounts innermost. What this part does is one thing:
|
|
15
|
-
* **it lifts the `BottomSheet.Overlay` out of its own children** and publishes it
|
|
16
|
-
* on context, so `BottomSheet.Container` can clone it into gorhom's
|
|
17
|
-
* `backdropComponent` slot.
|
|
18
|
-
*
|
|
19
|
-
* That indirection buys the anatomy. The scrim and the sheet are siblings on
|
|
20
|
-
* screen, so they read best as siblings in the tree — but gorhom takes the scrim
|
|
21
|
-
* as a render prop, and a component cannot hand a prop to a sibling. This is the
|
|
22
|
-
* seam where the two shapes are reconciled, and it is why the part exists at all
|
|
23
|
-
* rather than the container simply taking a `backdrop` prop.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* <BottomSheet.Portal>
|
|
27
|
-
* <BottomSheet.Overlay />
|
|
28
|
-
* <BottomSheet.Container>{…}</BottomSheet.Container>
|
|
29
|
-
* </BottomSheet.Portal>
|
|
30
|
-
*/
|
|
31
|
-
export function BottomSheetPortal({ children }: BottomSheetPortalProps): ReactElement {
|
|
32
|
-
const { content, overlay } = useMemo(() => hoistOverlay(children), [children]);
|
|
33
|
-
const value = useMemo(() => ({ overlay }), [overlay]);
|
|
34
|
-
|
|
35
|
-
return <BottomSheetPortalProvider value={value}>{content}</BottomSheetPortalProvider>;
|
|
36
|
-
}
|
|
37
|
-
BottomSheetPortal.displayName = "DelacourUI.BottomSheet.Portal";
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Splits a portal's children into the scrim and everything else.
|
|
41
|
-
*
|
|
42
|
-
* Lives here rather than with the parts: it is the portal that lifts its own
|
|
43
|
-
* children, and importing it from `bottom-sheet-overlay.tsx` would close a cycle.
|
|
44
|
-
* The same rule that keeps `withIndicator` in `radio.tsx`. See AGENTS.md.
|
|
45
|
-
*
|
|
46
|
-
* A second overlay is dropped rather than rendered — there is one
|
|
47
|
-
* `backdropComponent` slot to clone into, and silently drawing two scrims at
|
|
48
|
-
* different opacities is worse than the warning.
|
|
49
|
-
*/
|
|
50
|
-
function hoistOverlay(children: ReactNode): { content: ReactNode; overlay: ReactElement | null } {
|
|
51
|
-
const items = Children.toArray(children);
|
|
52
|
-
const content: ReactNode[] = [];
|
|
53
|
-
let overlay: ReactElement | null = null;
|
|
54
|
-
let seen = 0;
|
|
55
|
-
|
|
56
|
-
for (const child of items) {
|
|
57
|
-
if (isValidElement(child) && child.type === BottomSheetOverlay) {
|
|
58
|
-
seen += 1;
|
|
59
|
-
overlay ??= child;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
content.push(child);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (process.env.NODE_ENV !== "production" && seen > 1) {
|
|
66
|
-
console.warn(
|
|
67
|
-
`BottomSheet.Portal: ${seen} <BottomSheet.Overlay> elements were written; only the first is drawn. A sheet has one backdrop.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { content, overlay };
|
|
72
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
2
|
-
import { type ComponentRef, createContext, type ReactElement, type ReactNode, type RefObject, use } from "react";
|
|
3
|
-
import type { SharedValue } from "react-native-reanimated";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The imperative handle a `BottomSheetModal` exposes.
|
|
7
|
-
*
|
|
8
|
-
* Derived from the component rather than imported by name: gorhom re-exports the
|
|
9
|
-
* modal's *value* from its package root but not the `BottomSheetModalMethods`
|
|
10
|
-
* type behind its ref, and reaching into `lib/typescript/` for one would pin this
|
|
11
|
-
* package to a path that is not part of that package's public surface.
|
|
12
|
-
*/
|
|
13
|
-
export type BottomSheetHandle = ComponentRef<typeof BottomSheetModal>;
|
|
14
|
-
|
|
15
|
-
export type BottomSheetContextValue = {
|
|
16
|
-
isOpen: boolean;
|
|
17
|
-
/** The controllable-state setter. Reports upward whether controlled or not. */
|
|
18
|
-
setOpen: (isOpen: boolean) => void;
|
|
19
|
-
open: () => void;
|
|
20
|
-
close: () => void;
|
|
21
|
-
/**
|
|
22
|
-
* The modal's handle, for the imperative calls that have no declarative form —
|
|
23
|
-
* `snapToIndex`, `expand`, `collapse`. Presenting and dismissing are driven by
|
|
24
|
-
* `isOpen` instead, so a caller never has to keep two sources in step.
|
|
25
|
-
*/
|
|
26
|
-
sheetRef: RefObject<BottomSheetHandle | null>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);
|
|
30
|
-
|
|
31
|
-
export function BottomSheetProvider({
|
|
32
|
-
value,
|
|
33
|
-
children,
|
|
34
|
-
}: {
|
|
35
|
-
value: BottomSheetContextValue;
|
|
36
|
-
children: ReactNode;
|
|
37
|
-
}): ReactElement {
|
|
38
|
-
return <BottomSheetContext value={value}>{children}</BottomSheetContext>;
|
|
39
|
-
}
|
|
40
|
-
BottomSheetProvider.displayName = "DelacourUI.BottomSheet.Provider";
|
|
41
|
-
|
|
42
|
-
/** The sheet's state, or `null` outside one. For a component that works either way. */
|
|
43
|
-
export function useBottomSheetContext(): BottomSheetContextValue | null {
|
|
44
|
-
return use(BottomSheetContext);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The enclosing sheet's open state and controls.
|
|
49
|
-
*
|
|
50
|
-
* Reach for this to drive a sheet from a control of your own — a row that closes
|
|
51
|
-
* it, a button that snaps it somewhere — without threading props down.
|
|
52
|
-
*/
|
|
53
|
-
export function useBottomSheet(): BottomSheetContextValue {
|
|
54
|
-
const context = use(BottomSheetContext);
|
|
55
|
-
if (context === null) throw new Error("useBottomSheet must be called inside a <BottomSheet>.");
|
|
56
|
-
return context;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** The same read, with a message naming the part that is misplaced. Internal. */
|
|
60
|
-
export function useBottomSheetPart(component: string): BottomSheetContextValue {
|
|
61
|
-
const context = use(BottomSheetContext);
|
|
62
|
-
if (context === null) throw new Error(`${component} must be rendered inside a <BottomSheet>.`);
|
|
63
|
-
return context;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type BottomSheetPortalContextValue = {
|
|
67
|
-
/**
|
|
68
|
-
* The `BottomSheet.Overlay` element the portal lifted out of its own children,
|
|
69
|
-
* or `null` when none was written.
|
|
70
|
-
*
|
|
71
|
-
* gorhom takes the scrim as a `backdropComponent` render prop rather than as a
|
|
72
|
-
* child, so the element cannot be rendered where it was declared. The portal
|
|
73
|
-
* publishes it here and `BottomSheet.Container` clones it into that slot.
|
|
74
|
-
*/
|
|
75
|
-
overlay: ReactElement | null;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const BottomSheetPortalContext = createContext<BottomSheetPortalContextValue | null>(null);
|
|
79
|
-
|
|
80
|
-
export function BottomSheetPortalProvider({
|
|
81
|
-
value,
|
|
82
|
-
children,
|
|
83
|
-
}: {
|
|
84
|
-
value: BottomSheetPortalContextValue;
|
|
85
|
-
children: ReactNode;
|
|
86
|
-
}): ReactElement {
|
|
87
|
-
return <BottomSheetPortalContext value={value}>{children}</BottomSheetPortalContext>;
|
|
88
|
-
}
|
|
89
|
-
BottomSheetPortalProvider.displayName = "DelacourUI.BottomSheet.Portal.Provider";
|
|
90
|
-
|
|
91
|
-
/** What the enclosing portal lifted out, or `null` for a container written without one. */
|
|
92
|
-
export function useBottomSheetPortalContext(): BottomSheetPortalContextValue | null {
|
|
93
|
-
return use(BottomSheetPortalContext);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type BottomSheetContainerContextValue = {
|
|
97
|
-
/**
|
|
98
|
-
* Whether a pinned footer is drawing at the bottom of this sheet.
|
|
99
|
-
*
|
|
100
|
-
* `BottomSheet.Content` reads it to decide whether the safe-area band is its
|
|
101
|
-
* to apply. Exactly one part takes it: both applying leaves a gap the height
|
|
102
|
-
* of the home indicator between the content and the footer.
|
|
103
|
-
*/
|
|
104
|
-
hasStickyFooter: boolean;
|
|
105
|
-
/**
|
|
106
|
-
* What a pinned footer covers, measured, in points.
|
|
107
|
-
*
|
|
108
|
-
* A shared value rather than state: it changes on every frame of the keyboard
|
|
109
|
-
* animation as the footer gives up its safe-area band, and the body reserves it
|
|
110
|
-
* with an animated spacer. Routing that through React would commit a dozen
|
|
111
|
-
* renders per keystroke's worth of keyboard travel. `Screen` publishes its own
|
|
112
|
-
* chrome the same way.
|
|
113
|
-
*
|
|
114
|
-
* Zero when there is no pinned footer.
|
|
115
|
-
*/
|
|
116
|
-
footerHeight: SharedValue<number>;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const BottomSheetContainerContext = createContext<BottomSheetContainerContextValue | null>(null);
|
|
120
|
-
|
|
121
|
-
export function BottomSheetContainerProvider({
|
|
122
|
-
value,
|
|
123
|
-
children,
|
|
124
|
-
}: {
|
|
125
|
-
value: BottomSheetContainerContextValue;
|
|
126
|
-
children: ReactNode;
|
|
127
|
-
}): ReactElement {
|
|
128
|
-
return <BottomSheetContainerContext value={value}>{children}</BottomSheetContainerContext>;
|
|
129
|
-
}
|
|
130
|
-
BottomSheetContainerProvider.displayName = "DelacourUI.BottomSheet.Container.Provider";
|
|
131
|
-
|
|
132
|
-
/** The enclosing container's layout facts, or `null` outside one. */
|
|
133
|
-
export function useBottomSheetContainerContext(): BottomSheetContainerContextValue | null {
|
|
134
|
-
return use(BottomSheetContainerContext);
|
|
135
|
-
}
|