@delacour/react-native-ui 0.1.0-alpha.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/package.json +48 -4
- package/src/components/accordion/AGENTS.md +21 -9
- package/src/components/accordion/accordion-content.tsx +7 -4
- package/src/components/accordion/accordion-item.tsx +15 -13
- package/src/components/accordion/accordion.context.tsx +11 -5
- package/src/components/accordion/accordion.variants.test.ts +64 -3
- package/src/components/accordion/accordion.variants.ts +33 -9
- package/src/components/alert/AGENTS.md +102 -0
- package/src/components/alert/alert-action.tsx +17 -0
- package/src/components/alert/alert-close-button.tsx +50 -0
- package/src/components/alert/alert-content.tsx +15 -0
- package/src/components/alert/alert-description.tsx +16 -0
- package/src/components/alert/alert-glyphs.ts +25 -0
- package/src/components/alert/alert-indicator.tsx +40 -0
- package/src/components/alert/alert-title.tsx +18 -0
- package/src/components/alert/alert.context.tsx +69 -0
- package/src/components/alert/alert.tsx +160 -0
- package/src/components/alert/alert.types.ts +10 -0
- package/src/components/alert/alert.variants.test.ts +218 -0
- package/src/components/alert/alert.variants.ts +180 -0
- package/src/components/alert/index.ts +23 -0
- package/src/components/avatar/AGENTS.md +106 -0
- package/src/components/avatar/avatar-badge.tsx +51 -0
- package/src/components/avatar/avatar-group.tsx +139 -0
- package/src/components/avatar/avatar.context.tsx +93 -0
- package/src/components/avatar/avatar.tsx +201 -0
- package/src/components/avatar/avatar.utils.test.ts +174 -0
- package/src/components/avatar/avatar.utils.ts +141 -0
- package/src/components/avatar/avatar.variants.test.ts +251 -0
- package/src/components/avatar/avatar.variants.ts +270 -0
- package/src/components/avatar/index.ts +39 -0
- package/src/components/badge/AGENTS.md +5 -0
- package/src/components/badge/badge.tsx +6 -1
- package/src/components/bottom-sheet/AGENTS.md +186 -231
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
- package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
- package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
- package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
- package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
- package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
- package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
- package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
- package/src/components/bottom-sheet/index.ts +39 -17
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
- package/src/components/button/AGENTS.md +45 -11
- package/src/components/button/button-group.tsx +44 -8
- package/src/components/button/button.context.tsx +10 -0
- package/src/components/button/button.tsx +2 -1
- package/src/components/button/button.variants.test.ts +154 -0
- package/src/components/button/button.variants.ts +71 -6
- package/src/components/button/index.ts +2 -0
- package/src/components/calendar/AGENTS.md +142 -0
- package/src/components/calendar/calendar-caption.tsx +116 -0
- package/src/components/calendar/calendar-day.tsx +103 -0
- package/src/components/calendar/calendar-grid.tsx +183 -0
- package/src/components/calendar/calendar-header.tsx +34 -0
- package/src/components/calendar/calendar-nav.tsx +51 -0
- package/src/components/calendar/calendar-picker.tsx +128 -0
- package/src/components/calendar/calendar-weekdays.tsx +48 -0
- package/src/components/calendar/calendar.context.tsx +108 -0
- package/src/components/calendar/calendar.date.test.ts +456 -0
- package/src/components/calendar/calendar.date.ts +584 -0
- package/src/components/calendar/calendar.tsx +389 -0
- package/src/components/calendar/calendar.types.ts +87 -0
- package/src/components/calendar/calendar.variants.test.ts +363 -0
- package/src/components/calendar/calendar.variants.ts +426 -0
- package/src/components/calendar/index.ts +96 -0
- package/src/components/card/AGENTS.md +88 -0
- package/src/components/card/card-action.tsx +16 -0
- package/src/components/card/card-content.tsx +12 -0
- package/src/components/card/card-description.tsx +12 -0
- package/src/components/card/card-footer.tsx +30 -0
- package/src/components/card/card-header.tsx +45 -0
- package/src/components/card/card-title.tsx +25 -0
- package/src/components/card/card.context.tsx +64 -0
- package/src/components/card/card.tsx +94 -0
- package/src/components/card/card.types.ts +13 -0
- package/src/components/card/card.variants.test.ts +240 -0
- package/src/components/card/card.variants.ts +177 -0
- package/src/components/card/index.ts +15 -0
- package/src/components/chart/AGENTS.md +8 -0
- package/src/components/chart/chart.tsx +8 -1
- package/src/components/chip/AGENTS.md +113 -0
- package/src/components/chip/chip-close-button.tsx +48 -0
- package/src/components/chip/chip-end-content.tsx +15 -0
- package/src/components/chip/chip-label.tsx +21 -0
- package/src/components/chip/chip-start-content.tsx +16 -0
- package/src/components/chip/chip.context.tsx +66 -0
- package/src/components/chip/chip.tsx +268 -0
- package/src/components/chip/chip.types.ts +10 -0
- package/src/components/chip/chip.variants.test.ts +335 -0
- package/src/components/chip/chip.variants.ts +321 -0
- package/src/components/chip/index.ts +24 -0
- package/src/components/collapsible/AGENTS.md +97 -0
- package/src/components/collapsible/collapsible-content.tsx +97 -0
- package/src/components/collapsible/collapsible-description.tsx +17 -0
- package/src/components/collapsible/collapsible-indicator.tsx +92 -0
- package/src/components/collapsible/collapsible-title.tsx +17 -0
- package/src/components/collapsible/collapsible-trigger.tsx +133 -0
- package/src/components/collapsible/collapsible.context.tsx +91 -0
- package/src/components/collapsible/collapsible.tsx +167 -0
- package/src/components/collapsible/collapsible.types.ts +9 -0
- package/src/components/collapsible/collapsible.variants.test.ts +338 -0
- package/src/components/collapsible/collapsible.variants.ts +228 -0
- package/src/components/collapsible/index.ts +32 -0
- package/src/components/context-menu/AGENTS.md +103 -0
- package/src/components/context-menu/context-menu-content.tsx +64 -0
- package/src/components/context-menu/context-menu-item.tsx +22 -0
- package/src/components/context-menu/context-menu-preview.tsx +60 -0
- package/src/components/context-menu/context-menu-trigger.tsx +160 -0
- package/src/components/context-menu/context-menu.context.tsx +91 -0
- package/src/components/context-menu/context-menu.tsx +90 -0
- package/src/components/context-menu/context-menu.variants.test.ts +266 -0
- package/src/components/context-menu/context-menu.variants.ts +147 -0
- package/src/components/context-menu/index.ts +18 -0
- package/src/components/dialog/AGENTS.md +97 -0
- package/src/components/dialog/dialog-body.tsx +21 -0
- package/src/components/dialog/dialog-close.tsx +82 -0
- package/src/components/dialog/dialog-content.tsx +153 -0
- package/src/components/dialog/dialog-description.tsx +23 -0
- package/src/components/dialog/dialog-footer.tsx +32 -0
- package/src/components/dialog/dialog-header.tsx +20 -0
- package/src/components/dialog/dialog-title.tsx +42 -0
- package/src/components/dialog/dialog-trigger.tsx +54 -0
- package/src/components/dialog/dialog.context.tsx +90 -0
- package/src/components/dialog/dialog.tsx +119 -0
- package/src/components/dialog/dialog.variants.test.ts +224 -0
- package/src/components/dialog/dialog.variants.ts +173 -0
- package/src/components/dialog/index.ts +30 -0
- package/src/components/drawer/AGENTS.md +123 -0
- package/src/components/drawer/drawer-body.tsx +46 -0
- package/src/components/drawer/drawer-close.tsx +76 -0
- package/src/components/drawer/drawer-content.tsx +186 -0
- package/src/components/drawer/drawer-description.tsx +23 -0
- package/src/components/drawer/drawer-footer.tsx +21 -0
- package/src/components/drawer/drawer-header.tsx +42 -0
- package/src/components/drawer/drawer-title.tsx +35 -0
- package/src/components/drawer/drawer-trigger.tsx +54 -0
- package/src/components/drawer/drawer.context.tsx +114 -0
- package/src/components/drawer/drawer.tsx +119 -0
- package/src/components/drawer/drawer.variants.test.ts +392 -0
- package/src/components/drawer/drawer.variants.ts +244 -0
- package/src/components/drawer/index.ts +41 -0
- package/src/components/drawer/use-drawer-pan.ts +108 -0
- package/src/components/empty-state/AGENTS.md +90 -0
- package/src/components/empty-state/empty-state-content.tsx +17 -0
- package/src/components/empty-state/empty-state-description.tsx +12 -0
- package/src/components/empty-state/empty-state-header.tsx +17 -0
- package/src/components/empty-state/empty-state-media.tsx +56 -0
- package/src/components/empty-state/empty-state-title.tsx +18 -0
- package/src/components/empty-state/empty-state.context.tsx +65 -0
- package/src/components/empty-state/empty-state.tsx +84 -0
- package/src/components/empty-state/empty-state.types.ts +14 -0
- package/src/components/empty-state/empty-state.variants.test.ts +245 -0
- package/src/components/empty-state/empty-state.variants.ts +132 -0
- package/src/components/empty-state/index.ts +20 -0
- package/src/components/fab/AGENTS.md +119 -0
- package/src/components/fab/fab-action.tsx +118 -0
- package/src/components/fab/fab-group.tsx +214 -0
- package/src/components/fab/fab-label.tsx +18 -0
- package/src/components/fab/fab.context.tsx +125 -0
- package/src/components/fab/fab.tsx +156 -0
- package/src/components/fab/fab.types.ts +17 -0
- package/src/components/fab/fab.variants.test.ts +274 -0
- package/src/components/fab/fab.variants.ts +234 -0
- package/src/components/fab/index.ts +37 -0
- package/src/components/feedback/AGENTS.md +95 -0
- package/src/components/feedback/feedback-action.tsx +16 -0
- package/src/components/feedback/feedback-cancel.tsx +34 -0
- package/src/components/feedback/feedback-close.tsx +21 -0
- package/src/components/feedback/feedback-content.tsx +24 -0
- package/src/components/feedback/feedback-field.tsx +70 -0
- package/src/components/feedback/feedback-footer.tsx +21 -0
- package/src/components/feedback/feedback-panel.tsx +54 -0
- package/src/components/feedback/feedback-submit.tsx +74 -0
- package/src/components/feedback/feedback-title.tsx +34 -0
- package/src/components/feedback/feedback-trigger.tsx +20 -0
- package/src/components/feedback/feedback.context.tsx +73 -0
- package/src/components/feedback/feedback.tsx +135 -0
- package/src/components/feedback/feedback.variants.test.ts +185 -0
- package/src/components/feedback/feedback.variants.ts +128 -0
- package/src/components/feedback/index.ts +24 -0
- package/src/components/field/AGENTS.md +19 -0
- package/src/components/field/field-label.tsx +19 -5
- package/src/components/field/field.context.tsx +17 -0
- package/src/components/field/field.tsx +15 -2
- package/src/components/field/field.variants.test.ts +24 -0
- package/src/components/field/field.variants.ts +20 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/item/AGENTS.md +92 -0
- package/src/components/item/index.ts +25 -0
- package/src/components/item/item-actions.tsx +28 -0
- package/src/components/item/item-content.tsx +15 -0
- package/src/components/item/item-description.tsx +12 -0
- package/src/components/item/item-footer.tsx +13 -0
- package/src/components/item/item-group.tsx +32 -0
- package/src/components/item/item-header.tsx +13 -0
- package/src/components/item/item-media.tsx +37 -0
- package/src/components/item/item-separator.tsx +13 -0
- package/src/components/item/item-title.tsx +17 -0
- package/src/components/item/item.context.tsx +66 -0
- package/src/components/item/item.tsx +218 -0
- package/src/components/item/item.types.ts +12 -0
- package/src/components/item/item.variants.test.ts +263 -0
- package/src/components/item/item.variants.ts +200 -0
- package/src/components/kpi/AGENTS.md +125 -0
- package/src/components/kpi/index.ts +45 -0
- package/src/components/kpi/kpi-action.tsx +12 -0
- package/src/components/kpi/kpi-content.tsx +35 -0
- package/src/components/kpi/kpi-footer.tsx +52 -0
- package/src/components/kpi/kpi-group.tsx +70 -0
- package/src/components/kpi/kpi-header.tsx +25 -0
- package/src/components/kpi/kpi-icon.tsx +28 -0
- package/src/components/kpi/kpi-sparkline.tsx +145 -0
- package/src/components/kpi/kpi-stat.tsx +20 -0
- package/src/components/kpi/kpi-title.tsx +19 -0
- package/src/components/kpi/kpi-trend.tsx +105 -0
- package/src/components/kpi/kpi-value.tsx +32 -0
- package/src/components/kpi/kpi.context.tsx +111 -0
- package/src/components/kpi/kpi.tsx +154 -0
- package/src/components/kpi/kpi.types.ts +9 -0
- package/src/components/kpi/kpi.variants.test.ts +428 -0
- package/src/components/kpi/kpi.variants.ts +347 -0
- package/src/components/label/AGENTS.md +89 -0
- package/src/components/label/index.ts +10 -0
- package/src/components/label/label.tsx +97 -0
- package/src/components/label/label.variants.test.ts +176 -0
- package/src/components/label/label.variants.ts +123 -0
- package/src/components/menu/AGENTS.md +176 -0
- package/src/components/menu/index.ts +44 -0
- package/src/components/menu/menu-background.tsx +17 -0
- package/src/components/menu/menu-checkbox-item.tsx +57 -0
- package/src/components/menu/menu-content.tsx +303 -0
- package/src/components/menu/menu-item.tsx +162 -0
- package/src/components/menu/menu-label.tsx +14 -0
- package/src/components/menu/menu-radio-group.tsx +50 -0
- package/src/components/menu/menu-radio-item.tsx +57 -0
- package/src/components/menu/menu-separator.tsx +11 -0
- package/src/components/menu/menu-sub-content.tsx +72 -0
- package/src/components/menu/menu-sub-trigger.tsx +50 -0
- package/src/components/menu/menu-sub.tsx +75 -0
- package/src/components/menu/menu-trigger.tsx +57 -0
- package/src/components/menu/menu.context.tsx +214 -0
- package/src/components/menu/menu.tsx +88 -0
- package/src/components/menu/menu.types.ts +33 -0
- package/src/components/menu/menu.variants.test.ts +400 -0
- package/src/components/menu/menu.variants.ts +353 -0
- package/src/components/meter/AGENTS.md +135 -0
- package/src/components/meter/index.ts +43 -0
- package/src/components/meter/meter-fill.tsx +19 -0
- package/src/components/meter/meter-header.tsx +17 -0
- package/src/components/meter/meter-label.tsx +18 -0
- package/src/components/meter/meter-output.tsx +42 -0
- package/src/components/meter/meter-segments.tsx +83 -0
- package/src/components/meter/meter-track.tsx +16 -0
- package/src/components/meter/meter.context.tsx +62 -0
- package/src/components/meter/meter.tsx +286 -0
- package/src/components/meter/meter.types.ts +30 -0
- package/src/components/meter/meter.variants.test.ts +430 -0
- package/src/components/meter/meter.variants.ts +347 -0
- package/src/components/overlay/AGENTS.md +89 -0
- package/src/components/overlay/index.ts +27 -0
- package/src/components/overlay/overlay-portal.tsx +84 -0
- package/src/components/overlay/overlay-presence.test.ts +46 -0
- package/src/components/overlay/overlay-presence.ts +49 -0
- package/src/components/overlay/overlay-registry.test.ts +96 -0
- package/src/components/overlay/overlay-registry.ts +99 -0
- package/src/components/overlay/overlay-scrim.tsx +49 -0
- package/src/components/overlay/overlay.context.tsx +48 -0
- package/src/components/overlay/overlay.tsx +118 -0
- package/src/components/overlay/overlay.variants.test.ts +37 -0
- package/src/components/overlay/overlay.variants.ts +33 -0
- package/src/components/overlay/use-overlay-back-handler.ts +34 -0
- package/src/components/overlay/use-overlay-presence.ts +87 -0
- package/src/components/popover/AGENTS.md +138 -0
- package/src/components/popover/index.ts +48 -0
- package/src/components/popover/popover-anchor.tsx +49 -0
- package/src/components/popover/popover-arrow.tsx +89 -0
- package/src/components/popover/popover-close.tsx +73 -0
- package/src/components/popover/popover-content.tsx +235 -0
- package/src/components/popover/popover-description.tsx +18 -0
- package/src/components/popover/popover-title.tsx +33 -0
- package/src/components/popover/popover-trigger.tsx +56 -0
- package/src/components/popover/popover.context.tsx +71 -0
- package/src/components/popover/popover.position.test.ts +354 -0
- package/src/components/popover/popover.position.ts +286 -0
- package/src/components/popover/popover.tsx +123 -0
- package/src/components/popover/popover.variants.test.ts +135 -0
- package/src/components/popover/popover.variants.ts +85 -0
- package/src/components/popover/use-anchor-measure.ts +76 -0
- package/src/components/popover/use-anchored-content.ts +219 -0
- package/src/components/pressable/AGENTS.md +11 -1
- package/src/components/pressable/pressable.tsx +7 -2
- package/src/components/pressable/pressable.variants.test.ts +29 -0
- package/src/components/pressable/pressable.variants.ts +17 -0
- package/src/components/progress/AGENTS.md +120 -0
- package/src/components/progress/index.ts +40 -0
- package/src/components/progress/progress-fill.tsx +152 -0
- package/src/components/progress/progress-header.tsx +19 -0
- package/src/components/progress/progress-label.tsx +27 -0
- package/src/components/progress/progress-output.tsx +47 -0
- package/src/components/progress/progress-track.tsx +38 -0
- package/src/components/progress/progress.context.tsx +79 -0
- package/src/components/progress/progress.tsx +171 -0
- package/src/components/progress/progress.types.ts +23 -0
- package/src/components/progress/progress.variants.test.ts +335 -0
- package/src/components/progress/progress.variants.ts +284 -0
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/provider/AGENTS.md +38 -25
- package/src/components/provider/provider.tsx +38 -11
- package/src/components/radio/AGENTS.md +8 -10
- package/src/components/radio/radio.variants.test.ts +5 -6
- package/src/components/radio/radio.variants.ts +6 -7
- package/src/components/rating/AGENTS.md +143 -0
- package/src/components/rating/index.ts +43 -0
- package/src/components/rating/rating-output.tsx +39 -0
- package/src/components/rating/rating-star.tsx +70 -0
- package/src/components/rating/rating-stars.tsx +219 -0
- package/src/components/rating/rating.context.tsx +79 -0
- package/src/components/rating/rating.tsx +163 -0
- package/src/components/rating/rating.variants.test.ts +347 -0
- package/src/components/rating/rating.variants.ts +354 -0
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
- package/src/components/skeleton/AGENTS.md +120 -0
- package/src/components/skeleton/index.ts +15 -0
- package/src/components/skeleton/skeleton-clock.ts +48 -0
- package/src/components/skeleton/skeleton-group.tsx +64 -0
- package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
- package/src/components/skeleton/skeleton.context.tsx +45 -0
- package/src/components/skeleton/skeleton.tsx +227 -0
- package/src/components/skeleton/skeleton.variants.test.ts +245 -0
- package/src/components/skeleton/skeleton.variants.ts +222 -0
- package/src/components/slide-button/AGENTS.md +122 -0
- package/src/components/slide-button/index.ts +43 -0
- package/src/components/slide-button/slide-button-label.tsx +70 -0
- package/src/components/slide-button/slide-button-thumb.tsx +93 -0
- package/src/components/slide-button/slide-button.context.tsx +76 -0
- package/src/components/slide-button/slide-button.tsx +457 -0
- package/src/components/slide-button/slide-button.variants.test.ts +288 -0
- package/src/components/slide-button/slide-button.variants.ts +324 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider-thumb.tsx +33 -1
- package/src/components/slider/slider.variants.test.ts +52 -0
- package/src/components/slider/slider.variants.ts +56 -2
- package/src/components/stack-card/AGENTS.md +169 -0
- package/src/components/stack-card/index.ts +37 -0
- package/src/components/stack-card/stack-card-action.tsx +66 -0
- package/src/components/stack-card/stack-card-actions.tsx +18 -0
- package/src/components/stack-card/stack-card-card.tsx +71 -0
- package/src/components/stack-card/stack-card-empty.tsx +30 -0
- package/src/components/stack-card/stack-card-slot.tsx +90 -0
- package/src/components/stack-card/stack-card-stamp.tsx +76 -0
- package/src/components/stack-card/stack-card.context.tsx +117 -0
- package/src/components/stack-card/stack-card.tsx +557 -0
- package/src/components/stack-card/stack-card.types.ts +26 -0
- package/src/components/stack-card/stack-card.variants.test.ts +355 -0
- package/src/components/stack-card/stack-card.variants.ts +360 -0
- package/src/components/steps/AGENTS.md +153 -0
- package/src/components/steps/index.ts +45 -0
- package/src/components/steps/steps-connector.tsx +31 -0
- package/src/components/steps/steps-description.tsx +26 -0
- package/src/components/steps/steps-indicator.tsx +106 -0
- package/src/components/steps/steps-item.tsx +225 -0
- package/src/components/steps/steps-panel.tsx +41 -0
- package/src/components/steps/steps-title.tsx +32 -0
- package/src/components/steps/steps-track.tsx +59 -0
- package/src/components/steps/steps.context.tsx +123 -0
- package/src/components/steps/steps.tsx +166 -0
- package/src/components/steps/steps.variants.test.ts +388 -0
- package/src/components/steps/steps.variants.ts +468 -0
- package/src/components/surface/AGENTS.md +72 -0
- package/src/components/surface/index.ts +15 -0
- package/src/components/surface/surface.context.tsx +54 -0
- package/src/components/surface/surface.tsx +70 -0
- package/src/components/surface/surface.variants.test.ts +180 -0
- package/src/components/surface/surface.variants.ts +133 -0
- package/src/components/swipe/AGENTS.md +140 -0
- package/src/components/swipe/index.ts +31 -0
- package/src/components/swipe/swipe-action.tsx +99 -0
- package/src/components/swipe/swipe-end.tsx +14 -0
- package/src/components/swipe/swipe-group.tsx +55 -0
- package/src/components/swipe/swipe-panel.tsx +55 -0
- package/src/components/swipe/swipe-start.tsx +15 -0
- package/src/components/swipe/swipe.context.tsx +135 -0
- package/src/components/swipe/swipe.tsx +393 -0
- package/src/components/swipe/swipe.types.ts +7 -0
- package/src/components/swipe/swipe.variants.test.ts +337 -0
- package/src/components/swipe/swipe.variants.ts +290 -0
- package/src/components/switch/switch.variants.ts +2 -2
- package/src/components/tabs/AGENTS.md +4 -3
- package/src/components/tabs/tabs.variants.test.ts +1 -1
- package/src/components/tabs/tabs.variants.ts +4 -6
- package/src/components/textarea/AGENTS.md +86 -0
- package/src/components/textarea/index.ts +16 -0
- package/src/components/textarea/textarea.tsx +157 -0
- package/src/components/textarea/textarea.variants.test.ts +219 -0
- package/src/components/textarea/textarea.variants.ts +171 -0
- package/src/components/toast/AGENTS.md +140 -0
- package/src/components/toast/index.ts +74 -0
- package/src/components/toast/toast-action.tsx +59 -0
- package/src/components/toast/toast-api.ts +32 -0
- package/src/components/toast/toast-close.tsx +44 -0
- package/src/components/toast/toast-content.tsx +11 -0
- package/src/components/toast/toast-description.tsx +11 -0
- package/src/components/toast/toast-indicator.tsx +34 -0
- package/src/components/toast/toast-item.tsx +308 -0
- package/src/components/toast/toast-title.tsx +13 -0
- package/src/components/toast/toast-viewport.tsx +135 -0
- package/src/components/toast/toast.context.tsx +69 -0
- package/src/components/toast/toast.store.test.ts +259 -0
- package/src/components/toast/toast.store.ts +338 -0
- package/src/components/toast/toast.timer.test.ts +64 -0
- package/src/components/toast/toast.timer.ts +51 -0
- package/src/components/toast/toast.tsx +132 -0
- package/src/components/toast/toast.variants.test.ts +393 -0
- package/src/components/toast/toast.variants.ts +304 -0
- package/src/components/toggle-button/AGENTS.md +106 -0
- package/src/components/toggle-button/index.ts +30 -0
- package/src/components/toggle-button/toggle-button-group.tsx +156 -0
- package/src/components/toggle-button/toggle-button-label.tsx +19 -0
- package/src/components/toggle-button/toggle-button.context.tsx +130 -0
- package/src/components/toggle-button/toggle-button.tsx +172 -0
- package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
- package/src/components/toggle-button/toggle-button.variants.ts +160 -0
- package/src/components/tooltip/AGENTS.md +133 -0
- package/src/components/tooltip/index.ts +28 -0
- package/src/components/tooltip/tooltip-arrow.tsx +40 -0
- package/src/components/tooltip/tooltip-content.tsx +179 -0
- package/src/components/tooltip/tooltip-description.tsx +19 -0
- package/src/components/tooltip/tooltip-text.tsx +21 -0
- package/src/components/tooltip/tooltip-title.tsx +20 -0
- package/src/components/tooltip/tooltip-trigger.tsx +103 -0
- package/src/components/tooltip/tooltip.context.tsx +68 -0
- package/src/components/tooltip/tooltip.tsx +213 -0
- package/src/components/tooltip/tooltip.variants.test.ts +231 -0
- package/src/components/tooltip/tooltip.variants.ts +177 -0
- package/src/hooks/use-calm-motion.tsx +31 -0
- package/src/lib/calm-motion.test.ts +17 -0
- package/src/lib/calm-motion.ts +23 -0
- package/src/lib/cn.test.ts +14 -0
- package/src/lib/cn.ts +2 -2
- package/src/no-gorhom.test.ts +54 -0
- package/src/styles/geometry.test.ts +1 -0
- package/src/styles/theme.css +4 -0
- package/src/styles/tokens.css +11 -0
- package/src/styles/tokens.test.ts +22 -0
- package/src/styles/tokens.ts +20 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { Button, type ButtonProps } from "../button";
|
|
3
|
+
import { useFeedbackPart } from "./feedback.context";
|
|
4
|
+
|
|
5
|
+
export type FeedbackCancelProps = ButtonProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Closes the dialog and keeps the draft — a `secondary` `Button` labelled
|
|
9
|
+
* "Cancel" unless given children. Clearing on cancel would throw away a long
|
|
10
|
+
* message the user only meant to put down for a moment.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Feedback.Cancel />
|
|
14
|
+
*/
|
|
15
|
+
export function FeedbackCancel({
|
|
16
|
+
variant = "secondary",
|
|
17
|
+
children = "Cancel",
|
|
18
|
+
onPress,
|
|
19
|
+
...props
|
|
20
|
+
}: FeedbackCancelProps): ReactElement {
|
|
21
|
+
const { close } = useFeedbackPart("Feedback.Cancel");
|
|
22
|
+
|
|
23
|
+
const handlePress = useCallback(() => {
|
|
24
|
+
close();
|
|
25
|
+
onPress?.();
|
|
26
|
+
}, [close, onPress]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Button onPress={handlePress} variant={variant} {...props}>
|
|
30
|
+
{children}
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
FeedbackCancel.displayName = "DelacourUI.Feedback.Cancel";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Dialog, type DialogCloseProps } from "../dialog";
|
|
3
|
+
import { feedbackVariants } from "./feedback.variants";
|
|
4
|
+
|
|
5
|
+
export type FeedbackCloseProps = DialogCloseProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The ✕ in the well's top-right corner — `Dialog.Close`, positioned against
|
|
9
|
+
* the panel rather than the shell. `Feedback.Title`'s `pr-8` reserves its
|
|
10
|
+
* clearance. Labelled `"Close"`; the draft is kept.
|
|
11
|
+
*
|
|
12
|
+
* With `asChild` it donates the close to its child, as `Dialog.Close` does.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <Feedback.Close />
|
|
16
|
+
*/
|
|
17
|
+
export function FeedbackClose(props: FeedbackCloseProps): ReactElement {
|
|
18
|
+
if (props.asChild) return <Dialog.Close {...props} />;
|
|
19
|
+
return <Dialog.Close {...props} className={feedbackVariants().close({ className: props.className })} />;
|
|
20
|
+
}
|
|
21
|
+
FeedbackClose.displayName = "DelacourUI.Feedback.Close";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Dialog, type DialogContentProps } from "../dialog";
|
|
3
|
+
import { feedbackVariants } from "./feedback.variants";
|
|
4
|
+
|
|
5
|
+
export type FeedbackContentProps = DialogContentProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shell — a `Dialog.Content` on the muted band.
|
|
9
|
+
*
|
|
10
|
+
* Portal, scrim, motion, keyboard lift, back button, escape and focus are all
|
|
11
|
+
* Dialog's. This only swaps the card's fill for `bg-muted` and tightens its
|
|
12
|
+
* padding, so the `Feedback.Panel` inside reads as a well sunk into it and the
|
|
13
|
+
* footer's actions sit on the band around it.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <Feedback.Content>
|
|
17
|
+
* <Feedback.Panel>…</Feedback.Panel>
|
|
18
|
+
* <Feedback.Footer>…</Feedback.Footer>
|
|
19
|
+
* </Feedback.Content>
|
|
20
|
+
*/
|
|
21
|
+
export function FeedbackContent({ className, ...props }: FeedbackContentProps): ReactElement {
|
|
22
|
+
return <Dialog.Content className={feedbackVariants().content({ className })} {...props} />;
|
|
23
|
+
}
|
|
24
|
+
FeedbackContent.displayName = "DelacourUI.Feedback.Content";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { TextInput, type TextInputProps } from "react-native";
|
|
3
|
+
import { resolvePlaceholderAccentClass, resolveSelectionAccentClass } from "../input/input.variants";
|
|
4
|
+
import { useFeedbackPart } from "./feedback.context";
|
|
5
|
+
import { feedbackVariants, resolveFeedbackFieldHeightStyle } from "./feedback.variants";
|
|
6
|
+
|
|
7
|
+
export type FeedbackFieldProps = Omit<TextInputProps, "value" | "onChangeText" | "multiline"> & {
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Rows before it grows. Default 6. */
|
|
10
|
+
minRows?: number;
|
|
11
|
+
/** Rows it grows to before it scrolls. Default 12. */
|
|
12
|
+
maxRows?: number;
|
|
13
|
+
/** Read-only — while sending, say. A send in flight makes it read-only on its own. */
|
|
14
|
+
isDisabled?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Where the message is written: a bare multiline `TextInput` with no box of
|
|
19
|
+
* its own, because the well around it is the box.
|
|
20
|
+
*
|
|
21
|
+
* The draft is the root's — `value` and `onChangeText` are withheld — so it
|
|
22
|
+
* survives the dialog closing and reopening, and `useFeedback().clear()` is
|
|
23
|
+
* the one way to empty it. It is floored at `minRows` and grows to `maxRows`
|
|
24
|
+
* before it scrolls (`resolveFeedbackFieldHeightStyle`), as a style, since a
|
|
25
|
+
* runtime number cannot be a Tailwind class.
|
|
26
|
+
*
|
|
27
|
+
* While a promise from `Feedback.Submit` is in flight it is read-only, so what
|
|
28
|
+
* is being sent is what stays on screen. Its label defaults to the title's
|
|
29
|
+
* text when the title is a plain string.
|
|
30
|
+
*
|
|
31
|
+
* Placeholder, caret and selection take `Input`'s `accent-*` defaults.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <Feedback.Field placeholder="Tell us what got in your way" />
|
|
35
|
+
*/
|
|
36
|
+
export function FeedbackField({
|
|
37
|
+
className,
|
|
38
|
+
minRows,
|
|
39
|
+
maxRows,
|
|
40
|
+
isDisabled = false,
|
|
41
|
+
accessibilityLabel,
|
|
42
|
+
editable,
|
|
43
|
+
style,
|
|
44
|
+
...props
|
|
45
|
+
}: FeedbackFieldProps): ReactElement {
|
|
46
|
+
const { value, setValue, isSending, titleText } = useFeedbackPart("Feedback.Field");
|
|
47
|
+
const isReadOnly = isDisabled || isSending;
|
|
48
|
+
const selectionAccent = resolveSelectionAccentClass({});
|
|
49
|
+
|
|
50
|
+
const handleChangeText = useCallback((text: string) => setValue(text), [setValue]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TextInput
|
|
54
|
+
accessibilityLabel={accessibilityLabel ?? titleText}
|
|
55
|
+
accessibilityState={{ disabled: isReadOnly }}
|
|
56
|
+
className={feedbackVariants().field({ className })}
|
|
57
|
+
cursorColorClassName={selectionAccent}
|
|
58
|
+
placeholderTextColorClassName={resolvePlaceholderAccentClass()}
|
|
59
|
+
selectionColorClassName={selectionAccent}
|
|
60
|
+
style={[resolveFeedbackFieldHeightStyle({ maxRows, minRows }), style]}
|
|
61
|
+
textAlignVertical="top"
|
|
62
|
+
{...props}
|
|
63
|
+
editable={isReadOnly ? false : editable}
|
|
64
|
+
multiline
|
|
65
|
+
onChangeText={handleChangeText}
|
|
66
|
+
value={value}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
FeedbackField.displayName = "DelacourUI.Feedback.Field";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { feedbackVariants } from "./feedback.variants";
|
|
4
|
+
|
|
5
|
+
export type FeedbackFooterProps = ViewProps & { className?: string };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The actions, on the band under the well — an end-aligned row, inset less
|
|
9
|
+
* than the well so the buttons line up with the shell's edge rather than the
|
|
10
|
+
* well's text.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Feedback.Footer>
|
|
14
|
+
* <Feedback.Cancel />
|
|
15
|
+
* <Feedback.Submit onSubmit={send} />
|
|
16
|
+
* </Feedback.Footer>
|
|
17
|
+
*/
|
|
18
|
+
export function FeedbackFooter({ className, ...props }: FeedbackFooterProps): ReactElement {
|
|
19
|
+
return <View className={feedbackVariants().footer({ className })} {...props} />;
|
|
20
|
+
}
|
|
21
|
+
FeedbackFooter.displayName = "DelacourUI.Feedback.Footer";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { type LayoutChangeEvent, View, type ViewProps } from "react-native";
|
|
3
|
+
import Animated, { useAnimatedStyle, useReducedMotion, useSharedValue, withTiming } from "react-native-reanimated";
|
|
4
|
+
import { FEEDBACK_PANEL_RESIZE_MS, feedbackVariants } from "./feedback.variants";
|
|
5
|
+
|
|
6
|
+
export type FeedbackPanelProps = ViewProps & { className?: string };
|
|
7
|
+
|
|
8
|
+
/** No height measured yet: the clip wraps its content until the first layout. */
|
|
9
|
+
const UNMEASURED = -1;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The recessed well the title, the close and the field sit in.
|
|
13
|
+
*
|
|
14
|
+
* **Its height is measured and animated, never laid out.** The well is drawn
|
|
15
|
+
* inside a clip whose height follows the well's own `onLayout` on a short
|
|
16
|
+
* timing, so when a multi-step flow swaps the panel's children — a rating, then
|
|
17
|
+
* a message, then thanks — the clip eases to the new height. The shell grows
|
|
18
|
+
* with it through ordinary layout and stays centred on every frame, so it never
|
|
19
|
+
* jumps; a layout transition on the panel alone would animate the panel inside
|
|
20
|
+
* a shell that had already snapped to its final size. The first layout lands
|
|
21
|
+
* without animating, and under reduce motion every change does.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <Feedback.Panel>
|
|
25
|
+
* <Feedback.Title>What should we fix first?</Feedback.Title>
|
|
26
|
+
* <Feedback.Close />
|
|
27
|
+
* <Feedback.Field placeholder="Tell us what got in your way" />
|
|
28
|
+
* </Feedback.Panel>
|
|
29
|
+
*/
|
|
30
|
+
export function FeedbackPanel({ className, onLayout, ...props }: FeedbackPanelProps): ReactElement {
|
|
31
|
+
const isReduced = useReducedMotion();
|
|
32
|
+
const height = useSharedValue(UNMEASURED);
|
|
33
|
+
|
|
34
|
+
const handleLayout = useCallback(
|
|
35
|
+
(event: LayoutChangeEvent) => {
|
|
36
|
+
const next = event.nativeEvent.layout.height;
|
|
37
|
+
height.value =
|
|
38
|
+
height.value === UNMEASURED || isReduced ? next : withTiming(next, { duration: FEEDBACK_PANEL_RESIZE_MS });
|
|
39
|
+
onLayout?.(event);
|
|
40
|
+
},
|
|
41
|
+
[height, isReduced, onLayout]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const clipStyle = useAnimatedStyle(() => (height.value === UNMEASURED ? {} : { height: height.value }));
|
|
45
|
+
|
|
46
|
+
const slots = feedbackVariants();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Animated.View className={slots.clip()} style={clipStyle}>
|
|
50
|
+
<View className={slots.panel({ className })} onLayout={handleLayout} {...props} />
|
|
51
|
+
</Animated.View>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
FeedbackPanel.displayName = "DelacourUI.Feedback.Panel";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type ReactElement, useCallback } from "react";
|
|
2
|
+
import { Button, type ButtonProps } from "../button";
|
|
3
|
+
import { useFeedbackPart } from "./feedback.context";
|
|
4
|
+
import { canSubmitFeedback } from "./feedback.variants";
|
|
5
|
+
|
|
6
|
+
export type FeedbackSubmitProps = Omit<ButtonProps, "onPress"> & {
|
|
7
|
+
/** Receives the trimmed message. Does NOT close — sending has to finish first. */
|
|
8
|
+
onSubmit: (value: string) => void | Promise<void>;
|
|
9
|
+
/** Allow submitting an empty message (e.g. when chips were picked). Default false. */
|
|
10
|
+
canSubmitEmpty?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Whether a value is a promise, without trusting `instanceof` across realms. */
|
|
14
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
15
|
+
return typeof value === "object" && value !== null && typeof (value as PromiseLike<unknown>).then === "function";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sends the message — a primary `Button` labelled "Send" unless given children.
|
|
20
|
+
*
|
|
21
|
+
* Disabled while `canSubmitFeedback` says no: an empty or whitespace draft,
|
|
22
|
+
* unless `canSubmitEmpty`. `onSubmit` receives the trimmed text.
|
|
23
|
+
*
|
|
24
|
+
* **It never closes the dialog.** When `onSubmit` returns a promise the button
|
|
25
|
+
* shows its loading state and the field turns read-only until the promise
|
|
26
|
+
* settles, and then the dialog is still open — so a failure can be shown in
|
|
27
|
+
* the well with the draft intact. Close and clear from `onSubmit` on success,
|
|
28
|
+
* through `useFeedback()` or controlled state. Handle a failure inside
|
|
29
|
+
* `onSubmit`: a rejection is not caught here, so it still reaches the app's
|
|
30
|
+
* unhandled-rejection reporting rather than vanishing.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* <Feedback.Submit
|
|
34
|
+
* onSubmit={async (message) => {
|
|
35
|
+
* await send(message);
|
|
36
|
+
* clear();
|
|
37
|
+
* close();
|
|
38
|
+
* }}
|
|
39
|
+
* />
|
|
40
|
+
*/
|
|
41
|
+
export function FeedbackSubmit({
|
|
42
|
+
onSubmit,
|
|
43
|
+
canSubmitEmpty = false,
|
|
44
|
+
isDisabled = false,
|
|
45
|
+
isLoading = false,
|
|
46
|
+
children = "Send",
|
|
47
|
+
...props
|
|
48
|
+
}: FeedbackSubmitProps): ReactElement {
|
|
49
|
+
const { value, isSending, setSending } = useFeedbackPart("Feedback.Submit");
|
|
50
|
+
const canSubmit = canSubmitFeedback({ canSubmitEmpty, isDisabled, value });
|
|
51
|
+
|
|
52
|
+
const handlePress = useCallback(() => {
|
|
53
|
+
if (!canSubmit || isSending) return;
|
|
54
|
+
const result = onSubmit(value.trim());
|
|
55
|
+
if (!isPromiseLike(result)) return;
|
|
56
|
+
|
|
57
|
+
setSending(true);
|
|
58
|
+
const settle = () => setSending(false);
|
|
59
|
+
void Promise.resolve(result).finally(settle);
|
|
60
|
+
}, [canSubmit, isSending, onSubmit, setSending, value]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Button
|
|
64
|
+
isDisabled={!canSubmit}
|
|
65
|
+
isLoading={isLoading || isSending}
|
|
66
|
+
onPress={handlePress}
|
|
67
|
+
variant="primary"
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</Button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
FeedbackSubmit.displayName = "DelacourUI.Feedback.Submit";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactElement, useEffect } from "react";
|
|
2
|
+
import { Dialog, type DialogTitleProps } from "../dialog";
|
|
3
|
+
import { useFeedbackPart } from "./feedback.context";
|
|
4
|
+
import { feedbackVariants } from "./feedback.variants";
|
|
5
|
+
|
|
6
|
+
export type FeedbackTitleProps = DialogTitleProps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The question the dialog asks — a `Dialog.Title`, inside the well.
|
|
10
|
+
*
|
|
11
|
+
* It is what the dialog is labelled by and where accessibility focus lands on
|
|
12
|
+
* open, exactly as in a `Dialog`. When its children are a plain string, that
|
|
13
|
+
* string also becomes `Feedback.Field`'s default `accessibilityLabel`, so the
|
|
14
|
+
* field announces the question rather than only its placeholder.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <Feedback.Title>What should we fix first?</Feedback.Title>
|
|
18
|
+
*/
|
|
19
|
+
export function FeedbackTitle({ className, children, ...props }: FeedbackTitleProps): ReactElement {
|
|
20
|
+
const { setTitleText } = useFeedbackPart("Feedback.Title");
|
|
21
|
+
const text = typeof children === "string" ? children : undefined;
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setTitleText(text);
|
|
25
|
+
return () => setTitleText(undefined);
|
|
26
|
+
}, [setTitleText, text]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Dialog.Title className={feedbackVariants().title({ className })} {...props}>
|
|
30
|
+
{children}
|
|
31
|
+
</Dialog.Title>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
FeedbackTitle.displayName = "DelacourUI.Feedback.Title";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { Dialog, type DialogTriggerProps } from "../dialog";
|
|
3
|
+
|
|
4
|
+
export type FeedbackTriggerProps = DialogTriggerProps;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The control that opens the feedback dialog — `Dialog.Trigger`, unchanged.
|
|
8
|
+
*
|
|
9
|
+
* `asChild` donates the open to a `Button` rather than wrapping it, for the
|
|
10
|
+
* reason `Dialog.Trigger` gives: two tap gestures nested are not simultaneous.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Feedback.Trigger asChild>
|
|
14
|
+
* <Button variant="secondary">Give feedback</Button>
|
|
15
|
+
* </Feedback.Trigger>
|
|
16
|
+
*/
|
|
17
|
+
export function FeedbackTrigger(props: FeedbackTriggerProps): ReactElement {
|
|
18
|
+
return <Dialog.Trigger {...props} />;
|
|
19
|
+
}
|
|
20
|
+
FeedbackTrigger.displayName = "DelacourUI.Feedback.Trigger";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
|
|
3
|
+
export type FeedbackContextValue = {
|
|
4
|
+
/** The draft as typed — untrimmed. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Replaces the draft. Reports through `onValueChange`. */
|
|
7
|
+
setValue: (value: string) => void;
|
|
8
|
+
/** Whether the dialog is open. */
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
/** Closes the dialog. The draft is kept. */
|
|
11
|
+
close: () => void;
|
|
12
|
+
/** Empties the draft — after a successful send, say. */
|
|
13
|
+
clear: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** What the parts share that a caller has no use for. */
|
|
17
|
+
export type FeedbackInternalValue = FeedbackContextValue & {
|
|
18
|
+
/** Whether a promise `onSubmit` returned is still in flight. */
|
|
19
|
+
isSending: boolean;
|
|
20
|
+
setSending: (isSending: boolean) => void;
|
|
21
|
+
/** The title's text when it is a plain string — the field's default label. */
|
|
22
|
+
titleText: string | undefined;
|
|
23
|
+
setTitleText: (text: string | undefined) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const FeedbackContext = createContext<FeedbackInternalValue | null>(null);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Supplies the feedback dialog's draft and send state to its parts.
|
|
30
|
+
*
|
|
31
|
+
* Its own module, importing nothing but React, so a part can read it without
|
|
32
|
+
* importing `./feedback` — that import would close a cycle, and Metro serves a
|
|
33
|
+
* partially initialised module for one.
|
|
34
|
+
*/
|
|
35
|
+
export function FeedbackProvider({
|
|
36
|
+
value,
|
|
37
|
+
children,
|
|
38
|
+
}: {
|
|
39
|
+
value: FeedbackInternalValue;
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
}): ReactElement {
|
|
42
|
+
return <FeedbackContext value={value}>{children}</FeedbackContext>;
|
|
43
|
+
}
|
|
44
|
+
FeedbackProvider.displayName = "DelacourUI.Feedback.Provider";
|
|
45
|
+
|
|
46
|
+
/** The enclosing feedback dialog's context, or null outside a `<Feedback>`. */
|
|
47
|
+
export function useFeedbackContext(): FeedbackContextValue | null {
|
|
48
|
+
return use(FeedbackContext);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reads the enclosing feedback dialog's draft, its setters and its open state.
|
|
53
|
+
*
|
|
54
|
+
* Lets a custom child — a row of chips, a rating step — read or replace the
|
|
55
|
+
* draft, and lets an `onSubmit` close and clear once the send has gone through.
|
|
56
|
+
* Throws outside a `<Feedback>`.
|
|
57
|
+
*/
|
|
58
|
+
export function useFeedback(): FeedbackContextValue {
|
|
59
|
+
const context = use(FeedbackContext);
|
|
60
|
+
if (!context) {
|
|
61
|
+
throw new Error("useFeedback must be called inside a <Feedback>.");
|
|
62
|
+
}
|
|
63
|
+
return context;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The full context, for a part that cannot work without one. Internal. */
|
|
67
|
+
export function useFeedbackPart(component: string): FeedbackInternalValue {
|
|
68
|
+
const context = use(FeedbackContext);
|
|
69
|
+
if (!context) {
|
|
70
|
+
throw new Error(`${component} must be rendered inside a <Feedback>.`);
|
|
71
|
+
}
|
|
72
|
+
return context;
|
|
73
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
3
|
+
import { Dialog } from "../dialog";
|
|
4
|
+
import { type FeedbackInternalValue, FeedbackProvider } from "./feedback.context";
|
|
5
|
+
import { FeedbackAction } from "./feedback-action";
|
|
6
|
+
import { FeedbackCancel } from "./feedback-cancel";
|
|
7
|
+
import { FeedbackClose } from "./feedback-close";
|
|
8
|
+
import { FeedbackContent } from "./feedback-content";
|
|
9
|
+
import { FeedbackField } from "./feedback-field";
|
|
10
|
+
import { FeedbackFooter } from "./feedback-footer";
|
|
11
|
+
import { FeedbackPanel } from "./feedback-panel";
|
|
12
|
+
import { FeedbackSubmit } from "./feedback-submit";
|
|
13
|
+
import { FeedbackTitle } from "./feedback-title";
|
|
14
|
+
import { FeedbackTrigger } from "./feedback-trigger";
|
|
15
|
+
|
|
16
|
+
export type FeedbackProps = {
|
|
17
|
+
/** Whether the dialog is open. Pass it to control the dialog; omit it and the dialog holds its own state. */
|
|
18
|
+
isOpen?: boolean;
|
|
19
|
+
/** Whether an uncontrolled dialog starts open. Default false. */
|
|
20
|
+
defaultOpen?: boolean;
|
|
21
|
+
/** Called with the new value whenever the dialog opens or closes, from any path. */
|
|
22
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
23
|
+
/** The draft. Pass it to control the text; omit it and the dialog keeps its own draft. */
|
|
24
|
+
value?: string;
|
|
25
|
+
/** The draft an uncontrolled dialog starts with. Default "". */
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
/** Called with the draft on every keystroke, and on `clear()`. */
|
|
28
|
+
onValueChange?: (value: string) => void;
|
|
29
|
+
/** Whether a scrim tap, Android back and the iOS escape gesture close it. Default true. */
|
|
30
|
+
isDismissible?: boolean;
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function FeedbackRoot({
|
|
35
|
+
isOpen,
|
|
36
|
+
defaultOpen = false,
|
|
37
|
+
onOpenChange,
|
|
38
|
+
value,
|
|
39
|
+
defaultValue = "",
|
|
40
|
+
onValueChange,
|
|
41
|
+
isDismissible = true,
|
|
42
|
+
children,
|
|
43
|
+
}: FeedbackProps): ReactElement {
|
|
44
|
+
const [open, setOpen] = useControllableState({ defaultValue: defaultOpen, onChange: onOpenChange, value: isOpen });
|
|
45
|
+
const [draft, setDraft] = useControllableState({ defaultValue, onChange: onValueChange, value });
|
|
46
|
+
const [isSending, setSending] = useState(false);
|
|
47
|
+
const [titleText, setTitleText] = useState<string | undefined>(undefined);
|
|
48
|
+
|
|
49
|
+
const close = useCallback(() => {
|
|
50
|
+
if (open) setOpen(false);
|
|
51
|
+
}, [open, setOpen]);
|
|
52
|
+
const clear = useCallback(() => setDraft(""), [setDraft]);
|
|
53
|
+
|
|
54
|
+
const context = useMemo<FeedbackInternalValue>(
|
|
55
|
+
() => ({
|
|
56
|
+
clear,
|
|
57
|
+
close,
|
|
58
|
+
isOpen: open,
|
|
59
|
+
isSending,
|
|
60
|
+
setSending,
|
|
61
|
+
setTitleText,
|
|
62
|
+
setValue: setDraft,
|
|
63
|
+
titleText,
|
|
64
|
+
value: draft,
|
|
65
|
+
}),
|
|
66
|
+
[clear, close, draft, isSending, open, setDraft, titleText]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// The draft lives here, above `Dialog.Content`, which unmounts once the exit
|
|
70
|
+
// animation ends — so an uncontrolled draft survives a close and a reopen.
|
|
71
|
+
return (
|
|
72
|
+
<Dialog isDismissible={isDismissible} isOpen={open} onOpenChange={setOpen}>
|
|
73
|
+
<FeedbackProvider value={context}>{children}</FeedbackProvider>
|
|
74
|
+
</Dialog>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A dialog for writing — "What should we fix first?", a bug report, a
|
|
80
|
+
* one-question survey.
|
|
81
|
+
*
|
|
82
|
+
* It is a `Dialog` whose text field sits in a recessed well, with the actions
|
|
83
|
+
* on the band around it. Open state, portal, scrim, back, escape, focus and the
|
|
84
|
+
* keyboard lift are all Dialog's; this adds the draft, a submit that waits for
|
|
85
|
+
* the send, and a well that eases between heights when a multi-step flow swaps
|
|
86
|
+
* its content. Not for confirmations or warnings — use a `Dialog`.
|
|
87
|
+
*
|
|
88
|
+
* The draft is kept across close and reopen while uncontrolled;
|
|
89
|
+
* `useFeedback().clear()` empties it. `Feedback.Submit` never closes the
|
|
90
|
+
* dialog: close from `onSubmit` once the send has gone through.
|
|
91
|
+
*
|
|
92
|
+
* Draws through `Overlay.Portal`, so the app needs `OverlayProvider` mounted
|
|
93
|
+
* once at its root.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* <Feedback>
|
|
97
|
+
* <Feedback.Trigger asChild>
|
|
98
|
+
* <Button variant="secondary">Give feedback</Button>
|
|
99
|
+
* </Feedback.Trigger>
|
|
100
|
+
* <Feedback.Content>
|
|
101
|
+
* <Feedback.Panel>
|
|
102
|
+
* <Feedback.Title>What should we fix first?</Feedback.Title>
|
|
103
|
+
* <Feedback.Close />
|
|
104
|
+
* <Feedback.Field placeholder="Tell us what got in your way" />
|
|
105
|
+
* </Feedback.Panel>
|
|
106
|
+
* <Feedback.Footer>
|
|
107
|
+
* <Feedback.Cancel />
|
|
108
|
+
* <Feedback.Submit onSubmit={send} />
|
|
109
|
+
* </Feedback.Footer>
|
|
110
|
+
* </Feedback.Content>
|
|
111
|
+
* </Feedback>
|
|
112
|
+
*/
|
|
113
|
+
export const Feedback = Object.assign(FeedbackRoot, {
|
|
114
|
+
/** The control that opens it. `asChild` donates the press to a `Button`. */
|
|
115
|
+
Trigger: FeedbackTrigger,
|
|
116
|
+
/** The shell — a `Dialog.Content` on the muted band. Renders nothing while closed. */
|
|
117
|
+
Content: FeedbackContent,
|
|
118
|
+
/** The recessed well holding the title, the close and the field. Eases between heights. */
|
|
119
|
+
Panel: FeedbackPanel,
|
|
120
|
+
/** The question — a `Dialog.Title`, and the field's default label. */
|
|
121
|
+
Title: FeedbackTitle,
|
|
122
|
+
/** The ✕ in the well's corner. Keeps the draft. */
|
|
123
|
+
Close: FeedbackClose,
|
|
124
|
+
/** The multiline text field, bound to the draft. */
|
|
125
|
+
Field: FeedbackField,
|
|
126
|
+
/** The actions, end-aligned on the band under the well. */
|
|
127
|
+
Footer: FeedbackFooter,
|
|
128
|
+
/** A custom footer action — a `secondary` `Button`. */
|
|
129
|
+
Action: FeedbackAction,
|
|
130
|
+
/** Closes and keeps the draft. */
|
|
131
|
+
Cancel: FeedbackCancel,
|
|
132
|
+
/** Sends the trimmed draft; waits on a promise; never closes. */
|
|
133
|
+
Submit: FeedbackSubmit,
|
|
134
|
+
displayName: "DelacourUI.Feedback",
|
|
135
|
+
});
|