@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,133 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
A short label naming the control under the finger — what an icon-only button does, a shortcut, a
|
|
4
|
+
one-line hint. It is not interactive: anything with a button in it is a [Popover](../popover/AGENTS.md).
|
|
5
|
+
|
|
6
|
+
`import { Tooltip } from "@delacour/react-native-ui/tooltip";`
|
|
7
|
+
|
|
8
|
+
It draws through the overlay foundation, so it needs `OverlayProvider` at the app root and
|
|
9
|
+
`react-native-teleport` installed — see [Overlay](../overlay/AGENTS.md). The provider is also what
|
|
10
|
+
hears an outside tap; without it the tooltip renders inline, may be clipped, and closes only on its
|
|
11
|
+
timer or the trigger.
|
|
12
|
+
|
|
13
|
+
## Anatomy
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Tooltip isOpen? defaultOpen? onOpenChange? openOn? duration? label?>
|
|
17
|
+
<Tooltip.Trigger asChild><Button size="icon-md" variant="ghost" /></Tooltip.Trigger>
|
|
18
|
+
<Tooltip.Content placement? align? offset? alignOffset? variant? width? minWidth? maxHeight? isScrollable?>
|
|
19
|
+
<Tooltip.Arrow />
|
|
20
|
+
<Tooltip.Text /> // inverted: the one line
|
|
21
|
+
<Tooltip.Title /> <Tooltip.Description /> // surface: a heading and a line
|
|
22
|
+
</Tooltip.Content>
|
|
23
|
+
</Tooltip>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`useTooltip()` returns `{ isOpen, setOpen, close }`.
|
|
27
|
+
|
|
28
|
+
## Files
|
|
29
|
+
|
|
30
|
+
| File | What it holds |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `index.ts` | → `@delacour/react-native-ui/tooltip` |
|
|
33
|
+
| `tooltip.tsx` | `Tooltip` — open state, the screen-reader check, activation, the outside-tap subscription, the one-open-at-a-time rule; the `Object.assign` names every part |
|
|
34
|
+
| `tooltip.context.tsx` | **Leaf.** `TooltipContext`, `useTooltip`, and the content context the arrow and text read |
|
|
35
|
+
| `tooltip.variants.ts` | The slotted `tv()` — `content`, `arrow`, `text`, `title`, `description` per variant — the defaults, and the pure `resolveTooltipDuration`, `shouldTooltipActivate`, `resolveTooltipAccessibility`, `readableTextOf` |
|
|
36
|
+
| `tooltip.variants.test.ts` | Both variants and their tokens in both themes, the three resolvers, the defaults |
|
|
37
|
+
| `tooltip-trigger.tsx` | `Tooltip.Trigger` — `Pressable`, or `asChild` to donate the long press or the press; the anchor; the label or hint |
|
|
38
|
+
| `tooltip-content.tsx` | `Tooltip.Content` — the portal, the panel, the timer |
|
|
39
|
+
| `tooltip-arrow.tsx` | `Tooltip.Arrow` — Popover's `AnchoredArrow` in the variant's paint |
|
|
40
|
+
| `tooltip-text.tsx` | `Tooltip.Text` — `Text.Caption`, inked for the variant |
|
|
41
|
+
| `tooltip-title.tsx` | `Tooltip.Title` — `Text.Label`, for the surface variant |
|
|
42
|
+
| `tooltip-description.tsx` | `Tooltip.Description` — `Text.Caption`, for the surface variant |
|
|
43
|
+
|
|
44
|
+
## Builds on Popover's leaves
|
|
45
|
+
|
|
46
|
+
`popover/popover.position.ts`, `popover/use-anchor-measure.ts`, `popover/use-anchored-content.ts`,
|
|
47
|
+
`popover/popover-arrow.tsx` and `popover/popover.variants.ts` are imported **directly**, never
|
|
48
|
+
`../popover` — package rule 3's leaf exception, written down in Popover's own doc. So the tooltip
|
|
49
|
+
measures, flips, shifts, clamps and aims its arrow exactly as a popover does, and none of it is
|
|
50
|
+
re-tested here. It keeps Popover's collision padding and arrow inset; the inset is sized for the
|
|
51
|
+
card corner, which is larger than the inverted chip's `rounded-md`, so it clears both.
|
|
52
|
+
|
|
53
|
+
## Design
|
|
54
|
+
|
|
55
|
+
- **A long press opens it, so the control keeps its tap.** Mobile has no hover. A tooltip that
|
|
56
|
+
opened on a tap would take the tap from the button it names, so the default `openOn` is
|
|
57
|
+
`"longPress"`, with a `selection` haptic — fired from the UI thread through `playHaptic`, because
|
|
58
|
+
`Pressable`'s `haptic` plays on press-in, which is every tap. The trigger's own `onLongPress`
|
|
59
|
+
still runs, after the tooltip's. A tap shorter than the long-press delay still presses the
|
|
60
|
+
button; one held past it fails as a tap, so a long press never also presses it. `openOn="press"`
|
|
61
|
+
is for a trigger with no tap of its own — an info glyph.
|
|
62
|
+
- **`asChild` donates the gesture.** For `Popover.Trigger`'s reason: a `Button` inside a pressable
|
|
63
|
+
trigger would win the touch and the tooltip would never open. The trigger hands its handler to
|
|
64
|
+
the child as `onLongPress` or `onPress`, chained ahead of the child's own by `mergeProps`, and
|
|
65
|
+
composes the measuring ref onto the child's — so the child has to be built on `Pressable`.
|
|
66
|
+
- **An outside tap closes it and still lands.** Popover swallows an outside tap behind an invisible
|
|
67
|
+
catcher, which is right for a panel with things to press in it and wrong for a label: a tooltip
|
|
68
|
+
in the way of the next tap would make the app feel stuck. So there is no catcher. The panel is
|
|
69
|
+
`pointerEvents="none"` — a tap on it falls through too — and the tooltip subscribes, while open,
|
|
70
|
+
to `OverlayProvider`'s `subscribeTouchStart`, which hears every touch starting anywhere beneath
|
|
71
|
+
the provider without claiming it, and closes. Only the provider can do this: nothing else is an
|
|
72
|
+
ancestor of a view the tooltip knows nothing about. See [Overlay](../overlay/AGENTS.md).
|
|
73
|
+
- **The trigger again closes it, and that rides on bubbling order.** A touch on the trigger reaches
|
|
74
|
+
the trigger's `onTouchStart` before it bubbles to the provider, so the trigger records whether
|
|
75
|
+
the tooltip was open when this touch began; then the provider closes it; then the long press or
|
|
76
|
+
press arrives and, seeing it was open, leaves it closed rather than re-opening what the touch
|
|
77
|
+
just shut. In long-press mode a plain tap on the trigger closes it too — the user has moved on to
|
|
78
|
+
the button.
|
|
79
|
+
- **It hides itself.** `duration` ms (1500 by default) after the entrance settles, so a slow
|
|
80
|
+
entrance never eats into the time the words are on screen. `0` keeps it until an outside tap or
|
|
81
|
+
the trigger. A negative or non-finite duration is a mistake, not a request to vanish, and takes
|
|
82
|
+
the default (`resolveTooltipDuration`).
|
|
83
|
+
- **One at a time.** A module-level slot holds the open tooltip's id and close; opening another
|
|
84
|
+
closes it. Two labels pointing at two controls at once say nothing about either.
|
|
85
|
+
- **`inverted` is the default look.** The foreground colour as the fill and the background colour
|
|
86
|
+
as the ink reads over anything in either theme, which is what a label floating over arbitrary
|
|
87
|
+
content needs; a compact `rounded-md` chip sized for one caption-sized line. `surface` is the
|
|
88
|
+
popover card — fill, hairline, card corner — with room for `Tooltip.Title` over
|
|
89
|
+
`Tooltip.Description`. The text parts read the variant from the panel, so neither needs a class
|
|
90
|
+
at the call site.
|
|
91
|
+
- **The arrow is Popover's, repainted.** `AnchoredArrow` with `isUnstyled`, so Popover's paint is
|
|
92
|
+
stripped and the variant's `arrow` slot is all there is: fill alone on the borderless chip, fill
|
|
93
|
+
plus the border on its two outer edges on the card.
|
|
94
|
+
- **Motion is a fade and 4pt.** From `useAnchoredContent`: an invisible measure frame, then a fade
|
|
95
|
+
and a 4pt slide from the resolved side, with no scale — a label appears, it does not grow.
|
|
96
|
+
Under reduce motion only the fade runs.
|
|
97
|
+
|
|
98
|
+
## Accessibility
|
|
99
|
+
|
|
100
|
+
- **The trigger carries the words.** `resolveTooltipAccessibility`: `label` becomes the trigger's
|
|
101
|
+
`accessibilityLabel` when it has none — an icon button — or its `accessibilityHint` when it does;
|
|
102
|
+
a label identical to the trigger's is dropped rather than read twice. An `asChild` trigger's
|
|
103
|
+
label is read off the child's props, and visible text counts as a label (`readableTextOf`): a
|
|
104
|
+
`<Button>Sync now</Button>` is already named, and an early build replaced "Sync now" with the
|
|
105
|
+
tooltip's words — found on a simulator with the `surface` demo. So VoiceOver says the tooltip's words without anything
|
|
106
|
+
opening.
|
|
107
|
+
- **The panel is never read and never takes focus.** `accessibilityElementsHidden` and
|
|
108
|
+
`importantForAccessibility="no-hide-descendants"` on the positioner; non-modal overlays never move
|
|
109
|
+
focus (the overlay plan's rule). Reading it as well would say everything twice.
|
|
110
|
+
- **With a screen reader on, a long press does not open it** (`shouldTooltipActivate`) — the words
|
|
111
|
+
already reached the trigger, and double-tap-and-hold is an action. A press tooltip still opens,
|
|
112
|
+
for someone using zoom alongside VoiceOver, and then never times out
|
|
113
|
+
(`resolveTooltipDuration`): they read at their own pace.
|
|
114
|
+
- **Android back closes it** while it is the top overlay, so a tooltip opened over a popover does
|
|
115
|
+
not leave back doing nothing.
|
|
116
|
+
|
|
117
|
+
## Out of scope
|
|
118
|
+
|
|
119
|
+
- Following a trigger that scrolls while open — the panel stays where it opened, as Popover's does,
|
|
120
|
+
and the timer clears it soon after.
|
|
121
|
+
- Interactive content. A button in a tooltip cannot be pressed; that is a `Popover`.
|
|
122
|
+
|
|
123
|
+
## Testing
|
|
124
|
+
|
|
125
|
+
`bun test` reaches the variants and the three resolvers. Positioning is Popover's and tested there.
|
|
126
|
+
Everything else — the long press and its haptic, the button's tap surviving it, the timer, an
|
|
127
|
+
outside tap closing it and still landing, one-at-a-time, VoiceOver reading `label` — is verified on
|
|
128
|
+
a simulator through `apps/playground`'s `/tooltip` gallery.
|
|
129
|
+
|
|
130
|
+
Preview media is not captured yet (see the overlay plan); the flows are in
|
|
131
|
+
`.argent/flows/previews/tooltip/`. When a capture tool is back, mark these `capture`:
|
|
132
|
+
`icon-buttons` `{ flow: "tooltip/icon-buttons", frame: "device", hero: true }`, and `press`,
|
|
133
|
+
`placements`, `surface` `{ flow: "tooltip/<id>", frame: "device" }`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { Tooltip, type TooltipProps } from "./tooltip";
|
|
2
|
+
export {
|
|
3
|
+
TooltipContext,
|
|
4
|
+
type TooltipContextValue,
|
|
5
|
+
useTooltip,
|
|
6
|
+
} from "./tooltip.context";
|
|
7
|
+
export {
|
|
8
|
+
readableTextOf,
|
|
9
|
+
resolveTooltipAccessibility,
|
|
10
|
+
resolveTooltipDuration,
|
|
11
|
+
shouldTooltipActivate,
|
|
12
|
+
TOOLTIP_DEFAULTS,
|
|
13
|
+
TOOLTIP_DURATION,
|
|
14
|
+
TOOLTIP_ENTER_DISTANCE,
|
|
15
|
+
TOOLTIP_VARIANTS,
|
|
16
|
+
type TooltipAccessibility,
|
|
17
|
+
type TooltipGesture,
|
|
18
|
+
type TooltipOpenOn,
|
|
19
|
+
type TooltipVariant,
|
|
20
|
+
type TooltipVariantProps,
|
|
21
|
+
tooltipVariants,
|
|
22
|
+
} from "./tooltip.variants";
|
|
23
|
+
export type { TooltipArrowProps } from "./tooltip-arrow";
|
|
24
|
+
export type { TooltipContentProps } from "./tooltip-content";
|
|
25
|
+
export type { TooltipDescriptionProps } from "./tooltip-description";
|
|
26
|
+
export type { TooltipTextProps } from "./tooltip-text";
|
|
27
|
+
export type { TooltipTitleProps } from "./tooltip-title";
|
|
28
|
+
export type { TooltipTriggerProps } from "./tooltip-trigger";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { AnchoredArrow } from "../popover/popover-arrow";
|
|
3
|
+
import { useOptionalTooltipContent } from "./tooltip.context";
|
|
4
|
+
import { tooltipVariants } from "./tooltip.variants";
|
|
5
|
+
|
|
6
|
+
export type TooltipArrowProps = {
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The panel's arrow — Popover's `AnchoredArrow`, wearing the tooltip's paint.
|
|
12
|
+
*
|
|
13
|
+
* Write it anywhere inside `Tooltip.Content`: it reads the resolved placement
|
|
14
|
+
* and offset from the panel, follows a flip, points at the trigger's centre
|
|
15
|
+
* even after the panel shifted along an edge, and is lifted out of a
|
|
16
|
+
* scrollable body. Popover's own paint is stripped and the variant's slot
|
|
17
|
+
* supplies it, so an inverted chip's arrow is the foreground fill with no
|
|
18
|
+
* border and a surface card's is the popover fill with its hairline.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <Tooltip.Content>
|
|
22
|
+
* <Tooltip.Arrow />
|
|
23
|
+
* <Tooltip.Text>Share</Tooltip.Text>
|
|
24
|
+
* </Tooltip.Content>
|
|
25
|
+
*/
|
|
26
|
+
export function TooltipArrow({ className }: TooltipArrowProps): ReactElement | null {
|
|
27
|
+
const content = useOptionalTooltipContent();
|
|
28
|
+
if (content === null) return null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<AnchoredArrow
|
|
32
|
+
arrowOffset={content.arrowOffset}
|
|
33
|
+
className={tooltipVariants({ variant: content.variant }).arrow({ className })}
|
|
34
|
+
isUnstyled
|
|
35
|
+
placement={content.placement}
|
|
36
|
+
size={content.size}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
TooltipArrow.displayName = "DelacourUI.Tooltip.Arrow";
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
isValidElement,
|
|
4
|
+
type ReactElement,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { ScrollView, View, type ViewProps } from "react-native";
|
|
13
|
+
import Animated from "react-native-reanimated";
|
|
14
|
+
import { Overlay, useOverlayBackHandler } from "../overlay";
|
|
15
|
+
import type { PopoverAlign, PopoverPlacement, PopoverWidth } from "../popover/popover.position";
|
|
16
|
+
import { POPOVER_ARROW_INSET, POPOVER_COLLISION_PADDING } from "../popover/popover.variants";
|
|
17
|
+
import { useAnchoredContent } from "../popover/use-anchored-content";
|
|
18
|
+
import { TooltipContentContext, type TooltipContentContextValue, useTooltipContext } from "./tooltip.context";
|
|
19
|
+
import { TOOLTIP_DEFAULTS, TOOLTIP_ENTER_DISTANCE, type TooltipVariant, tooltipVariants } from "./tooltip.variants";
|
|
20
|
+
import { TooltipArrow } from "./tooltip-arrow";
|
|
21
|
+
|
|
22
|
+
export type TooltipContentProps = ViewProps & {
|
|
23
|
+
className?: string;
|
|
24
|
+
/** The side of the trigger the panel prefers. It flips when that side lacks room. Default `"top"`. */
|
|
25
|
+
placement?: PopoverPlacement;
|
|
26
|
+
/** Which edges line up along the cross axis; logical under RTL. Default `"center"`. */
|
|
27
|
+
align?: PopoverAlign;
|
|
28
|
+
/** The gap between trigger and panel, in points. Default 6. */
|
|
29
|
+
offset?: number;
|
|
30
|
+
/** A nudge along the cross axis, inward from the aligned edge. Default 0. */
|
|
31
|
+
alignOffset?: number;
|
|
32
|
+
/** `"inverted"` — a dark chip for one line; `"surface"` — a popover card for a title and a description. Default `"inverted"`. */
|
|
33
|
+
variant?: TooltipVariant;
|
|
34
|
+
/** A number, the trigger's width, the content's own, or the safe span. Default `"content-fit"`. */
|
|
35
|
+
width?: PopoverWidth;
|
|
36
|
+
minWidth?: number;
|
|
37
|
+
/** Clamped to the room on the resolved side. */
|
|
38
|
+
maxHeight?: number;
|
|
39
|
+
/** Scroll the body when it is taller than `maxHeight` — the one case the panel takes a touch. Default false. */
|
|
40
|
+
isScrollable?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Pulls `Tooltip.Arrow` out of the body, so a scrolling body never scrolls or clips it. */
|
|
44
|
+
function partitionArrow(children: ReactNode): { arrows: ReactNode[]; body: ReactNode[] } {
|
|
45
|
+
const arrows: ReactNode[] = [];
|
|
46
|
+
const body: ReactNode[] = [];
|
|
47
|
+
Children.forEach(children, (child) => {
|
|
48
|
+
if (isValidElement(child) && child.type === TooltipArrow) arrows.push(child);
|
|
49
|
+
else body.push(child);
|
|
50
|
+
});
|
|
51
|
+
return { arrows, body };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The panel, drawn over the app and anchored to the trigger.
|
|
56
|
+
*
|
|
57
|
+
* Renders nothing while closed. On open it mounts in the `anchored` band of
|
|
58
|
+
* the overlay z-order, measures itself invisibly, resolves where it fits —
|
|
59
|
+
* Popover's resolver, so it flips and shifts the same way — and fades in from
|
|
60
|
+
* 4pt toward its resolved side; under reduce motion it only fades. `duration`
|
|
61
|
+
* ms after that entrance settles it hides itself.
|
|
62
|
+
*
|
|
63
|
+
* It takes no touch: there is no catcher under it and the panel is
|
|
64
|
+
* `pointerEvents="none"`, so a tap anywhere — the panel included — reaches
|
|
65
|
+
* what is under it, and the provider closes the tooltip on the way. Only an
|
|
66
|
+
* `isScrollable` body takes a touch, to scroll. It is hidden from assistive
|
|
67
|
+
* technology and never takes focus: its words already reached the trigger.
|
|
68
|
+
* Android back closes it while it is the top overlay.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* <Tooltip.Content placement="bottom">
|
|
72
|
+
* <Tooltip.Arrow />
|
|
73
|
+
* <Tooltip.Text>Copy link</Tooltip.Text>
|
|
74
|
+
* </Tooltip.Content>
|
|
75
|
+
*/
|
|
76
|
+
export function TooltipContent({
|
|
77
|
+
children,
|
|
78
|
+
className,
|
|
79
|
+
placement = TOOLTIP_DEFAULTS.placement,
|
|
80
|
+
align = TOOLTIP_DEFAULTS.align,
|
|
81
|
+
offset = TOOLTIP_DEFAULTS.offset,
|
|
82
|
+
alignOffset = TOOLTIP_DEFAULTS.alignOffset,
|
|
83
|
+
variant = TOOLTIP_DEFAULTS.variant,
|
|
84
|
+
width = TOOLTIP_DEFAULTS.width,
|
|
85
|
+
minWidth,
|
|
86
|
+
maxHeight,
|
|
87
|
+
isScrollable = false,
|
|
88
|
+
style,
|
|
89
|
+
onTouchStart,
|
|
90
|
+
...props
|
|
91
|
+
}: TooltipContentProps): ReactElement | null {
|
|
92
|
+
const { isOpen, close, duration, anchorRect, onContentTouchStart } = useTooltipContext();
|
|
93
|
+
const overlayId = useId();
|
|
94
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
95
|
+
|
|
96
|
+
const clearTimer = useCallback(() => {
|
|
97
|
+
if (timer.current === null) return;
|
|
98
|
+
clearTimeout(timer.current);
|
|
99
|
+
timer.current = null;
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
const startTimer = useCallback(() => {
|
|
103
|
+
clearTimer();
|
|
104
|
+
if (duration > 0) timer.current = setTimeout(close, duration);
|
|
105
|
+
}, [clearTimer, close, duration]);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!isOpen) clearTimer();
|
|
109
|
+
}, [isOpen, clearTimer]);
|
|
110
|
+
useEffect(() => clearTimer, [clearTimer]);
|
|
111
|
+
|
|
112
|
+
const anchored = useAnchoredContent({
|
|
113
|
+
isOpen,
|
|
114
|
+
anchor: anchorRect,
|
|
115
|
+
placement,
|
|
116
|
+
align,
|
|
117
|
+
offset,
|
|
118
|
+
alignOffset,
|
|
119
|
+
collisionPadding: POPOVER_COLLISION_PADDING,
|
|
120
|
+
arrowInset: POPOVER_ARROW_INSET,
|
|
121
|
+
width,
|
|
122
|
+
minWidth,
|
|
123
|
+
maxHeight,
|
|
124
|
+
enterDistance: TOOLTIP_ENTER_DISTANCE,
|
|
125
|
+
enterScale: 1,
|
|
126
|
+
onEntered: startTimer,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
useOverlayBackHandler({ id: overlayId, isEnabled: anchored.isMounted, onBack: close });
|
|
130
|
+
|
|
131
|
+
const contentContext = useMemo<TooltipContentContextValue>(
|
|
132
|
+
() => ({
|
|
133
|
+
variant,
|
|
134
|
+
placement: anchored.position?.placement ?? placement,
|
|
135
|
+
arrowOffset: anchored.position?.arrowOffset ?? 0,
|
|
136
|
+
size: anchored.size ?? { width: 0, height: 0 },
|
|
137
|
+
}),
|
|
138
|
+
[variant, anchored.position?.placement, placement, anchored.position?.arrowOffset, anchored.size]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!anchored.isMounted) return null;
|
|
142
|
+
|
|
143
|
+
const { arrows, body } = partitionArrow(children);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Overlay.Portal id={overlayId} layer="anchored">
|
|
147
|
+
<Animated.View
|
|
148
|
+
accessibilityElementsHidden
|
|
149
|
+
importantForAccessibility="no-hide-descendants"
|
|
150
|
+
onLayout={anchored.onLayout}
|
|
151
|
+
pointerEvents={isScrollable ? "box-none" : "none"}
|
|
152
|
+
style={[anchored.positionerStyle, anchored.animatedStyle]}
|
|
153
|
+
>
|
|
154
|
+
<View
|
|
155
|
+
accessible={false}
|
|
156
|
+
className={tooltipVariants({ variant }).content({ className })}
|
|
157
|
+
onTouchStart={(event) => {
|
|
158
|
+
onContentTouchStart();
|
|
159
|
+
onTouchStart?.(event);
|
|
160
|
+
}}
|
|
161
|
+
style={[anchored.frameStyle, style]}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<TooltipContentContext.Provider value={contentContext}>
|
|
165
|
+
{isScrollable ? (
|
|
166
|
+
<ScrollView className="shrink grow-0" contentContainerClassName="gap-1">
|
|
167
|
+
{body}
|
|
168
|
+
</ScrollView>
|
|
169
|
+
) : (
|
|
170
|
+
body
|
|
171
|
+
)}
|
|
172
|
+
{arrows}
|
|
173
|
+
</TooltipContentContext.Provider>
|
|
174
|
+
</View>
|
|
175
|
+
</Animated.View>
|
|
176
|
+
</Overlay.Portal>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
TooltipContent.displayName = "DelacourUI.Tooltip.Content";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { useOptionalTooltipContent } from "./tooltip.context";
|
|
4
|
+
import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
|
|
5
|
+
|
|
6
|
+
export type TooltipDescriptionProps = TextPresetProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supporting copy under the title — a `Text.Caption`, muted on a surface card
|
|
10
|
+
* and the background colour at 80% on an inverted chip.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Tooltip.Description>Changes reach your other devices within a minute.</Tooltip.Description>
|
|
14
|
+
*/
|
|
15
|
+
export function TooltipDescription({ className, ...props }: TooltipDescriptionProps): ReactElement {
|
|
16
|
+
const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
|
|
17
|
+
return <Text.Caption className={tooltipVariants({ variant }).description({ className })} {...props} />;
|
|
18
|
+
}
|
|
19
|
+
TooltipDescription.displayName = "DelacourUI.Tooltip.Description";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { useOptionalTooltipContent } from "./tooltip.context";
|
|
4
|
+
import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
|
|
5
|
+
|
|
6
|
+
export type TooltipTextProps = TextPresetProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The one-line label — a `Text.Caption`, inked for the panel it sits on: the
|
|
10
|
+
* background colour on an inverted chip, the popover foreground on a surface
|
|
11
|
+
* card. Caption rather than label because a tooltip is an aside to its
|
|
12
|
+
* control, not a second control.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <Tooltip.Text>Share</Tooltip.Text>
|
|
16
|
+
*/
|
|
17
|
+
export function TooltipText({ className, ...props }: TooltipTextProps): ReactElement {
|
|
18
|
+
const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
|
|
19
|
+
return <Text.Caption className={tooltipVariants({ variant }).text({ className })} {...props} />;
|
|
20
|
+
}
|
|
21
|
+
TooltipText.displayName = "DelacourUI.Tooltip.Text";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Text, type TextPresetProps } from "../text";
|
|
3
|
+
import { useOptionalTooltipContent } from "./tooltip.context";
|
|
4
|
+
import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
|
|
5
|
+
|
|
6
|
+
export type TooltipTitleProps = TextPresetProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A heading over a description, for the surface variant — a `Text.Label`, the
|
|
10
|
+
* size a popover's title takes. Not announced as a header: the panel is hidden
|
|
11
|
+
* from assistive technology, and its words reach the trigger through `label`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <Tooltip.Title>Sync</Tooltip.Title>
|
|
15
|
+
*/
|
|
16
|
+
export function TooltipTitle({ className, ...props }: TooltipTitleProps): ReactElement {
|
|
17
|
+
const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
|
|
18
|
+
return <Text.Label className={tooltipVariants({ variant }).title({ className })} {...props} />;
|
|
19
|
+
}
|
|
20
|
+
TooltipTitle.displayName = "DelacourUI.Tooltip.Title";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { isValidElement, type ReactElement } from "react";
|
|
2
|
+
import type { GestureResponderEvent } from "react-native";
|
|
3
|
+
import { Slot } from "../../lib/slot";
|
|
4
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
5
|
+
import { useTooltipContext } from "./tooltip.context";
|
|
6
|
+
import { readableTextOf, resolveTooltipAccessibility } from "./tooltip.variants";
|
|
7
|
+
|
|
8
|
+
export type TooltipTriggerProps = PressableProps;
|
|
9
|
+
|
|
10
|
+
/** The `accessibilityLabel` an `asChild` trigger's child already carries, if any. */
|
|
11
|
+
function childLabel(children: PressableProps["children"]): string | undefined {
|
|
12
|
+
if (!isValidElement<{ accessibilityLabel?: unknown }>(children)) return undefined;
|
|
13
|
+
const label = children.props.accessibilityLabel;
|
|
14
|
+
return typeof label === "string" ? label : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What the trigger is already called: its own label, the child's when it is
|
|
19
|
+
* donated to, or else its visible text — which is what a screen reader names
|
|
20
|
+
* a control by when nothing else does.
|
|
21
|
+
*/
|
|
22
|
+
function triggerName(
|
|
23
|
+
accessibilityLabel: string | undefined,
|
|
24
|
+
asChild: boolean,
|
|
25
|
+
children: PressableProps["children"]
|
|
26
|
+
): string | undefined {
|
|
27
|
+
return accessibilityLabel ?? (asChild ? childLabel(children) : undefined) ?? readableTextOf(children);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The control the tooltip names, and the view the panel is anchored to.
|
|
32
|
+
*
|
|
33
|
+
* On its own it is this library's `Pressable`. **`asChild` donates the
|
|
34
|
+
* gesture** rather than wrapping the child, for `Popover.Trigger`'s reason —
|
|
35
|
+
* a `Button` inside a pressable trigger would win the touch. With the default
|
|
36
|
+
* `openOn="longPress"` the trigger hands the child an `onLongPress`; with
|
|
37
|
+
* `"press"`, an `onPress`. Either is chained ahead of the child's own, so the
|
|
38
|
+
* child's handler still runs, and a long press never costs the child its tap:
|
|
39
|
+
* a tap held past the long-press delay fails, so `onPress` does not fire for
|
|
40
|
+
* it. The measuring ref is composed onto the child's own, so the child has to
|
|
41
|
+
* be built on `Pressable`.
|
|
42
|
+
*
|
|
43
|
+
* The tooltip's `label` becomes the trigger's accessibility label when it has
|
|
44
|
+
* none, or its hint when it does — and visible text counts as a name, so a
|
|
45
|
+
* `<Button>Sync now</Button>` keeps saying "Sync now".
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* <Tooltip.Trigger asChild>
|
|
49
|
+
* <Button size="icon-md" variant="ghost"><Icon icon={IconShare} /></Button>
|
|
50
|
+
* </Tooltip.Trigger>
|
|
51
|
+
*/
|
|
52
|
+
export function TooltipTrigger({
|
|
53
|
+
asChild = false,
|
|
54
|
+
accessibilityLabel,
|
|
55
|
+
children,
|
|
56
|
+
onPress,
|
|
57
|
+
onLongPress,
|
|
58
|
+
onTouchStart,
|
|
59
|
+
...props
|
|
60
|
+
}: TooltipTriggerProps): ReactElement {
|
|
61
|
+
const { openOn, label, activate, onTriggerTouchStart, triggerRef } = useTooltipContext();
|
|
62
|
+
|
|
63
|
+
const accessibility = resolveTooltipAccessibility({
|
|
64
|
+
label,
|
|
65
|
+
triggerLabel: triggerName(accessibilityLabel, asChild, children),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const handlers = {
|
|
69
|
+
onTouchStart: (event: GestureResponderEvent) => {
|
|
70
|
+
onTriggerTouchStart();
|
|
71
|
+
onTouchStart?.(event);
|
|
72
|
+
},
|
|
73
|
+
onPress:
|
|
74
|
+
openOn === "press"
|
|
75
|
+
? () => {
|
|
76
|
+
activate("press");
|
|
77
|
+
onPress?.();
|
|
78
|
+
}
|
|
79
|
+
: onPress,
|
|
80
|
+
onLongPress:
|
|
81
|
+
openOn === "longPress"
|
|
82
|
+
? () => {
|
|
83
|
+
activate("longPress");
|
|
84
|
+
onLongPress?.();
|
|
85
|
+
}
|
|
86
|
+
: onLongPress,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (asChild) {
|
|
90
|
+
return (
|
|
91
|
+
<Slot {...props} accessibilityLabel={accessibilityLabel} {...accessibility} {...handlers} ref={triggerRef}>
|
|
92
|
+
{children}
|
|
93
|
+
</Slot>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Pressable {...props} accessibilityLabel={accessibilityLabel} {...accessibility} {...handlers} ref={triggerRef}>
|
|
99
|
+
{children}
|
|
100
|
+
</Pressable>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
TooltipTrigger.displayName = "DelacourUI.Tooltip.Trigger";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { AnchoredSize, AnchorRect, PopoverPlacement } from "../popover/popover.position";
|
|
3
|
+
import type { MeasurableNode } from "../popover/use-anchor-measure";
|
|
4
|
+
import type { TooltipGesture, TooltipOpenOn, TooltipVariant } from "./tooltip.variants";
|
|
5
|
+
|
|
6
|
+
/** What `Tooltip` shares with its parts. */
|
|
7
|
+
export type TooltipContextValue = {
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
setOpen: (isOpen: boolean) => void;
|
|
10
|
+
close: () => void;
|
|
11
|
+
openOn: TooltipOpenOn;
|
|
12
|
+
/** The tooltip's words, for the trigger's label or hint. */
|
|
13
|
+
label: string | undefined;
|
|
14
|
+
/** ms after the entrance settles before it hides; 0 for never. Already resolved against the screen reader. */
|
|
15
|
+
duration: number;
|
|
16
|
+
/** The trigger reports a gesture; the root decides whether it toggles. */
|
|
17
|
+
activate: (gesture: TooltipGesture) => void;
|
|
18
|
+
/** The trigger reports that a touch started on it — before the provider hears the same touch. */
|
|
19
|
+
onTriggerTouchStart: () => void;
|
|
20
|
+
/** The panel reports that a touch started on it, so a scrollable body is not an outside tap. */
|
|
21
|
+
onContentTouchStart: () => void;
|
|
22
|
+
/** The trigger's frame in window coordinates, `null` until it is measured on open. */
|
|
23
|
+
anchorRect: AnchorRect | null;
|
|
24
|
+
/** The callback ref `Tooltip.Trigger` measures through. */
|
|
25
|
+
triggerRef: (node: MeasurableNode | null) => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const TooltipContext = createContext<TooltipContextValue | null>(null);
|
|
29
|
+
TooltipContext.displayName = "DelacourUI.Tooltip.Context";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The tooltip a part sits in — open state and the close. Throws outside a
|
|
33
|
+
* `<Tooltip>`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* function Hint() {
|
|
37
|
+
* const { isOpen } = useTooltip();
|
|
38
|
+
* return isOpen ? <Text.Caption>Shown</Text.Caption> : null;
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
export function useTooltip(): Pick<TooltipContextValue, "isOpen" | "setOpen" | "close"> {
|
|
42
|
+
const context = useContext(TooltipContext);
|
|
43
|
+
if (context === null) throw new Error("useTooltip must be used inside a <Tooltip>.");
|
|
44
|
+
return context;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The full context, for the parts. Throws outside a `<Tooltip>`. */
|
|
48
|
+
export function useTooltipContext(): TooltipContextValue {
|
|
49
|
+
const context = useContext(TooltipContext);
|
|
50
|
+
if (context === null) throw new Error("Tooltip parts must be used inside a <Tooltip>.");
|
|
51
|
+
return context;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What `Tooltip.Content` shares with the parts drawn inside the panel. */
|
|
55
|
+
export type TooltipContentContextValue = {
|
|
56
|
+
variant: TooltipVariant;
|
|
57
|
+
placement: PopoverPlacement;
|
|
58
|
+
arrowOffset: number;
|
|
59
|
+
size: AnchoredSize;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const TooltipContentContext = createContext<TooltipContentContextValue | null>(null);
|
|
63
|
+
TooltipContentContext.displayName = "DelacourUI.Tooltip.ContentContext";
|
|
64
|
+
|
|
65
|
+
/** The panel's variant and placement, or `null` outside `Tooltip.Content`. */
|
|
66
|
+
export function useOptionalTooltipContent(): TooltipContentContextValue | null {
|
|
67
|
+
return useContext(TooltipContentContext);
|
|
68
|
+
}
|