@stevederico/skateboard-ui 3.12.0 → 4.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/CHANGELOG.md +25 -0
- package/README.md +37 -31
- package/dist/components/core/Command.js +33 -7
- package/dist/shadcn/ui/accordion.d.ts +1 -6
- package/dist/shadcn/ui/accordion.js +3 -17
- package/dist/shadcn/ui/alert-dialog.d.ts +1 -18
- package/dist/shadcn/ui/alert-dialog.js +3 -43
- package/dist/shadcn/ui/alert.d.ts +1 -13
- package/dist/shadcn/ui/alert.js +3 -28
- package/dist/shadcn/ui/aspect-ratio.d.ts +1 -4
- package/dist/shadcn/ui/aspect-ratio.js +3 -8
- package/dist/shadcn/ui/avatar.d.ts +1 -11
- package/dist/shadcn/ui/avatar.js +3 -24
- package/dist/shadcn/ui/badge.d.ts +1 -14
- package/dist/shadcn/ui/badge.js +3 -33
- package/dist/shadcn/ui/breadcrumb.d.ts +1 -10
- package/dist/shadcn/ui/breadcrumb.js +3 -37
- package/dist/shadcn/ui/button-group.d.ts +1 -13
- package/dist/shadcn/ui/button-group.js +3 -36
- package/dist/shadcn/ui/button.d.ts +1 -34
- package/dist/shadcn/ui/button.js +3 -53
- package/dist/shadcn/ui/calendar.d.ts +1 -13
- package/dist/shadcn/ui/calendar.js +3 -75
- package/dist/shadcn/ui/card.d.ts +1 -11
- package/dist/shadcn/ui/card.js +3 -25
- package/dist/shadcn/ui/checkbox.d.ts +1 -3
- package/dist/shadcn/ui/checkbox.js +3 -9
- package/dist/shadcn/ui/collapsible.d.ts +1 -5
- package/dist/shadcn/ui/collapsible.js +3 -13
- package/dist/shadcn/ui/command.d.ts +1 -19
- package/dist/shadcn/ui/command.js +3 -35
- package/dist/shadcn/ui/context-menu.d.ts +1 -29
- package/dist/shadcn/ui/context-menu.js +3 -52
- package/dist/shadcn/ui/dialog.d.ts +1 -17
- package/dist/shadcn/ui/dialog.js +3 -38
- package/dist/shadcn/ui/drawer.d.ts +1 -13
- package/dist/shadcn/ui/drawer.js +3 -196
- package/dist/shadcn/ui/dropdown-menu.d.ts +1 -29
- package/dist/shadcn/ui/dropdown-menu.js +3 -52
- package/dist/shadcn/ui/empty.d.ts +1 -14
- package/dist/shadcn/ui/empty.js +3 -33
- package/dist/shadcn/ui/field.d.ts +1 -28
- package/dist/shadcn/ui/field.js +3 -68
- package/dist/shadcn/ui/hover-card.d.ts +1 -5
- package/dist/shadcn/ui/hover-card.js +3 -14
- package/dist/shadcn/ui/input-group.d.ts +1 -28
- package/dist/shadcn/ui/input-group.js +3 -58
- package/dist/shadcn/ui/input.d.ts +1 -3
- package/dist/shadcn/ui/input.js +3 -8
- package/dist/shadcn/ui/item.d.ts +1 -34
- package/dist/shadcn/ui/item.js +3 -79
- package/dist/shadcn/ui/kbd.d.ts +1 -3
- package/dist/shadcn/ui/kbd.js +3 -9
- package/dist/shadcn/ui/label.d.ts +1 -3
- package/dist/shadcn/ui/label.js +3 -8
- package/dist/shadcn/ui/menubar.d.ts +1 -29
- package/dist/shadcn/ui/menubar.js +3 -57
- package/dist/shadcn/ui/navigation-menu.d.ts +1 -13
- package/dist/shadcn/ui/navigation-menu.js +3 -31
- package/dist/shadcn/ui/pagination.d.ts +1 -17
- package/dist/shadcn/ui/pagination.js +3 -27
- package/dist/shadcn/ui/popover.d.ts +1 -9
- package/dist/shadcn/ui/popover.js +3 -24
- package/dist/shadcn/ui/progress.d.ts +1 -7
- package/dist/shadcn/ui/progress.js +3 -20
- package/dist/shadcn/ui/radio-group.d.ts +1 -5
- package/dist/shadcn/ui/radio-group.js +3 -12
- package/dist/shadcn/ui/scroll-area.d.ts +1 -5
- package/dist/shadcn/ui/scroll-area.js +3 -12
- package/dist/shadcn/ui/select.d.ts +1 -15
- package/dist/shadcn/ui/select.js +3 -35
- package/dist/shadcn/ui/separator.d.ts +1 -3
- package/dist/shadcn/ui/separator.js +3 -8
- package/dist/shadcn/ui/sheet.d.ts +1 -14
- package/dist/shadcn/ui/sheet.js +3 -38
- package/dist/shadcn/ui/sidebar.d.ts +1 -70
- package/dist/shadcn/ui/sidebar.js +3 -256
- package/dist/shadcn/ui/skeleton.d.ts +1 -2
- package/dist/shadcn/ui/skeleton.js +3 -6
- package/dist/shadcn/ui/slider.d.ts +1 -3
- package/dist/shadcn/ui/slider.js +3 -12
- package/dist/shadcn/ui/spinner.d.ts +1 -2
- package/dist/shadcn/ui/spinner.js +3 -7
- package/dist/shadcn/ui/switch.d.ts +1 -5
- package/dist/shadcn/ui/switch.js +3 -8
- package/dist/shadcn/ui/table.d.ts +1 -10
- package/dist/shadcn/ui/table.js +3 -29
- package/dist/shadcn/ui/tabs.d.ts +1 -13
- package/dist/shadcn/ui/tabs.js +3 -29
- package/dist/shadcn/ui/textarea.d.ts +1 -3
- package/dist/shadcn/ui/textarea.js +3 -7
- package/dist/shadcn/ui/toggle-group.d.ts +1 -11
- package/dist/shadcn/ui/toggle-group.js +3 -25
- package/dist/shadcn/ui/toggle.d.ts +1 -15
- package/dist/shadcn/ui/toggle.js +3 -26
- package/dist/shadcn/ui/tooltip.d.ts +1 -6
- package/dist/shadcn/ui/tooltip.js +3 -17
- package/dist/ui/accordion.d.ts +21 -0
- package/dist/ui/accordion.js +67 -0
- package/dist/ui/alert-dialog.d.ts +32 -0
- package/dist/ui/alert-dialog.js +102 -0
- package/dist/ui/alert.d.ts +13 -0
- package/dist/ui/alert.js +28 -0
- package/dist/ui/aspect-ratio.d.ts +5 -0
- package/dist/ui/aspect-ratio.js +9 -0
- package/dist/ui/avatar.d.ts +14 -0
- package/dist/ui/avatar.js +49 -0
- package/dist/ui/badge.d.ts +22 -0
- package/dist/ui/badge.js +26 -0
- package/dist/ui/breadcrumb.d.ts +13 -0
- package/dist/ui/breadcrumb.js +29 -0
- package/dist/ui/button-group.d.ts +21 -0
- package/dist/ui/button-group.js +29 -0
- package/dist/ui/button.d.ts +38 -0
- package/dist/ui/button.js +49 -0
- package/dist/ui/calendar.d.ts +13 -0
- package/dist/ui/calendar.js +75 -0
- package/dist/ui/card.d.ts +11 -0
- package/dist/ui/card.js +25 -0
- package/dist/ui/checkbox.d.ts +17 -0
- package/dist/ui/checkbox.js +25 -0
- package/dist/ui/collapsible.d.ts +11 -0
- package/dist/ui/collapsible.js +40 -0
- package/dist/ui/command.d.ts +19 -0
- package/dist/ui/command.js +35 -0
- package/dist/ui/context-menu.d.ts +53 -0
- package/dist/ui/context-menu.js +372 -0
- package/dist/ui/dialog.d.ts +40 -0
- package/dist/ui/dialog.js +109 -0
- package/dist/ui/drawer.d.ts +36 -0
- package/dist/ui/drawer.js +243 -0
- package/dist/ui/dropdown-menu.d.ts +61 -0
- package/dist/ui/dropdown-menu.js +347 -0
- package/dist/ui/empty.d.ts +14 -0
- package/dist/ui/empty.js +33 -0
- package/dist/ui/field.d.ts +28 -0
- package/dist/ui/field.js +68 -0
- package/dist/ui/hover-card.d.ts +32 -0
- package/dist/ui/hover-card.js +119 -0
- package/dist/ui/input-group.d.ts +28 -0
- package/dist/ui/input-group.js +58 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.js +7 -0
- package/dist/ui/item.d.ts +41 -0
- package/dist/ui/item.js +69 -0
- package/dist/ui/kbd.d.ts +4 -0
- package/dist/ui/kbd.js +10 -0
- package/dist/ui/label.d.ts +3 -0
- package/dist/ui/label.js +8 -0
- package/dist/ui/menubar.d.ts +59 -0
- package/dist/ui/menubar.js +397 -0
- package/dist/ui/navigation-menu.d.ts +24 -0
- package/dist/ui/navigation-menu.js +203 -0
- package/dist/ui/pagination.d.ts +17 -0
- package/dist/ui/pagination.js +36 -0
- package/dist/ui/popover.d.ts +25 -0
- package/dist/ui/popover.js +126 -0
- package/dist/ui/portal.d.ts +7 -0
- package/dist/ui/portal.js +12 -0
- package/dist/ui/progress.d.ts +15 -0
- package/dist/ui/progress.js +36 -0
- package/dist/ui/radio-group.d.ts +17 -0
- package/dist/ui/radio-group.js +66 -0
- package/dist/ui/scroll-area.d.ts +9 -0
- package/dist/ui/scroll-area.js +72 -0
- package/dist/ui/select.d.ts +37 -0
- package/dist/ui/select.js +226 -0
- package/dist/ui/separator.d.ts +14 -0
- package/dist/ui/separator.js +13 -0
- package/dist/ui/sheet.d.ts +31 -0
- package/dist/ui/sheet.js +121 -0
- package/dist/ui/sidebar.d.ts +86 -0
- package/dist/ui/sidebar.js +211 -0
- package/dist/ui/skeleton.d.ts +3 -0
- package/dist/ui/skeleton.js +7 -0
- package/dist/ui/slider.d.ts +18 -0
- package/dist/ui/slider.js +94 -0
- package/dist/ui/slot.d.ts +27 -0
- package/dist/ui/slot.js +62 -0
- package/dist/ui/spinner.d.ts +3 -0
- package/dist/ui/spinner.js +8 -0
- package/dist/ui/switch.d.ts +17 -0
- package/dist/ui/switch.js +23 -0
- package/dist/ui/table.d.ts +10 -0
- package/dist/ui/table.js +29 -0
- package/dist/ui/tabs.d.ts +29 -0
- package/dist/ui/tabs.js +96 -0
- package/dist/ui/textarea.d.ts +3 -0
- package/dist/ui/textarea.js +7 -0
- package/dist/ui/toggle-group.d.ts +23 -0
- package/dist/ui/toggle-group.js +52 -0
- package/dist/ui/toggle.d.ts +25 -0
- package/dist/ui/toggle.js +41 -0
- package/dist/ui/tooltip.d.ts +25 -0
- package/dist/ui/tooltip.js +90 -0
- package/dist/ui/use-controllable-state.d.ts +10 -0
- package/dist/ui/use-controllable-state.js +17 -0
- package/dist/ui/use-dismiss.d.ts +10 -0
- package/dist/ui/use-dismiss.js +51 -0
- package/dist/ui/use-floating.d.ts +42 -0
- package/dist/ui/use-floating.js +136 -0
- package/dist/ui/use-presence.d.ts +8 -0
- package/dist/ui/use-presence.js +48 -0
- package/dist/ui/use-typeahead.d.ts +19 -0
- package/dist/ui/use-typeahead.js +64 -0
- package/package.json +6 -1
- package/dist/shadcn/lib/base-ui/LICENSE +0 -21
- package/dist/shadcn/lib/base-ui/_chunk-000kmre8.js +0 -150
- package/dist/shadcn/lib/base-ui/_chunk-1vw45v38.js +0 -64
- package/dist/shadcn/lib/base-ui/_chunk-23rx7haq.js +0 -81
- package/dist/shadcn/lib/base-ui/_chunk-242gh8ph.js +0 -230
- package/dist/shadcn/lib/base-ui/_chunk-26cc610z.js +0 -6
- package/dist/shadcn/lib/base-ui/_chunk-2z044bba.js +0 -5890
- package/dist/shadcn/lib/base-ui/_chunk-3cpd1vjz.js +0 -13
- package/dist/shadcn/lib/base-ui/_chunk-3enq1vat.js +0 -82
- package/dist/shadcn/lib/base-ui/_chunk-3xpke33f.js +0 -23
- package/dist/shadcn/lib/base-ui/_chunk-451nqgsa.js +0 -6
- package/dist/shadcn/lib/base-ui/_chunk-4maf6pas.js +0 -11
- package/dist/shadcn/lib/base-ui/_chunk-4zhfq4d3.js +0 -16
- package/dist/shadcn/lib/base-ui/_chunk-5gaqyne5.js +0 -21
- package/dist/shadcn/lib/base-ui/_chunk-5tt5hk59.js +0 -4
- package/dist/shadcn/lib/base-ui/_chunk-5tze5c8q.js +0 -13
- package/dist/shadcn/lib/base-ui/_chunk-5xmdvndx.js +0 -225
- package/dist/shadcn/lib/base-ui/_chunk-6adaer33.js +0 -435
- package/dist/shadcn/lib/base-ui/_chunk-6ejf1z1r.js +0 -9
- package/dist/shadcn/lib/base-ui/_chunk-6kqramh9.js +0 -27
- package/dist/shadcn/lib/base-ui/_chunk-7b9dc493.js +0 -14
- package/dist/shadcn/lib/base-ui/_chunk-8a9vv8am.js +0 -89
- package/dist/shadcn/lib/base-ui/_chunk-8ctgmf06.js +0 -129
- package/dist/shadcn/lib/base-ui/_chunk-8ganmsbg.js +0 -54
- package/dist/shadcn/lib/base-ui/_chunk-8xaqxgck.js +0 -15
- package/dist/shadcn/lib/base-ui/_chunk-95tf2rxq.js +0 -62
- package/dist/shadcn/lib/base-ui/_chunk-9x63vfqj.js +0 -70
- package/dist/shadcn/lib/base-ui/_chunk-ase0ydtt.js +0 -9
- package/dist/shadcn/lib/base-ui/_chunk-b0nc8wq7.js +0 -34
- package/dist/shadcn/lib/base-ui/_chunk-b5ya6xds.js +0 -10
- package/dist/shadcn/lib/base-ui/_chunk-b6dkjkbw.js +0 -43
- package/dist/shadcn/lib/base-ui/_chunk-c2bz952e.js +0 -19
- package/dist/shadcn/lib/base-ui/_chunk-cdgfsr3q.js +0 -15
- package/dist/shadcn/lib/base-ui/_chunk-cdj8cpx5.js +0 -347
- package/dist/shadcn/lib/base-ui/_chunk-cgptgywc.js +0 -104
- package/dist/shadcn/lib/base-ui/_chunk-cwvtvwc7.js +0 -35
- package/dist/shadcn/lib/base-ui/_chunk-dan0mva4.js +0 -23
- package/dist/shadcn/lib/base-ui/_chunk-dzvjwv25.js +0 -0
- package/dist/shadcn/lib/base-ui/_chunk-e13rsb6b.js +0 -184
- package/dist/shadcn/lib/base-ui/_chunk-e56mpvk1.js +0 -118
- package/dist/shadcn/lib/base-ui/_chunk-e6k2ecs7.js +0 -48
- package/dist/shadcn/lib/base-ui/_chunk-ebyxgtb4.js +0 -29
- package/dist/shadcn/lib/base-ui/_chunk-f2wttwrf.js +0 -39
- package/dist/shadcn/lib/base-ui/_chunk-fdzctasg.js +0 -38
- package/dist/shadcn/lib/base-ui/_chunk-ffpjy0ta.js +0 -39
- package/dist/shadcn/lib/base-ui/_chunk-fqry7pew.js +0 -603
- package/dist/shadcn/lib/base-ui/_chunk-ghn1tzt8.js +0 -84
- package/dist/shadcn/lib/base-ui/_chunk-gy0bpkmx.js +0 -12
- package/dist/shadcn/lib/base-ui/_chunk-hm5h9vsk.js +0 -16
- package/dist/shadcn/lib/base-ui/_chunk-htdj1jps.js +0 -833
- package/dist/shadcn/lib/base-ui/_chunk-j29xjete.js +0 -146
- package/dist/shadcn/lib/base-ui/_chunk-j3qkyd10.js +0 -50
- package/dist/shadcn/lib/base-ui/_chunk-ktvsd5k6.js +0 -2386
- package/dist/shadcn/lib/base-ui/_chunk-kw8nnq00.js +0 -546
- package/dist/shadcn/lib/base-ui/_chunk-m307wpdj.js +0 -18
- package/dist/shadcn/lib/base-ui/_chunk-mnd0j7v9.js +0 -15
- package/dist/shadcn/lib/base-ui/_chunk-n11h505c.js +0 -32
- package/dist/shadcn/lib/base-ui/_chunk-p0e2qacz.js +0 -26
- package/dist/shadcn/lib/base-ui/_chunk-pv7b791x.js +0 -106
- package/dist/shadcn/lib/base-ui/_chunk-q5cg71p7.js +0 -83
- package/dist/shadcn/lib/base-ui/_chunk-qbezxj1g.js +0 -385
- package/dist/shadcn/lib/base-ui/_chunk-qmsqz6nt.js +0 -15
- package/dist/shadcn/lib/base-ui/_chunk-rrh8rt4v.js +0 -65
- package/dist/shadcn/lib/base-ui/_chunk-s5pwkz8v.js +0 -25
- package/dist/shadcn/lib/base-ui/_chunk-svxv97ph.js +0 -218
- package/dist/shadcn/lib/base-ui/_chunk-t7ppm3t0.js +0 -68
- package/dist/shadcn/lib/base-ui/_chunk-vg16xxmb.js +0 -52
- package/dist/shadcn/lib/base-ui/_chunk-vgkfmtbe.js +0 -21
- package/dist/shadcn/lib/base-ui/_chunk-watha94s.js +0 -14
- package/dist/shadcn/lib/base-ui/_chunk-wdqynnjf.js +0 -35
- package/dist/shadcn/lib/base-ui/_chunk-wx2nxg0p.js +0 -15
- package/dist/shadcn/lib/base-ui/_chunk-x11e1k9r.js +0 -0
- package/dist/shadcn/lib/base-ui/_chunk-x8xehj6d.js +0 -302
- package/dist/shadcn/lib/base-ui/_chunk-xcqbtm2f.js +0 -28
- package/dist/shadcn/lib/base-ui/_chunk-ytnp24gq.js +0 -328
- package/dist/shadcn/lib/base-ui/_chunk-yzr7eg4b.js +0 -6
- package/dist/shadcn/lib/base-ui/_chunk-zk4mtm9m.js +0 -9
- package/dist/shadcn/lib/base-ui/accordion.d.ts +0 -447
- package/dist/shadcn/lib/base-ui/accordion.js +0 -630
- package/dist/shadcn/lib/base-ui/alert-dialog.d.ts +0 -1467
- package/dist/shadcn/lib/base-ui/alert-dialog.js +0 -98
- package/dist/shadcn/lib/base-ui/avatar.d.ts +0 -123
- package/dist/shadcn/lib/base-ui/avatar.js +0 -235
- package/dist/shadcn/lib/base-ui/button.d.ts +0 -84
- package/dist/shadcn/lib/base-ui/button.js +0 -51
- package/dist/shadcn/lib/base-ui/checkbox.d.ts +0 -341
- package/dist/shadcn/lib/base-ui/checkbox.js +0 -501
- package/dist/shadcn/lib/base-ui/collapsible.d.ts +0 -291
- package/dist/shadcn/lib/base-ui/collapsible.js +0 -264
- package/dist/shadcn/lib/base-ui/context-menu.d.ts +0 -2213
- package/dist/shadcn/lib/base-ui/context-menu.js +0 -323
- package/dist/shadcn/lib/base-ui/dialog.d.ts +0 -1448
- package/dist/shadcn/lib/base-ui/dialog.js +0 -79
- package/dist/shadcn/lib/base-ui/input.d.ts +0 -506
- package/dist/shadcn/lib/base-ui/input.js +0 -1027
- package/dist/shadcn/lib/base-ui/menu.d.ts +0 -2275
- package/dist/shadcn/lib/base-ui/menu.js +0 -60
- package/dist/shadcn/lib/base-ui/menubar.d.ts +0 -48
- package/dist/shadcn/lib/base-ui/menubar.js +0 -156
- package/dist/shadcn/lib/base-ui/merge-props.d.ts +0 -70
- package/dist/shadcn/lib/base-ui/merge-props.js +0 -15
- package/dist/shadcn/lib/base-ui/navigation-menu.d.ts +0 -825
- package/dist/shadcn/lib/base-ui/navigation-menu.js +0 -1849
- package/dist/shadcn/lib/base-ui/popover.d.ts +0 -1743
- package/dist/shadcn/lib/base-ui/popover.js +0 -1089
- package/dist/shadcn/lib/base-ui/preview-card.d.ts +0 -1608
- package/dist/shadcn/lib/base-ui/preview-card.js +0 -919
- package/dist/shadcn/lib/base-ui/progress.d.ts +0 -172
- package/dist/shadcn/lib/base-ui/progress.js +0 -272
- package/dist/shadcn/lib/base-ui/radio-group.d.ts +0 -260
- package/dist/shadcn/lib/base-ui/radio-group.js +0 -239
- package/dist/shadcn/lib/base-ui/radio.d.ts +0 -176
- package/dist/shadcn/lib/base-ui/radio.js +0 -387
- package/dist/shadcn/lib/base-ui/scroll-area.d.ts +0 -241
- package/dist/shadcn/lib/base-ui/scroll-area.js +0 -1055
- package/dist/shadcn/lib/base-ui/select.d.ts +0 -1266
- package/dist/shadcn/lib/base-ui/select.js +0 -2450
- package/dist/shadcn/lib/base-ui/separator.d.ts +0 -76
- package/dist/shadcn/lib/base-ui/separator.js +0 -11
- package/dist/shadcn/lib/base-ui/slider.d.ts +0 -551
- package/dist/shadcn/lib/base-ui/slider.js +0 -1583
- package/dist/shadcn/lib/base-ui/switch.d.ts +0 -313
- package/dist/shadcn/lib/base-ui/switch.js +0 -326
- package/dist/shadcn/lib/base-ui/tabs.d.ts +0 -406
- package/dist/shadcn/lib/base-ui/tabs.js +0 -915
- package/dist/shadcn/lib/base-ui/toggle-group.d.ts +0 -235
- package/dist/shadcn/lib/base-ui/toggle-group.js +0 -145
- package/dist/shadcn/lib/base-ui/toggle.d.ts +0 -224
- package/dist/shadcn/lib/base-ui/toggle.js +0 -127
- package/dist/shadcn/lib/base-ui/tooltip.d.ts +0 -1656
- package/dist/shadcn/lib/base-ui/tooltip.js +0 -920
- package/dist/shadcn/lib/base-ui/use-render.d.ts +0 -87
- package/dist/shadcn/lib/base-ui/use-render.js +0 -15
package/dist/ui/tabs.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva } from "../shadcn/lib/cva.js";
|
|
5
|
+
import { cn } from "../shadcn/lib/utils.js";
|
|
6
|
+
import { mergeRefs } from "./slot.js";
|
|
7
|
+
import { useControllableState } from "./use-controllable-state.js";
|
|
8
|
+
const TabsContext = React.createContext(null);
|
|
9
|
+
function useTabs() {
|
|
10
|
+
const ctx = React.useContext(TabsContext);
|
|
11
|
+
if (!ctx)
|
|
12
|
+
throw new Error("Tabs parts must be used within <Tabs>");
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
// Sanitize a tab value for use in an element id — spaces/special chars would
|
|
16
|
+
// break the aria-controls / aria-labelledby IDREF association.
|
|
17
|
+
const idify = (v) => v.replace(/[^\w-]/g, "_");
|
|
18
|
+
/**
|
|
19
|
+
* Self-contained tabs. Controlled/uncontrolled active value; the list provides
|
|
20
|
+
* roving arrow-key navigation with automatic activation (WAI-ARIA tabs pattern).
|
|
21
|
+
*/
|
|
22
|
+
function Tabs({ className, value, defaultValue, onValueChange, orientation = "horizontal", ...props }) {
|
|
23
|
+
const [active, setActive] = useControllableState({
|
|
24
|
+
value,
|
|
25
|
+
defaultValue,
|
|
26
|
+
onChange: onValueChange,
|
|
27
|
+
});
|
|
28
|
+
const idBase = React.useId();
|
|
29
|
+
return (_jsx(TabsContext.Provider, { value: { value: active, setValue: setActive, orientation, idBase }, children: _jsx("div", { "data-slot": "tabs", "data-orientation": orientation, className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className), ...props }) }));
|
|
30
|
+
}
|
|
31
|
+
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", {
|
|
32
|
+
variants: {
|
|
33
|
+
variant: {
|
|
34
|
+
default: "bg-muted",
|
|
35
|
+
line: "gap-1 bg-transparent",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
function TabsList({ className, variant = "default", onKeyDown, ...props }) {
|
|
43
|
+
const { orientation } = useTabs();
|
|
44
|
+
return (_jsx("div", { role: "tablist", "aria-orientation": orientation, "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), onKeyDown: (e) => {
|
|
45
|
+
onKeyDown?.(e);
|
|
46
|
+
if (e.defaultPrevented)
|
|
47
|
+
return;
|
|
48
|
+
const tabs = Array.from(e.currentTarget.querySelectorAll('[role="tab"]:not([disabled]):not([data-disabled])'));
|
|
49
|
+
const current = tabs.indexOf(document.activeElement);
|
|
50
|
+
if (current < 0)
|
|
51
|
+
return;
|
|
52
|
+
const horizontal = orientation !== "vertical";
|
|
53
|
+
let next = -1;
|
|
54
|
+
if ((horizontal && e.key === "ArrowRight") || (!horizontal && e.key === "ArrowDown"))
|
|
55
|
+
next = (current + 1) % tabs.length;
|
|
56
|
+
else if ((horizontal && e.key === "ArrowLeft") || (!horizontal && e.key === "ArrowUp"))
|
|
57
|
+
next = (current - 1 + tabs.length) % tabs.length;
|
|
58
|
+
else if (e.key === "Home")
|
|
59
|
+
next = 0;
|
|
60
|
+
else if (e.key === "End")
|
|
61
|
+
next = tabs.length - 1;
|
|
62
|
+
if (next >= 0) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
tabs[next].focus();
|
|
65
|
+
tabs[next].click();
|
|
66
|
+
}
|
|
67
|
+
}, ...props }));
|
|
68
|
+
}
|
|
69
|
+
function TabsTrigger({ className, value, onClick, ref, ...props }) {
|
|
70
|
+
const { value: active, setValue, idBase } = useTabs();
|
|
71
|
+
const selected = active === value;
|
|
72
|
+
const btnRef = React.useRef(null);
|
|
73
|
+
// Roving tab index: the selected tab is tabbable. When nothing is selected the
|
|
74
|
+
// first enabled tab must still be reachable so the tablist stays in Tab order
|
|
75
|
+
// (WAI-ARIA: a tablist always has exactly one tab stop).
|
|
76
|
+
const [isFirstEnabled, setIsFirstEnabled] = React.useState(false);
|
|
77
|
+
React.useLayoutEffect(() => {
|
|
78
|
+
if (active !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
const el = btnRef.current;
|
|
81
|
+
const first = el?.parentElement?.querySelector('[role="tab"]:not([disabled]):not([data-disabled])');
|
|
82
|
+
setIsFirstEnabled(first === el);
|
|
83
|
+
});
|
|
84
|
+
const tabbable = selected || (active === undefined && isFirstEnabled);
|
|
85
|
+
return (_jsx("button", { ref: mergeRefs(btnRef, ref), type: "button", role: "tab", id: `${idBase}-tab-${idify(value)}`, "aria-selected": selected, "aria-controls": `${idBase}-panel-${idify(value)}`, tabIndex: tabbable ? 0 : -1, "data-slot": "tabs-trigger", "data-active": selected ? "" : undefined, className: cn("cursor-pointer relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), onClick: (e) => {
|
|
86
|
+
onClick?.(e);
|
|
87
|
+
if (!e.defaultPrevented)
|
|
88
|
+
setValue(value);
|
|
89
|
+
}, ...props }));
|
|
90
|
+
}
|
|
91
|
+
function TabsContent({ className, value, ...props }) {
|
|
92
|
+
const { value: active, idBase } = useTabs();
|
|
93
|
+
const selected = active === value;
|
|
94
|
+
return (_jsx("div", { role: "tabpanel", id: `${idBase}-panel-${idify(value)}`, "aria-labelledby": `${idBase}-tab-${idify(value)}`, hidden: !selected, tabIndex: 0, "data-slot": "tabs-content", className: cn("flex-1 text-sm outline-none", className), ...props }));
|
|
95
|
+
}
|
|
96
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../shadcn/lib/utils.js";
|
|
4
|
+
function Textarea({ className, ...props }) {
|
|
5
|
+
return (_jsx("textarea", { "data-slot": "textarea", className: cn("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
|
|
6
|
+
}
|
|
7
|
+
export { Textarea };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type VariantProps } from "../shadcn/lib/cva.js";
|
|
3
|
+
import { toggleVariants } from "./toggle.js";
|
|
4
|
+
export interface ToggleGroupProps extends Omit<React.ComponentProps<"div">, "onChange">, VariantProps<typeof toggleVariants> {
|
|
5
|
+
value?: string[];
|
|
6
|
+
defaultValue?: string[];
|
|
7
|
+
onValueChange?: (value: string[]) => void;
|
|
8
|
+
/** Allow more than one item selected at once. */
|
|
9
|
+
toggleMultiple?: boolean;
|
|
10
|
+
spacing?: number;
|
|
11
|
+
orientation?: "horizontal" | "vertical";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Self-contained toggle group: manages single- or multi-select state across its
|
|
15
|
+
* items via context. Keyboard: native button focus order + Space/Enter (roving
|
|
16
|
+
* arrow nav is intentionally omitted from this tier).
|
|
17
|
+
*/
|
|
18
|
+
declare function ToggleGroup({ className, variant, size, spacing, orientation, value, defaultValue, onValueChange, toggleMultiple, children, ...props }: ToggleGroupProps): React.JSX.Element;
|
|
19
|
+
export interface ToggleGroupItemProps extends Omit<React.ComponentProps<"button">, "value">, VariantProps<typeof toggleVariants> {
|
|
20
|
+
value: string;
|
|
21
|
+
}
|
|
22
|
+
declare function ToggleGroupItem({ className, children, variant, size, value, onClick, ...props }: ToggleGroupItemProps): React.JSX.Element;
|
|
23
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {} from "../shadcn/lib/cva.js";
|
|
5
|
+
import { cn } from "../shadcn/lib/utils.js";
|
|
6
|
+
import { toggleVariants } from "./toggle.js";
|
|
7
|
+
import { useControllableState } from "./use-controllable-state.js";
|
|
8
|
+
const ToggleGroupContext = React.createContext({
|
|
9
|
+
size: "default",
|
|
10
|
+
variant: "default",
|
|
11
|
+
spacing: 2,
|
|
12
|
+
orientation: "horizontal",
|
|
13
|
+
value: [],
|
|
14
|
+
toggle: () => { },
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Self-contained toggle group: manages single- or multi-select state across its
|
|
18
|
+
* items via context. Keyboard: native button focus order + Space/Enter (roving
|
|
19
|
+
* arrow nav is intentionally omitted from this tier).
|
|
20
|
+
*/
|
|
21
|
+
function ToggleGroup({ className, variant, size, spacing = 2, orientation = "horizontal", value, defaultValue = [], onValueChange, toggleMultiple = false, children, ...props }) {
|
|
22
|
+
const [selected, setSelected] = useControllableState({
|
|
23
|
+
value,
|
|
24
|
+
defaultValue,
|
|
25
|
+
onChange: onValueChange,
|
|
26
|
+
});
|
|
27
|
+
const toggle = React.useCallback((item) => {
|
|
28
|
+
const has = selected.includes(item);
|
|
29
|
+
const next = toggleMultiple
|
|
30
|
+
? has
|
|
31
|
+
? selected.filter((v) => v !== item)
|
|
32
|
+
: [...selected, item]
|
|
33
|
+
: has
|
|
34
|
+
? []
|
|
35
|
+
: [item];
|
|
36
|
+
setSelected(next);
|
|
37
|
+
}, [selected, toggleMultiple, setSelected]);
|
|
38
|
+
return (_jsx("div", { role: "group", "data-slot": "toggle-group", "data-variant": variant, "data-size": size, "data-spacing": spacing, "data-orientation": orientation, style: { "--gap": spacing }, className: cn("group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch", className), ...props, children: _jsx(ToggleGroupContext.Provider, { value: { variant, size, spacing, orientation, value: selected, toggle }, children: children }) }));
|
|
39
|
+
}
|
|
40
|
+
function ToggleGroupItem({ className, children, variant = "default", size = "default", value, onClick, ...props }) {
|
|
41
|
+
const context = React.useContext(ToggleGroupContext);
|
|
42
|
+
const pressed = context.value.includes(value);
|
|
43
|
+
return (_jsx("button", { type: "button", "data-slot": "toggle-group-item", "data-state": pressed ? "on" : "off", "aria-pressed": pressed, "data-variant": context.variant || variant, "data-size": context.size || size, "data-spacing": context.spacing, className: cn("shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md data-[state=on]:bg-muted group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t", toggleVariants({
|
|
44
|
+
variant: context.variant || variant,
|
|
45
|
+
size: context.size || size,
|
|
46
|
+
}), className), onClick: (e) => {
|
|
47
|
+
onClick?.(e);
|
|
48
|
+
if (!e.defaultPrevented)
|
|
49
|
+
context.toggle(value);
|
|
50
|
+
}, ...props, children: children }));
|
|
51
|
+
}
|
|
52
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type VariantProps } from "../shadcn/lib/cva.js";
|
|
3
|
+
declare const toggleVariants: (props?: import("../shadcn/lib/cva.js").CvaProps<{
|
|
4
|
+
variant: {
|
|
5
|
+
default: string;
|
|
6
|
+
outline: string;
|
|
7
|
+
};
|
|
8
|
+
size: {
|
|
9
|
+
default: string;
|
|
10
|
+
sm: string;
|
|
11
|
+
lg: string;
|
|
12
|
+
};
|
|
13
|
+
}> | undefined) => string;
|
|
14
|
+
export interface ToggleProps extends Omit<React.ComponentProps<"button">, "onChange">, VariantProps<typeof toggleVariants> {
|
|
15
|
+
pressed?: boolean;
|
|
16
|
+
defaultPressed?: boolean;
|
|
17
|
+
onPressedChange?: (pressed: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Self-contained two-state toggle button. Controlled via `pressed`/
|
|
21
|
+
* `onPressedChange` or uncontrolled via `defaultPressed`. Emits `aria-pressed`
|
|
22
|
+
* and `data-state` so the shared toggle classes apply.
|
|
23
|
+
*/
|
|
24
|
+
declare function Toggle({ className, variant, size, pressed, defaultPressed, onPressedChange, onClick, ...props }: ToggleProps): React.JSX.Element;
|
|
25
|
+
export { Toggle, toggleVariants };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva } from "../shadcn/lib/cva.js";
|
|
5
|
+
import { cn } from "../shadcn/lib/utils.js";
|
|
6
|
+
import { useControllableState } from "./use-controllable-state.js";
|
|
7
|
+
const toggleVariants = cva("cursor-pointer group/toggle inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-transparent",
|
|
11
|
+
outline: "border border-input bg-transparent shadow-xs hover:bg-muted",
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
default: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
15
|
+
sm: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
|
16
|
+
lg: "h-10 min-w-10 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
size: "default",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Self-contained two-state toggle button. Controlled via `pressed`/
|
|
26
|
+
* `onPressedChange` or uncontrolled via `defaultPressed`. Emits `aria-pressed`
|
|
27
|
+
* and `data-state` so the shared toggle classes apply.
|
|
28
|
+
*/
|
|
29
|
+
function Toggle({ className, variant = "default", size = "default", pressed, defaultPressed = false, onPressedChange, onClick, ...props }) {
|
|
30
|
+
const [on, setOn] = useControllableState({
|
|
31
|
+
value: pressed,
|
|
32
|
+
defaultValue: defaultPressed,
|
|
33
|
+
onChange: onPressedChange,
|
|
34
|
+
});
|
|
35
|
+
return (_jsx("button", { type: "button", "data-slot": "toggle", "data-state": on ? "on" : "off", "aria-pressed": on, className: cn(toggleVariants({ variant, size, className })), onClick: (e) => {
|
|
36
|
+
onClick?.(e);
|
|
37
|
+
if (!e.defaultPrevented)
|
|
38
|
+
setOn(!on);
|
|
39
|
+
}, ...props }));
|
|
40
|
+
}
|
|
41
|
+
export { Toggle, toggleVariants };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type Side, type Align } from "./use-floating.js";
|
|
3
|
+
declare function TooltipProvider({ delay, children, }: {
|
|
4
|
+
delay?: number;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
}): React.JSX.Element;
|
|
7
|
+
export interface TooltipProps {
|
|
8
|
+
delay?: number;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
declare function Tooltip({ delay, defaultOpen, children }: TooltipProps): React.JSX.Element;
|
|
13
|
+
declare function TooltipTrigger({ asChild, render, nativeButton: _nativeButton, children, onMouseEnter, onMouseLeave, onFocus, onBlur, ...props }: React.ComponentProps<"button"> & {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
render?: React.ReactElement;
|
|
16
|
+
nativeButton?: boolean;
|
|
17
|
+
}): React.JSX.Element;
|
|
18
|
+
export interface TooltipContentProps extends React.ComponentProps<"div"> {
|
|
19
|
+
side?: Side;
|
|
20
|
+
align?: Align;
|
|
21
|
+
sideOffset?: number;
|
|
22
|
+
alignOffset?: number;
|
|
23
|
+
}
|
|
24
|
+
declare function TooltipContent({ className, style, ref, side, sideOffset, align, alignOffset, children, ...props }: TooltipContentProps): React.JSX.Element | null;
|
|
25
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../shadcn/lib/utils.js";
|
|
5
|
+
import { Slot, mergeRefs, resolveRender } from "./slot.js";
|
|
6
|
+
import { Portal } from "./portal.js";
|
|
7
|
+
import { useFloating } from "./use-floating.js";
|
|
8
|
+
import { usePresence } from "./use-presence.js";
|
|
9
|
+
const TooltipContext = React.createContext(null);
|
|
10
|
+
function useTooltip() {
|
|
11
|
+
const ctx = React.useContext(TooltipContext);
|
|
12
|
+
if (!ctx)
|
|
13
|
+
throw new Error("Tooltip parts must be used within <Tooltip>");
|
|
14
|
+
return ctx;
|
|
15
|
+
}
|
|
16
|
+
const DelayContext = React.createContext(0);
|
|
17
|
+
function TooltipProvider({ delay = 0, children, }) {
|
|
18
|
+
return _jsx(DelayContext.Provider, { value: delay, children: children });
|
|
19
|
+
}
|
|
20
|
+
function Tooltip({ delay, defaultOpen = false, children }) {
|
|
21
|
+
const providerDelay = React.useContext(DelayContext);
|
|
22
|
+
const openDelay = delay ?? providerDelay;
|
|
23
|
+
const [open, setOpen] = React.useState(defaultOpen);
|
|
24
|
+
const triggerRef = React.useRef(null);
|
|
25
|
+
const contentId = React.useId();
|
|
26
|
+
const timer = React.useRef(undefined);
|
|
27
|
+
const show = React.useCallback(() => {
|
|
28
|
+
window.clearTimeout(timer.current);
|
|
29
|
+
if (openDelay > 0) {
|
|
30
|
+
timer.current = window.setTimeout(() => setOpen(true), openDelay);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
setOpen(true);
|
|
34
|
+
}
|
|
35
|
+
}, [openDelay]);
|
|
36
|
+
const hide = React.useCallback(() => {
|
|
37
|
+
window.clearTimeout(timer.current);
|
|
38
|
+
setOpen(false);
|
|
39
|
+
}, []);
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
if (!open)
|
|
42
|
+
return;
|
|
43
|
+
const onKey = (e) => {
|
|
44
|
+
if (e.key === "Escape")
|
|
45
|
+
hide();
|
|
46
|
+
};
|
|
47
|
+
document.addEventListener("keydown", onKey);
|
|
48
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
49
|
+
}, [open, hide]);
|
|
50
|
+
return (_jsx(TooltipContext.Provider, { value: { open, show, hide, triggerRef, contentId }, children: children }));
|
|
51
|
+
}
|
|
52
|
+
function TooltipTrigger({ asChild = false, render, nativeButton: _nativeButton, children, onMouseEnter, onMouseLeave, onFocus, onBlur, ...props }) {
|
|
53
|
+
const { open, show, hide, triggerRef, contentId } = useTooltip();
|
|
54
|
+
const { useSlot, slotChild } = resolveRender(asChild, render, children);
|
|
55
|
+
const Comp = useSlot ? Slot : "button";
|
|
56
|
+
return (_jsx(Comp, { ref: triggerRef, type: useSlot ? undefined : "button", "data-slot": "tooltip-trigger", "aria-describedby": open ? contentId : undefined, "data-state": open ? "open" : "closed", onMouseEnter: (e) => {
|
|
57
|
+
onMouseEnter?.(e);
|
|
58
|
+
show();
|
|
59
|
+
}, onMouseLeave: (e) => {
|
|
60
|
+
onMouseLeave?.(e);
|
|
61
|
+
hide();
|
|
62
|
+
}, onFocus: (e) => {
|
|
63
|
+
onFocus?.(e);
|
|
64
|
+
show();
|
|
65
|
+
}, onBlur: (e) => {
|
|
66
|
+
onBlur?.(e);
|
|
67
|
+
hide();
|
|
68
|
+
}, ...props, children: slotChild }));
|
|
69
|
+
}
|
|
70
|
+
function TooltipContent({ className, style, ref, side = "top", sideOffset = 4, align = "center", alignOffset = 0, children, ...props }) {
|
|
71
|
+
const { open, contentId, triggerRef } = useTooltip();
|
|
72
|
+
const [mounted, presenceRef] = usePresence(open);
|
|
73
|
+
const { floatingRef, pos } = useFloating(triggerRef, mounted, {
|
|
74
|
+
side,
|
|
75
|
+
align,
|
|
76
|
+
sideOffset,
|
|
77
|
+
alignOffset,
|
|
78
|
+
});
|
|
79
|
+
if (!mounted)
|
|
80
|
+
return null;
|
|
81
|
+
return (_jsx(Portal, { children: _jsx("div", { ref: mergeRefs(floatingRef, presenceRef, ref), id: contentId, role: "tooltip", "data-slot": "tooltip-content", "data-state": open ? "open" : "closed", "data-open": open ? "" : undefined, "data-closed": open ? undefined : "", "data-side": pos?.side ?? side, style: {
|
|
82
|
+
...style,
|
|
83
|
+
position: "fixed",
|
|
84
|
+
left: pos?.x ?? 0,
|
|
85
|
+
top: pos?.y ?? 0,
|
|
86
|
+
visibility: pos ? "visible" : "hidden",
|
|
87
|
+
["--transform-origin"]: pos?.transformOrigin ?? "center",
|
|
88
|
+
}, className: cn("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className), ...props, children: children }) }));
|
|
89
|
+
}
|
|
90
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlled/uncontrolled state in one hook. If `value` is provided the state is
|
|
3
|
+
* controlled (the hook just forwards it and calls `onChange`); otherwise it owns
|
|
4
|
+
* the state internally, seeded by `defaultValue`. Self-contained, no deps.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useControllableState<T>({ value, defaultValue, onChange, }: {
|
|
7
|
+
value?: T;
|
|
8
|
+
defaultValue: T;
|
|
9
|
+
onChange?: (value: T) => void;
|
|
10
|
+
}): [T, (next: T) => void];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Controlled/uncontrolled state in one hook. If `value` is provided the state is
|
|
4
|
+
* controlled (the hook just forwards it and calls `onChange`); otherwise it owns
|
|
5
|
+
* the state internally, seeded by `defaultValue`. Self-contained, no deps.
|
|
6
|
+
*/
|
|
7
|
+
export function useControllableState({ value, defaultValue, onChange, }) {
|
|
8
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
|
|
9
|
+
const isControlled = value !== undefined;
|
|
10
|
+
const state = isControlled ? value : uncontrolled;
|
|
11
|
+
const setState = React.useCallback((next) => {
|
|
12
|
+
if (!isControlled)
|
|
13
|
+
setUncontrolled(next);
|
|
14
|
+
onChange?.(next);
|
|
15
|
+
}, [isControlled, onChange]);
|
|
16
|
+
return [state, setState];
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Close an open layer on Escape or on a pointer press outside all of the given
|
|
4
|
+
* elements (e.g. the popup and its trigger). The listeners are only attached
|
|
5
|
+
* while `open`, and because the effect runs after the click that opened the
|
|
6
|
+
* layer, that same click never re-closes it. Escape is stacked: only the
|
|
7
|
+
* top layer closes, and the keydown is prevented/stopped so a native `<dialog>`
|
|
8
|
+
* ancestor's Escape-to-close doesn't also fire.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useDismiss(open: boolean, onDismiss: () => void, refs: Array<React.RefObject<HTMLElement | null>>): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
// Shared stack of open dismissable layers (most-recently-opened last). Escape
|
|
4
|
+
// only closes the topmost layer, so a popover opened inside a dialog doesn't
|
|
5
|
+
// close both at once.
|
|
6
|
+
const escapeStack = [];
|
|
7
|
+
/**
|
|
8
|
+
* Close an open layer on Escape or on a pointer press outside all of the given
|
|
9
|
+
* elements (e.g. the popup and its trigger). The listeners are only attached
|
|
10
|
+
* while `open`, and because the effect runs after the click that opened the
|
|
11
|
+
* layer, that same click never re-closes it. Escape is stacked: only the
|
|
12
|
+
* top layer closes, and the keydown is prevented/stopped so a native `<dialog>`
|
|
13
|
+
* ancestor's Escape-to-close doesn't also fire.
|
|
14
|
+
*/
|
|
15
|
+
export function useDismiss(open, onDismiss, refs) {
|
|
16
|
+
const onDismissRef = React.useRef(onDismiss);
|
|
17
|
+
onDismissRef.current = onDismiss;
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
if (!open)
|
|
20
|
+
return;
|
|
21
|
+
const token = () => onDismissRef.current();
|
|
22
|
+
escapeStack.push(token);
|
|
23
|
+
const onKeyDown = (e) => {
|
|
24
|
+
if (e.key !== "Escape")
|
|
25
|
+
return;
|
|
26
|
+
if (escapeStack[escapeStack.length - 1] !== token)
|
|
27
|
+
return;
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
onDismissRef.current();
|
|
31
|
+
};
|
|
32
|
+
const onPointerDown = (e) => {
|
|
33
|
+
const target = e.target;
|
|
34
|
+
if (!target)
|
|
35
|
+
return;
|
|
36
|
+
if (refs.some((r) => r.current && r.current.contains(target)))
|
|
37
|
+
return;
|
|
38
|
+
onDismissRef.current();
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
41
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
42
|
+
return () => {
|
|
43
|
+
const i = escapeStack.indexOf(token);
|
|
44
|
+
if (i !== -1)
|
|
45
|
+
escapeStack.splice(i, 1);
|
|
46
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
47
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
48
|
+
};
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
|
+
}, [open]);
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type Side = "top" | "bottom" | "left" | "right";
|
|
3
|
+
export type Align = "start" | "center" | "end";
|
|
4
|
+
export interface FloatingOptions {
|
|
5
|
+
side?: Side;
|
|
6
|
+
align?: Align;
|
|
7
|
+
sideOffset?: number;
|
|
8
|
+
alignOffset?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FloatingResult {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
side: Side;
|
|
14
|
+
align: Align;
|
|
15
|
+
transformOrigin: string;
|
|
16
|
+
/** Usable height (px) between the anchor and the viewport edge on the chosen
|
|
17
|
+
* side, less offsets/padding. Exposed as `--available-height` so scrollable
|
|
18
|
+
* popups (menus, selects) can clamp with `max-h-(--available-height)`. */
|
|
19
|
+
availableHeight: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pure positioning math: place a floating box relative to an anchor on the given
|
|
23
|
+
* side/alignment, flipping to the opposite side when it would overflow and
|
|
24
|
+
* shifting along the cross axis to stay within the viewport. A focused subset of
|
|
25
|
+
* what a full floating-ui pipeline does — enough for popovers, menus, tooltips.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computePosition(anchor: DOMRect, floating: {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}, opts: Required<FloatingOptions>, viewport: {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}): FloatingResult;
|
|
34
|
+
/**
|
|
35
|
+
* Position a floating element against an anchor, recomputing on scroll, resize,
|
|
36
|
+
* and content resize while `open`. Attach `floatingRef` to the popup.
|
|
37
|
+
*/
|
|
38
|
+
export declare function useFloating(anchorRef: React.RefObject<HTMLElement | null>, open: boolean, options?: FloatingOptions): {
|
|
39
|
+
floatingRef: React.RefObject<HTMLDivElement | null>;
|
|
40
|
+
pos: FloatingResult | null;
|
|
41
|
+
update: () => void;
|
|
42
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const PAD = 8;
|
|
4
|
+
const OPPOSITE = {
|
|
5
|
+
top: "bottom",
|
|
6
|
+
bottom: "top",
|
|
7
|
+
left: "right",
|
|
8
|
+
right: "left",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Pure positioning math: place a floating box relative to an anchor on the given
|
|
12
|
+
* side/alignment, flipping to the opposite side when it would overflow and
|
|
13
|
+
* shifting along the cross axis to stay within the viewport. A focused subset of
|
|
14
|
+
* what a full floating-ui pipeline does — enough for popovers, menus, tooltips.
|
|
15
|
+
*/
|
|
16
|
+
export function computePosition(anchor, floating, opts, viewport) {
|
|
17
|
+
let { side } = opts;
|
|
18
|
+
const { align, sideOffset, alignOffset } = opts;
|
|
19
|
+
const space = {
|
|
20
|
+
top: anchor.top,
|
|
21
|
+
bottom: viewport.height - anchor.bottom,
|
|
22
|
+
left: anchor.left,
|
|
23
|
+
right: viewport.width - anchor.right,
|
|
24
|
+
};
|
|
25
|
+
const isVertical = side === "top" || side === "bottom";
|
|
26
|
+
const need = (isVertical ? floating.height : floating.width) + sideOffset;
|
|
27
|
+
if (space[side] < need && space[OPPOSITE[side]] > space[side]) {
|
|
28
|
+
side = OPPOSITE[side];
|
|
29
|
+
}
|
|
30
|
+
const vertical = side === "top" || side === "bottom";
|
|
31
|
+
let x = 0;
|
|
32
|
+
let y = 0;
|
|
33
|
+
if (side === "bottom")
|
|
34
|
+
y = anchor.bottom + sideOffset;
|
|
35
|
+
else if (side === "top")
|
|
36
|
+
y = anchor.top - floating.height - sideOffset;
|
|
37
|
+
else if (side === "right")
|
|
38
|
+
x = anchor.right + sideOffset;
|
|
39
|
+
else
|
|
40
|
+
x = anchor.left - floating.width - sideOffset;
|
|
41
|
+
if (vertical) {
|
|
42
|
+
if (align === "start")
|
|
43
|
+
x = anchor.left + alignOffset;
|
|
44
|
+
else if (align === "end")
|
|
45
|
+
x = anchor.right - floating.width - alignOffset;
|
|
46
|
+
else
|
|
47
|
+
x = anchor.left + anchor.width / 2 - floating.width / 2 + alignOffset;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (align === "start")
|
|
51
|
+
y = anchor.top + alignOffset;
|
|
52
|
+
else if (align === "end")
|
|
53
|
+
y = anchor.bottom - floating.height - alignOffset;
|
|
54
|
+
else
|
|
55
|
+
y = anchor.top + anchor.height / 2 - floating.height / 2 + alignOffset;
|
|
56
|
+
}
|
|
57
|
+
x = Math.max(PAD, Math.min(x, viewport.width - floating.width - PAD));
|
|
58
|
+
y = Math.max(PAD, Math.min(y, viewport.height - floating.height - PAD));
|
|
59
|
+
const originX = vertical
|
|
60
|
+
? align === "start"
|
|
61
|
+
? "left"
|
|
62
|
+
: align === "end"
|
|
63
|
+
? "right"
|
|
64
|
+
: "center"
|
|
65
|
+
: side === "right"
|
|
66
|
+
? "left"
|
|
67
|
+
: "right";
|
|
68
|
+
const originY = vertical
|
|
69
|
+
? side === "bottom"
|
|
70
|
+
? "top"
|
|
71
|
+
: "bottom"
|
|
72
|
+
: align === "start"
|
|
73
|
+
? "top"
|
|
74
|
+
: align === "end"
|
|
75
|
+
? "bottom"
|
|
76
|
+
: "center";
|
|
77
|
+
// Space left for the popup on its final side, so it can clamp + scroll
|
|
78
|
+
// instead of overflowing the viewport. For left/right placements the popup
|
|
79
|
+
// spans the cross axis, so the usable height is the full viewport less padding.
|
|
80
|
+
const availableHeight = Math.max(0, (vertical ? space[side] - sideOffset : viewport.height) - PAD);
|
|
81
|
+
return { x, y, side, align, transformOrigin: `${originX} ${originY}`, availableHeight };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Position a floating element against an anchor, recomputing on scroll, resize,
|
|
85
|
+
* and content resize while `open`. Attach `floatingRef` to the popup.
|
|
86
|
+
*/
|
|
87
|
+
export function useFloating(anchorRef, open, options = {}) {
|
|
88
|
+
const floatingRef = React.useRef(null);
|
|
89
|
+
const [pos, setPos] = React.useState(null);
|
|
90
|
+
const side = options.side ?? "bottom";
|
|
91
|
+
const align = options.align ?? "center";
|
|
92
|
+
const sideOffset = options.sideOffset ?? 0;
|
|
93
|
+
const alignOffset = options.alignOffset ?? 0;
|
|
94
|
+
const update = React.useCallback(() => {
|
|
95
|
+
const a = anchorRef.current;
|
|
96
|
+
const f = floatingRef.current;
|
|
97
|
+
if (!a || !f)
|
|
98
|
+
return;
|
|
99
|
+
setPos(computePosition(a.getBoundingClientRect(), { width: f.offsetWidth, height: f.offsetHeight }, { side, align, sideOffset, alignOffset }, { width: window.innerWidth, height: window.innerHeight }));
|
|
100
|
+
}, [anchorRef, side, align, sideOffset, alignOffset]);
|
|
101
|
+
React.useLayoutEffect(() => {
|
|
102
|
+
// Drop the last position on close so a reopen stays visibility:hidden until
|
|
103
|
+
// it is re-measured, rather than flashing one frame at stale coordinates.
|
|
104
|
+
if (!open) {
|
|
105
|
+
setPos(null);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const ro = new ResizeObserver(update);
|
|
109
|
+
window.addEventListener("scroll", update, true);
|
|
110
|
+
window.addEventListener("resize", update);
|
|
111
|
+
// The floating node is portaled and may attach a frame late (the Portal
|
|
112
|
+
// defers rendering until its own effect runs). Retry each frame until the
|
|
113
|
+
// ref is set, then measure + observe — otherwise the first update() runs
|
|
114
|
+
// against a null ref, `pos` stays null, and the popup is stuck
|
|
115
|
+
// `visibility:hidden`.
|
|
116
|
+
let raf = 0;
|
|
117
|
+
const attach = () => {
|
|
118
|
+
if (!floatingRef.current) {
|
|
119
|
+
raf = requestAnimationFrame(attach);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
update();
|
|
123
|
+
ro.observe(floatingRef.current);
|
|
124
|
+
if (anchorRef.current)
|
|
125
|
+
ro.observe(anchorRef.current);
|
|
126
|
+
};
|
|
127
|
+
attach();
|
|
128
|
+
return () => {
|
|
129
|
+
cancelAnimationFrame(raf);
|
|
130
|
+
window.removeEventListener("scroll", update, true);
|
|
131
|
+
window.removeEventListener("resize", update);
|
|
132
|
+
ro.disconnect();
|
|
133
|
+
};
|
|
134
|
+
}, [open, update, anchorRef]);
|
|
135
|
+
return { floatingRef, pos, update };
|
|
136
|
+
}
|