@waveso/ui 0.0.9 → 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/dist/accordion.d.ts +24 -8
- package/dist/accordion.d.ts.map +1 -0
- package/dist/accordion.js +50 -73
- package/dist/accordion.js.map +1 -1
- package/dist/action-bar.d.ts +83 -0
- package/dist/action-bar.d.ts.map +1 -0
- package/dist/action-bar.js +264 -0
- package/dist/action-bar.js.map +1 -0
- package/dist/alert-dialog.d.ts +56 -21
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +75 -127
- package/dist/alert-dialog.js.map +1 -1
- package/dist/alert.d.ts +26 -11
- package/dist/alert.d.ts.map +1 -0
- package/dist/alert.js +37 -68
- package/dist/alert.js.map +1 -1
- package/dist/animate.d.ts +117 -75
- package/dist/animate.d.ts.map +1 -0
- package/dist/animate.js +259 -223
- package/dist/animate.js.map +1 -1
- package/dist/aspect-ratio.d.ts +11 -6
- package/dist/aspect-ratio.d.ts.map +1 -0
- package/dist/aspect-ratio.js +12 -14
- package/dist/aspect-ratio.js.map +1 -1
- package/dist/autocomplete.d.ts +91 -25
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +119 -181
- package/dist/autocomplete.js.map +1 -1
- package/dist/avatar.d.ts +32 -11
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +42 -89
- package/dist/avatar.js.map +1 -1
- package/dist/badge.d.ts +15 -8
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +34 -48
- package/dist/badge.js.map +1 -1
- package/dist/breadcrumb.d.ts +35 -11
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +60 -110
- package/dist/breadcrumb.js.map +1 -1
- package/dist/button-group.d.ts +26 -13
- package/dist/button-group.d.ts.map +1 -0
- package/dist/button-group.js +38 -76
- package/dist/button-group.js.map +1 -1
- package/dist/button.d.ts +17 -10
- package/dist/button.d.ts.map +1 -0
- package/dist/button.js +50 -3
- package/dist/button.js.map +1 -1
- package/dist/card.d.ts +35 -11
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +43 -82
- package/dist/card.js.map +1 -1
- package/dist/checkbox.d.ts +6 -4
- package/dist/checkbox.d.ts.map +1 -0
- package/dist/checkbox.js +21 -29
- package/dist/checkbox.js.map +1 -1
- package/dist/collapsible.d.ts +15 -7
- package/dist/collapsible.d.ts.map +1 -0
- package/dist/collapsible.js +19 -8
- package/dist/collapsible.js.map +1 -1
- package/dist/combobox.d.ts +83 -23
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +149 -247
- package/dist/combobox.js.map +1 -1
- package/dist/context-menu.d.ts +80 -26
- package/dist/context-menu.d.ts.map +1 -0
- package/dist/context-menu.js +108 -164
- package/dist/context-menu.js.map +1 -1
- package/dist/count.d.ts +45 -31
- package/dist/count.d.ts.map +1 -0
- package/dist/count.js +170 -165
- package/dist/count.js.map +1 -1
- package/dist/dialog.d.ts +61 -28
- package/dist/dialog.d.ts.map +1 -0
- package/dist/dialog.js +77 -120
- package/dist/dialog.js.map +1 -1
- package/dist/direction.d.ts +2 -1
- package/dist/direction.js +3 -3
- package/dist/drawer.d.ts +45 -15
- package/dist/drawer.d.ts.map +1 -0
- package/dist/drawer.js +93 -5
- package/dist/drawer.js.map +1 -1
- package/dist/encrypted-text.d.ts +25 -12
- package/dist/encrypted-text.d.ts.map +1 -0
- package/dist/encrypted-text.js +102 -134
- package/dist/encrypted-text.js.map +1 -1
- package/dist/field.d.ts +37 -21
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +52 -3
- package/dist/field.js.map +1 -1
- package/dist/film-grain-shader.d.ts +6 -0
- package/dist/film-grain-shader.d.ts.map +1 -0
- package/dist/film-grain-shader.js +88 -0
- package/dist/film-grain-shader.js.map +1 -0
- package/dist/film-grain-webgl.d.ts +20 -0
- package/dist/film-grain-webgl.d.ts.map +1 -0
- package/dist/film-grain-webgl.js +306 -0
- package/dist/film-grain-webgl.js.map +1 -0
- package/dist/film-grain.d.ts +21 -11
- package/dist/film-grain.d.ts.map +1 -0
- package/dist/film-grain.js +28 -420
- package/dist/film-grain.js.map +1 -1
- package/dist/form.d.ts +64 -49
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +112 -91
- package/dist/form.js.map +1 -1
- package/dist/gradient-reveal-text.d.ts +35 -18
- package/dist/gradient-reveal-text.d.ts.map +1 -0
- package/dist/gradient-reveal-text.js +238 -202
- package/dist/gradient-reveal-text.js.map +1 -1
- package/dist/hooks/use-mobile.d.ts +3 -1
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +28 -2
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/infinite-scroll.d.ts +29 -15
- package/dist/infinite-scroll.d.ts.map +1 -0
- package/dist/infinite-scroll.js +69 -99
- package/dist/infinite-scroll.js.map +1 -1
- package/dist/input-group.d.ts +41 -18
- package/dist/input-group.d.ts.map +1 -0
- package/dist/input-group.js +80 -6
- package/dist/input-group.js.map +1 -1
- package/dist/input-otp.d.ts +26 -10
- package/dist/input-otp.d.ts.map +1 -0
- package/dist/input-otp.js +40 -70
- package/dist/input-otp.js.map +1 -1
- package/dist/input.d.ts +10 -4
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +16 -3
- package/dist/input.js.map +1 -1
- package/dist/item.d.ts +58 -23
- package/dist/item.d.ts.map +1 -0
- package/dist/item.js +102 -160
- package/dist/item.js.map +1 -1
- package/dist/kbd.d.ts +12 -4
- package/dist/kbd.d.ts.map +1 -0
- package/dist/kbd.js +15 -24
- package/dist/kbd.js.map +1 -1
- package/dist/label.d.ts +9 -4
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +12 -16
- package/dist/label.js.map +1 -1
- package/dist/lib/focus.d.ts +42 -0
- package/dist/lib/focus.d.ts.map +1 -0
- package/dist/lib/focus.js +21 -0
- package/dist/lib/focus.js.map +1 -0
- package/dist/lib/internal-icons.d.ts +32 -0
- package/dist/lib/internal-icons.d.ts.map +1 -0
- package/dist/lib/internal-icons.js +222 -0
- package/dist/lib/internal-icons.js.map +1 -0
- package/dist/lib/utils.d.ts +4 -2
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +12 -2
- package/dist/lib/utils.js.map +1 -1
- package/dist/masonry.d.ts +25 -11
- package/dist/masonry.d.ts.map +1 -0
- package/dist/masonry.js +188 -229
- package/dist/masonry.js.map +1 -1
- package/dist/menu.d.ts +84 -26
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +141 -4
- package/dist/menu.js.map +1 -1
- package/dist/menubar.d.ts +60 -22
- package/dist/menubar.d.ts.map +1 -0
- package/dist/menubar.js +80 -52
- package/dist/menubar.js.map +1 -1
- package/dist/pagination.d.ts +38 -17
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +68 -107
- package/dist/pagination.js.map +1 -1
- package/dist/popover.d.ts +56 -14
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +62 -87
- package/dist/popover.js.map +1 -1
- package/dist/preview-card.d.ts +28 -9
- package/dist/preview-card.d.ts.map +1 -0
- package/dist/preview-card.js +40 -60
- package/dist/preview-card.js.map +1 -1
- package/dist/progress.d.ts +28 -9
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +35 -60
- package/dist/progress.js.map +1 -1
- package/dist/radio-group.d.ts +14 -8
- package/dist/radio-group.d.ts.map +1 -0
- package/dist/radio-group.js +18 -22
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.d.ts +14 -6
- package/dist/radio.d.ts.map +1 -0
- package/dist/radio.js +24 -3
- package/dist/radio.js.map +1 -1
- package/dist/scroll-area.d.ts +16 -6
- package/dist/scroll-area.d.ts.map +1 -0
- package/dist/scroll-area.js +34 -55
- package/dist/scroll-area.js.map +1 -1
- package/dist/select.d.ts +66 -18
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +105 -185
- package/dist/select.js.map +1 -1
- package/dist/separator.d.ts +11 -5
- package/dist/separator.d.ts.map +1 -0
- package/dist/separator.js +17 -3
- package/dist/separator.js.map +1 -1
- package/dist/sidebar.d.ts +172 -79
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +363 -585
- package/dist/sidebar.js.map +1 -1
- package/dist/skeleton.d.ts +8 -3
- package/dist/skeleton.d.ts.map +1 -0
- package/dist/skeleton.js +13 -3
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.d.ts +16 -6
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +40 -67
- package/dist/slider.js.map +1 -1
- package/dist/spinner.d.ts +8 -3
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +15 -4
- package/dist/spinner.js.map +1 -1
- package/dist/switch.d.ts +12 -6
- package/dist/switch.d.ts.map +1 -0
- package/dist/switch.js +18 -25
- package/dist/switch.js.map +1 -1
- package/dist/table.d.ts +37 -11
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +51 -88
- package/dist/table.js.map +1 -1
- package/dist/tabs.d.ts +28 -12
- package/dist/tabs.d.ts.map +1 -0
- package/dist/tabs.js +40 -74
- package/dist/tabs.js.map +1 -1
- package/dist/textarea.d.ts +13 -6
- package/dist/textarea.d.ts.map +1 -0
- package/dist/textarea.js +19 -3
- package/dist/textarea.js.map +1 -1
- package/dist/toast.d.ts +63 -39
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +177 -215
- package/dist/toast.js.map +1 -1
- package/dist/toggle-group.d.ts +26 -12
- package/dist/toggle-group.d.ts.map +1 -0
- package/dist/toggle-group.js +49 -73
- package/dist/toggle-group.js.map +1 -1
- package/dist/toggle.d.ts +17 -10
- package/dist/toggle.d.ts.map +1 -0
- package/dist/toggle.js +38 -3
- package/dist/toggle.js.map +1 -1
- package/dist/tooltip.d.ts +35 -14
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +52 -3
- package/dist/tooltip.js.map +1 -1
- package/dist/typewriter.d.ts +44 -31
- package/dist/typewriter.d.ts.map +1 -0
- package/dist/typewriter.js +185 -185
- package/dist/typewriter.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-45VQAWIM.js +0 -228
- package/dist/chunk-45VQAWIM.js.map +0 -1
- package/dist/chunk-6Y7LPQMO.js +0 -11
- package/dist/chunk-6Y7LPQMO.js.map +0 -1
- package/dist/chunk-76UQO56T.js +0 -19
- package/dist/chunk-76UQO56T.js.map +0 -1
- package/dist/chunk-7F4MPMLJ.js +0 -17
- package/dist/chunk-7F4MPMLJ.js.map +0 -1
- package/dist/chunk-BKTJYX4M.js +0 -143
- package/dist/chunk-BKTJYX4M.js.map +0 -1
- package/dist/chunk-D5XPEJ6T.js +0 -36
- package/dist/chunk-D5XPEJ6T.js.map +0 -1
- package/dist/chunk-DIGOLJIR.js +0 -105
- package/dist/chunk-DIGOLJIR.js.map +0 -1
- package/dist/chunk-IQ7YQ5XA.js +0 -141
- package/dist/chunk-IQ7YQ5XA.js.map +0 -1
- package/dist/chunk-NCHHHWTB.js +0 -85
- package/dist/chunk-NCHHHWTB.js.map +0 -1
- package/dist/chunk-OUFYQLVN.js +0 -56
- package/dist/chunk-OUFYQLVN.js.map +0 -1
- package/dist/chunk-QFSEK4M6.js +0 -22
- package/dist/chunk-QFSEK4M6.js.map +0 -1
- package/dist/chunk-QRW37LRP.js +0 -25
- package/dist/chunk-QRW37LRP.js.map +0 -1
- package/dist/chunk-RPQHL6C5.js +0 -26
- package/dist/chunk-RPQHL6C5.js.map +0 -1
- package/dist/chunk-V4ZX4YCP.js +0 -66
- package/dist/chunk-V4ZX4YCP.js.map +0 -1
- package/dist/chunk-YTSQQTSF.js +0 -44
- package/dist/chunk-YTSQQTSF.js.map +0 -1
- package/dist/chunk-ZZZH3JGW.js +0 -23
- package/dist/chunk-ZZZH3JGW.js.map +0 -1
- package/dist/direction.js.map +0 -1
package/dist/accordion.d.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Accordion as Accordion$1 } from "@base-ui/react/accordion";
|
|
3
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
|
+
//#region src/accordion.d.ts
|
|
5
6
|
type AccordionProps = React.ComponentProps<typeof Accordion$1.Root>;
|
|
6
7
|
type AccordionItemProps = React.ComponentProps<typeof Accordion$1.Item>;
|
|
7
8
|
type AccordionTriggerProps = React.ComponentProps<typeof Accordion$1.Trigger>;
|
|
8
9
|
type AccordionContentProps = React.ComponentProps<typeof Accordion$1.Panel>;
|
|
9
|
-
declare function Accordion({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
declare function Accordion({
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}: AccordionProps): _$react_jsx_runtime0.JSX.Element;
|
|
14
|
+
declare function AccordionItem({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: AccordionItemProps): _$react_jsx_runtime0.JSX.Element;
|
|
18
|
+
declare function AccordionTrigger({
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}: AccordionTriggerProps): _$react_jsx_runtime0.JSX.Element;
|
|
23
|
+
declare function AccordionContent({
|
|
24
|
+
className,
|
|
25
|
+
children,
|
|
26
|
+
...props
|
|
27
|
+
}: AccordionContentProps): _$react_jsx_runtime0.JSX.Element;
|
|
28
|
+
//#endregion
|
|
14
29
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
30
|
+
//# sourceMappingURL=accordion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accordion.d.ts","names":[],"sources":["../src/accordion.tsx"],"mappings":";;;;;KAUK,cAAA,GAAiB,KAAA,CAAM,cAAA,QAAsB,WAAA,CAAmB,IAAA;AAAA,KAChE,kBAAA,GAAqB,KAAA,CAAM,cAAA,QAAsB,WAAA,CAAmB,IAAA;AAAA,KACpE,qBAAA,GAAwB,KAAA,CAAM,cAAA,QAAsB,WAAA,CAAmB,OAAA;AAAA,KACvE,qBAAA,GAAwB,KAAA,CAAM,cAAA,QAAsB,WAAA,CAAmB,KAAA;AAAA,iBAEnE,SAAA,CAAA;EAAY,SAAA;EAAA,GAAc;AAAA,GAAS,cAAA,GAAc,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAajD,aAAA,CAAA;EAAgB,SAAA;EAAA,GAAc;AAAA,GAAS,kBAAA,GAAkB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAazD,gBAAA,CAAA;EACP,SAAA;EACA,QAAA;EAAA,GACG;AAAA,GACF,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAmBf,gBAAA,CAAA;EACP,SAAA;EACA,QAAA;EAAA,GACG;AAAA,GACF,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/accordion.js
CHANGED
|
@@ -1,81 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "./lib/utils.js";
|
|
3
|
+
import { ChevronDownIcon, ChevronUpIcon } from "./lib/internal-icons.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { Accordion as Accordion$1 } from "@base-ui/react/accordion";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/accordion.tsx
|
|
6
8
|
function Accordion({ className, ...props }) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"overflow-hidden flex w-full flex-col",
|
|
13
|
-
className
|
|
14
|
-
),
|
|
15
|
-
...props
|
|
16
|
-
}
|
|
17
|
-
);
|
|
9
|
+
return /* @__PURE__ */ jsx(Accordion$1.Root, {
|
|
10
|
+
"data-slot": "accordion",
|
|
11
|
+
className: cn("overflow-hidden flex w-full flex-col", className),
|
|
12
|
+
...props
|
|
13
|
+
});
|
|
18
14
|
}
|
|
19
15
|
function AccordionItem({ className, ...props }) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"not-last:border-b",
|
|
26
|
-
className
|
|
27
|
-
),
|
|
28
|
-
...props
|
|
29
|
-
}
|
|
30
|
-
);
|
|
16
|
+
return /* @__PURE__ */ jsx(Accordion$1.Item, {
|
|
17
|
+
"data-slot": "accordion-item",
|
|
18
|
+
className: cn("not-last:border-b", className),
|
|
19
|
+
...props
|
|
20
|
+
});
|
|
31
21
|
}
|
|
32
|
-
function AccordionTrigger({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
function AccordionTrigger({ className, children, ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsx(Accordion$1.Header, {
|
|
24
|
+
"data-slot": "accordion-header",
|
|
25
|
+
className: "flex",
|
|
26
|
+
children: /* @__PURE__ */ jsxs(Accordion$1.Trigger, {
|
|
27
|
+
"data-slot": "accordion-trigger",
|
|
28
|
+
className: cn("focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-3 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4", className),
|
|
29
|
+
...props,
|
|
30
|
+
children: [
|
|
31
|
+
children,
|
|
32
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
33
|
+
"data-slot": "accordion-trigger-icon",
|
|
34
|
+
className: "pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ jsx(ChevronUpIcon, {
|
|
37
|
+
"data-slot": "accordion-trigger-icon",
|
|
38
|
+
className: "pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
});
|
|
53
43
|
}
|
|
54
|
-
function AccordionContent({
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...props,
|
|
65
|
-
children: /* @__PURE__ */ jsx(
|
|
66
|
-
"div",
|
|
67
|
-
{
|
|
68
|
-
className: cn(
|
|
69
|
-
"[&_a]:hover:text-foreground h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
|
|
70
|
-
className
|
|
71
|
-
),
|
|
72
|
-
children
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
);
|
|
44
|
+
function AccordionContent({ className, children, ...props }) {
|
|
45
|
+
return /* @__PURE__ */ jsx(Accordion$1.Panel, {
|
|
46
|
+
"data-slot": "accordion-content",
|
|
47
|
+
className: "data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden text-sm",
|
|
48
|
+
...props,
|
|
49
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
50
|
+
className: cn("[&_a]:hover:text-foreground h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4", className),
|
|
51
|
+
children
|
|
52
|
+
})
|
|
53
|
+
});
|
|
77
54
|
}
|
|
78
|
-
|
|
55
|
+
//#endregion
|
|
79
56
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
80
|
-
|
|
57
|
+
|
|
81
58
|
//# sourceMappingURL=accordion.js.map
|
package/dist/accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"accordion.js","names":["AccordionPrimitive"],"sources":["../src/accordion.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\"\n\nimport { cn } from \"./lib/utils\"\n\nimport { ChevronDownIcon, ChevronUpIcon } from \"./lib/internal-icons\"\n\ntype AccordionProps = React.ComponentProps<typeof AccordionPrimitive.Root>\ntype AccordionItemProps = React.ComponentProps<typeof AccordionPrimitive.Item>\ntype AccordionTriggerProps = React.ComponentProps<typeof AccordionPrimitive.Trigger>\ntype AccordionContentProps = React.ComponentProps<typeof AccordionPrimitive.Panel>\n\nfunction Accordion({ className, ...props }: AccordionProps) {\n return (\n <AccordionPrimitive.Root\n data-slot=\"accordion\"\n className={cn(\n \"overflow-hidden flex w-full flex-col\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionItem({ className, ...props }: AccordionItemProps) {\n return (\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn(\n \"not-last:border-b\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionTrigger({\n className,\n children,\n ...props\n}: AccordionTriggerProps) {\n return (\n <AccordionPrimitive.Header data-slot=\"accordion-header\" className=\"flex\">\n <AccordionPrimitive.Trigger\n data-slot=\"accordion-trigger\"\n className={cn(\n \"focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-3 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronDownIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\" />\n <ChevronUpIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\" />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n )\n}\n\nfunction AccordionContent({\n className,\n children,\n ...props\n}: AccordionContentProps) {\n return (\n <AccordionPrimitive.Panel\n data-slot=\"accordion-content\"\n className=\"data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden text-sm\"\n {...props}\n >\n <div\n className={cn(\n \"[&_a]:hover:text-foreground h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4\",\n className\n )}\n >\n {children}\n </div>\n </AccordionPrimitive.Panel>\n )\n}\n\nexport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n}\n"],"mappings":";;;;;;;AAeA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAyB;AAC1D,QACE,oBAACA,YAAmB,MAApB;EACE,aAAU;EACV,WAAW,GACT,wCACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,cAAc,EAAE,WAAW,GAAG,SAA6B;AAClE,QACE,oBAACA,YAAmB,MAApB;EACE,aAAU;EACV,WAAW,GACT,qBACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,iBAAiB,EACxB,WACA,UACA,GAAG,SACqB;AACxB,QACE,oBAACA,YAAmB,QAApB;EAA2B,aAAU;EAAmB,WAAU;YAChE,qBAACA,YAAmB,SAApB;GACE,aAAU;GACV,WAAW,GACT,8fACA,UACD;GACD,GAAI;aANN;IAQG;IACD,oBAAC,iBAAD;KAAiB,aAAU;KAAyB,WAAU;KAA8E,CAAA;IAC5I,oBAAC,eAAD;KAAe,aAAU;KAAyB,WAAU;KAAqF,CAAA;IACtH;;EACH,CAAA;;AAIhC,SAAS,iBAAiB,EACxB,WACA,UACA,GAAG,SACqB;AACxB,QACE,oBAACA,YAAmB,OAApB;EACE,aAAU;EACV,WAAU;EACV,GAAI;YAEJ,oBAAC,OAAD;GACE,WAAW,GACT,2LACA,UACD;GAEA;GACG,CAAA;EACmB,CAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/action-bar.d.ts
|
|
5
|
+
/** State a form registers with the ActionBar. */
|
|
6
|
+
interface ActionBarEntry {
|
|
7
|
+
/** Whether this form has unsaved changes. */
|
|
8
|
+
hasChanges: boolean;
|
|
9
|
+
/** Whether this form is currently saving. */
|
|
10
|
+
saving: boolean;
|
|
11
|
+
/** Save this form's changes. */
|
|
12
|
+
onSave: () => void | Promise<void>;
|
|
13
|
+
/** Discard this form's changes. */
|
|
14
|
+
onReset: () => void;
|
|
15
|
+
}
|
|
16
|
+
interface ActionBarProviderProps {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
/** Message when one form has changes. */
|
|
19
|
+
message?: string;
|
|
20
|
+
/** Message template for multiple dirty forms. */
|
|
21
|
+
pluralMessage?: (count: number) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Announced to assistive tech (and the only feedback reduced-motion
|
|
24
|
+
* users get) when guarded navigation is blocked.
|
|
25
|
+
*/
|
|
26
|
+
blockedMessage?: string;
|
|
27
|
+
/** Additional className for the bar's outer wrapper. */
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Global action bar for unsaved changes.
|
|
32
|
+
*
|
|
33
|
+
* Place once at the root layout. Forms register via `useActionBar()`.
|
|
34
|
+
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - Multi-form aggregation (Save All / Reset All)
|
|
37
|
+
* - ⌘S / Ctrl+S keyboard shortcut
|
|
38
|
+
* - Navigation guard with Discord-style page jiggle
|
|
39
|
+
* - `beforeunload` guard for browser close/refresh
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* // Root layout — once
|
|
44
|
+
* <ActionBarProvider>{children}</ActionBarProvider>
|
|
45
|
+
*
|
|
46
|
+
* // Any form component
|
|
47
|
+
* useActionBar('profile', { hasChanges, saving, onSave, onReset });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function ActionBarProvider({
|
|
51
|
+
children,
|
|
52
|
+
message,
|
|
53
|
+
pluralMessage,
|
|
54
|
+
blockedMessage,
|
|
55
|
+
className
|
|
56
|
+
}: ActionBarProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
57
|
+
/**
|
|
58
|
+
* Register a form's unsaved state with the global ActionBar.
|
|
59
|
+
*
|
|
60
|
+
* Unregisters automatically on unmount.
|
|
61
|
+
*
|
|
62
|
+
* @param id — Unique key (e.g., 'profile', 'notifications')
|
|
63
|
+
* @param entry — Current form state
|
|
64
|
+
*/
|
|
65
|
+
declare function useActionBar(id: string, entry: ActionBarEntry): void;
|
|
66
|
+
/**
|
|
67
|
+
* Returns a function that guards navigation.
|
|
68
|
+
* If unsaved changes exist, triggers the jiggle and blocks navigation.
|
|
69
|
+
* Otherwise navigates normally via the provided callback.
|
|
70
|
+
*
|
|
71
|
+
* @param navigate — Your navigation function (e.g., `router.push`)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* const router = useRouter();
|
|
76
|
+
* const guardedPush = useActionBarGuard(router.push);
|
|
77
|
+
* <button onClick={() => guardedPush('/settings')}>Settings</button>
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function useActionBarGuard(navigate: (href: string) => void): (href: string) => boolean;
|
|
81
|
+
//#endregion
|
|
82
|
+
export { ActionBarEntry, ActionBarProvider, ActionBarProviderProps, useActionBar, useActionBarGuard };
|
|
83
|
+
//# sourceMappingURL=action-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-bar.d.ts","names":[],"sources":["../src/action-bar.tsx"],"mappings":";;;;;UAeiB,cAAA;;EAEf,UAAA;EAF6B;EAI7B,MAAA;EAE4B;EAA5B,MAAA,eAAqB,OAAA;EAFrB;EAIA,OAAA;AAAA;AAAA,UA4Ce,sBAAA;EACf,QAAA,EAAU,KAAA,CAAM,SAAA;EA7CT;EA+CP,OAAA;EAHqC;EAKrC,aAAA,IAAiB,KAAA;EAJQ;;;;EASzB,cAAA;EALA;EAOA,SAAA;AAAA;;;;AACD;;;;;;;;;;;;;;;;;iBAsBQ,iBAAA,CAAA;EACP,QAAA;EACA,OAAA;EACA,aAAA;EACA,cAAA;EACA;AAAA,GACC,sBAAA,GAAsB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;;iBAuQhB,YAAA,CAAa,EAAA,UAAY,KAAA,EAAO,cAAA;AAvQhB;;;;;;;;;AAuQ8B;;;;;AAvQ9B,iBA8ShB,iBAAA,CAAkB,QAAA,GAAW,IAAA,qBAAqB,IAAA"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "./lib/utils.js";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { Spinner } from "./spinner.js";
|
|
5
|
+
import { Kbd } from "./kbd.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
9
|
+
//#region src/action-bar.tsx
|
|
10
|
+
const JIGGLE_STEPS = 6;
|
|
11
|
+
const JIGGLE_INTERVAL = 50;
|
|
12
|
+
const JIGGLE_RANGE = 15;
|
|
13
|
+
/**
|
|
14
|
+
* SSR-safe platform detection. Returns `false` on the server and on the
|
|
15
|
+
* first client render so hydration matches, then resolves the real
|
|
16
|
+
* platform after mount. Avoids the `⌘S`/`Ctrl+S` hydration mismatch
|
|
17
|
+
* that a module-scope `navigator` read would cause.
|
|
18
|
+
*/
|
|
19
|
+
function useIsMac() {
|
|
20
|
+
const [isMac, setIsMac] = React.useState(false);
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
setIsMac(/Mac|iPhone|iPad/.test(navigator.userAgent));
|
|
23
|
+
}, []);
|
|
24
|
+
return isMac;
|
|
25
|
+
}
|
|
26
|
+
const ActionBarContext = React.createContext(null);
|
|
27
|
+
/**
|
|
28
|
+
* Global action bar for unsaved changes.
|
|
29
|
+
*
|
|
30
|
+
* Place once at the root layout. Forms register via `useActionBar()`.
|
|
31
|
+
*
|
|
32
|
+
* Features:
|
|
33
|
+
* - Multi-form aggregation (Save All / Reset All)
|
|
34
|
+
* - ⌘S / Ctrl+S keyboard shortcut
|
|
35
|
+
* - Navigation guard with Discord-style page jiggle
|
|
36
|
+
* - `beforeunload` guard for browser close/refresh
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* // Root layout — once
|
|
41
|
+
* <ActionBarProvider>{children}</ActionBarProvider>
|
|
42
|
+
*
|
|
43
|
+
* // Any form component
|
|
44
|
+
* useActionBar('profile', { hasChanges, saving, onSave, onReset });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function ActionBarProvider({ children, message = "You have unsaved changes", pluralMessage = (count) => `${count} unsaved changes`, blockedMessage = "Save or reset your changes before leaving this page", className }) {
|
|
48
|
+
const entriesRef = React.useRef(/* @__PURE__ */ new Map());
|
|
49
|
+
const [, forceUpdate] = React.useState(0);
|
|
50
|
+
const [jiggleTransform, setJiggleTransform] = React.useState(null);
|
|
51
|
+
const jiggleTimerRef = React.useRef(null);
|
|
52
|
+
const contentRef = React.useRef(null);
|
|
53
|
+
const isMac = useIsMac();
|
|
54
|
+
const prefersReducedMotion = useReducedMotion();
|
|
55
|
+
const reducedMotionRef = React.useRef(prefersReducedMotion);
|
|
56
|
+
reducedMotionRef.current = prefersReducedMotion;
|
|
57
|
+
const [blockedNotice, setBlockedNotice] = React.useState("");
|
|
58
|
+
const register = React.useCallback((id, entry) => {
|
|
59
|
+
const prev = entriesRef.current.get(id);
|
|
60
|
+
entriesRef.current.set(id, entry);
|
|
61
|
+
if (!prev || prev.hasChanges !== entry.hasChanges || prev.saving !== entry.saving) forceUpdate((n) => n + 1);
|
|
62
|
+
}, []);
|
|
63
|
+
const unregister = React.useCallback((id) => {
|
|
64
|
+
if (entriesRef.current.delete(id)) forceUpdate((n) => n + 1);
|
|
65
|
+
}, []);
|
|
66
|
+
const hasDirty = React.useCallback(() => {
|
|
67
|
+
for (const entry of entriesRef.current.values()) if (entry.hasChanges) return true;
|
|
68
|
+
return false;
|
|
69
|
+
}, []);
|
|
70
|
+
const jiggle = React.useCallback(() => {
|
|
71
|
+
setBlockedNotice("");
|
|
72
|
+
requestAnimationFrame(() => setBlockedNotice(blockedMessage));
|
|
73
|
+
if (reducedMotionRef.current) return;
|
|
74
|
+
if (jiggleTimerRef.current) return;
|
|
75
|
+
let step = 0;
|
|
76
|
+
function tick() {
|
|
77
|
+
step++;
|
|
78
|
+
if (step >= JIGGLE_STEPS) {
|
|
79
|
+
setJiggleTransform(null);
|
|
80
|
+
jiggleTimerRef.current = null;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const x = (Math.random() - .5) * 2 * JIGGLE_RANGE;
|
|
84
|
+
const y = (Math.random() - .5) * 2 * JIGGLE_RANGE;
|
|
85
|
+
setJiggleTransform(`translate3d(${x.toFixed(5)}px, ${y.toFixed(5)}px, 0px)`);
|
|
86
|
+
jiggleTimerRef.current = setTimeout(tick, JIGGLE_INTERVAL);
|
|
87
|
+
}
|
|
88
|
+
tick();
|
|
89
|
+
}, [blockedMessage]);
|
|
90
|
+
const dirty = [];
|
|
91
|
+
let anySaving = false;
|
|
92
|
+
for (const entry of entriesRef.current.values()) {
|
|
93
|
+
if (entry.hasChanges) dirty.push(entry);
|
|
94
|
+
if (entry.saving) anySaving = true;
|
|
95
|
+
}
|
|
96
|
+
const showBar = dirty.length > 0 || anySaving;
|
|
97
|
+
const dirtyCount = dirty.length;
|
|
98
|
+
const displayMessage = dirtyCount > 1 ? pluralMessage(dirtyCount) : message;
|
|
99
|
+
const handleSaveAll = React.useCallback(async () => {
|
|
100
|
+
const saves = [...entriesRef.current.values()].filter((e) => e.hasChanges).map((e) => e.onSave());
|
|
101
|
+
await Promise.all(saves);
|
|
102
|
+
}, []);
|
|
103
|
+
const handleResetAll = React.useCallback(() => {
|
|
104
|
+
for (const entry of entriesRef.current.values()) if (entry.hasChanges) entry.onReset();
|
|
105
|
+
}, []);
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
function handleKeyDown(e) {
|
|
108
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
if (hasDirty()) handleSaveAll();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
114
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
115
|
+
}, [hasDirty, handleSaveAll]);
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
function handleBeforeUnload(e) {
|
|
118
|
+
if (hasDirty()) e.preventDefault();
|
|
119
|
+
}
|
|
120
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
121
|
+
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
122
|
+
}, [hasDirty]);
|
|
123
|
+
const ctx = React.useMemo(() => ({
|
|
124
|
+
register,
|
|
125
|
+
unregister,
|
|
126
|
+
hasDirty,
|
|
127
|
+
jiggle
|
|
128
|
+
}), [
|
|
129
|
+
register,
|
|
130
|
+
unregister,
|
|
131
|
+
hasDirty,
|
|
132
|
+
jiggle
|
|
133
|
+
]);
|
|
134
|
+
return /* @__PURE__ */ jsxs(ActionBarContext.Provider, {
|
|
135
|
+
value: ctx,
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx("div", {
|
|
138
|
+
"data-slot": "action-bar-shell",
|
|
139
|
+
ref: contentRef,
|
|
140
|
+
style: jiggleTransform ? { transform: jiggleTransform } : void 0,
|
|
141
|
+
children
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsx("span", {
|
|
144
|
+
"data-slot": "action-bar-status",
|
|
145
|
+
"aria-live": "polite",
|
|
146
|
+
className: "sr-only",
|
|
147
|
+
children: showBar ? displayMessage : ""
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ jsx("span", {
|
|
150
|
+
"data-slot": "action-bar-blocked-status",
|
|
151
|
+
"aria-live": "assertive",
|
|
152
|
+
className: "sr-only",
|
|
153
|
+
children: blockedNotice
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: showBar && /* @__PURE__ */ jsx(motion.div, {
|
|
156
|
+
"data-slot": "action-bar",
|
|
157
|
+
initial: prefersReducedMotion ? {
|
|
158
|
+
opacity: 0,
|
|
159
|
+
x: "-50%"
|
|
160
|
+
} : {
|
|
161
|
+
opacity: 0,
|
|
162
|
+
y: 20,
|
|
163
|
+
x: "-50%"
|
|
164
|
+
},
|
|
165
|
+
animate: {
|
|
166
|
+
opacity: 1,
|
|
167
|
+
y: 0,
|
|
168
|
+
x: "-50%"
|
|
169
|
+
},
|
|
170
|
+
exit: prefersReducedMotion ? {
|
|
171
|
+
opacity: 0,
|
|
172
|
+
x: "-50%"
|
|
173
|
+
} : {
|
|
174
|
+
opacity: 0,
|
|
175
|
+
y: 20,
|
|
176
|
+
x: "-50%"
|
|
177
|
+
},
|
|
178
|
+
transition: {
|
|
179
|
+
duration: .2,
|
|
180
|
+
ease: "easeOut"
|
|
181
|
+
},
|
|
182
|
+
className: "fixed bottom-6 left-1/2 z-50",
|
|
183
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
184
|
+
"data-slot": "action-bar-content",
|
|
185
|
+
className: cn("flex items-center gap-6 rounded-xl border border-border/60 bg-card px-5 py-2.5 shadow-lg ring-1 ring-black/5 backdrop-blur-sm dark:ring-white/5", className),
|
|
186
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
187
|
+
"data-slot": "action-bar-message",
|
|
188
|
+
className: "whitespace-nowrap text-sm font-medium text-foreground",
|
|
189
|
+
children: displayMessage
|
|
190
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
191
|
+
"data-slot": "action-bar-actions",
|
|
192
|
+
className: "flex items-center gap-1.5",
|
|
193
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
194
|
+
type: "button",
|
|
195
|
+
variant: "ghost",
|
|
196
|
+
disabled: anySaving,
|
|
197
|
+
onClick: handleResetAll,
|
|
198
|
+
className: "h-9 px-3 text-sm hover:text-destructive",
|
|
199
|
+
children: "Reset"
|
|
200
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
201
|
+
type: "button",
|
|
202
|
+
variant: "default",
|
|
203
|
+
disabled: anySaving,
|
|
204
|
+
onClick: handleSaveAll,
|
|
205
|
+
className: "h-9 px-4 text-sm",
|
|
206
|
+
children: anySaving ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { className: "mr-1.5 h-3.5 w-3.5" }), "Saving"] }) : /* @__PURE__ */ jsxs(Fragment, { children: ["Save", /* @__PURE__ */ jsx(Kbd, {
|
|
207
|
+
className: "ml-1.5",
|
|
208
|
+
children: isMac ? "⌘S" : "Ctrl+S"
|
|
209
|
+
})] })
|
|
210
|
+
})]
|
|
211
|
+
})]
|
|
212
|
+
})
|
|
213
|
+
}) })
|
|
214
|
+
]
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Register a form's unsaved state with the global ActionBar.
|
|
219
|
+
*
|
|
220
|
+
* Unregisters automatically on unmount.
|
|
221
|
+
*
|
|
222
|
+
* @param id — Unique key (e.g., 'profile', 'notifications')
|
|
223
|
+
* @param entry — Current form state
|
|
224
|
+
*/
|
|
225
|
+
function useActionBar(id, entry) {
|
|
226
|
+
const ctx = React.useContext(ActionBarContext);
|
|
227
|
+
if (!ctx) throw new Error("useActionBar must be used within <ActionBarProvider>");
|
|
228
|
+
const { register, unregister } = ctx;
|
|
229
|
+
React.useEffect(() => {
|
|
230
|
+
register(id, entry);
|
|
231
|
+
});
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
return () => unregister(id);
|
|
234
|
+
}, [id, unregister]);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Returns a function that guards navigation.
|
|
238
|
+
* If unsaved changes exist, triggers the jiggle and blocks navigation.
|
|
239
|
+
* Otherwise navigates normally via the provided callback.
|
|
240
|
+
*
|
|
241
|
+
* @param navigate — Your navigation function (e.g., `router.push`)
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```tsx
|
|
245
|
+
* const router = useRouter();
|
|
246
|
+
* const guardedPush = useActionBarGuard(router.push);
|
|
247
|
+
* <button onClick={() => guardedPush('/settings')}>Settings</button>
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
function useActionBarGuard(navigate) {
|
|
251
|
+
const ctx = React.useContext(ActionBarContext);
|
|
252
|
+
return React.useCallback((href) => {
|
|
253
|
+
if (ctx?.hasDirty()) {
|
|
254
|
+
ctx.jiggle();
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
navigate(href);
|
|
258
|
+
return true;
|
|
259
|
+
}, [ctx, navigate]);
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
export { ActionBarProvider, useActionBar, useActionBarGuard };
|
|
263
|
+
|
|
264
|
+
//# sourceMappingURL=action-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-bar.js","names":[],"sources":["../src/action-bar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"./lib/utils\"\nimport { Button } from \"./button\"\nimport { Spinner } from \"./spinner\"\nimport { Kbd } from \"./kbd\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** State a form registers with the ActionBar. */\nexport interface ActionBarEntry {\n /** Whether this form has unsaved changes. */\n hasChanges: boolean\n /** Whether this form is currently saving. */\n saving: boolean\n /** Save this form's changes. */\n onSave: () => void | Promise<void>\n /** Discard this form's changes. */\n onReset: () => void\n}\n\ninterface ActionBarContextValue {\n register: (id: string, entry: ActionBarEntry) => void\n unregister: (id: string) => void\n /** Returns true if any registered form has unsaved changes. */\n hasDirty: () => boolean\n /** Trigger the jiggle effect on blocked navigation. */\n jiggle: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst JIGGLE_STEPS = 6\nconst JIGGLE_INTERVAL = 50\nconst JIGGLE_RANGE = 15\n\n/**\n * SSR-safe platform detection. Returns `false` on the server and on the\n * first client render so hydration matches, then resolves the real\n * platform after mount. Avoids the `⌘S`/`Ctrl+S` hydration mismatch\n * that a module-scope `navigator` read would cause.\n */\nfunction useIsMac(): boolean {\n const [isMac, setIsMac] = React.useState(false)\n React.useEffect(() => {\n setIsMac(/Mac|iPhone|iPad/.test(navigator.userAgent))\n }, [])\n return isMac\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ActionBarContext = React.createContext<ActionBarContextValue | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface ActionBarProviderProps {\n children: React.ReactNode\n /** Message when one form has changes. */\n message?: string\n /** Message template for multiple dirty forms. */\n pluralMessage?: (count: number) => string\n /**\n * Announced to assistive tech (and the only feedback reduced-motion\n * users get) when guarded navigation is blocked.\n */\n blockedMessage?: string\n /** Additional className for the bar's outer wrapper. */\n className?: string\n}\n\n/**\n * Global action bar for unsaved changes.\n *\n * Place once at the root layout. Forms register via `useActionBar()`.\n *\n * Features:\n * - Multi-form aggregation (Save All / Reset All)\n * - ⌘S / Ctrl+S keyboard shortcut\n * - Navigation guard with Discord-style page jiggle\n * - `beforeunload` guard for browser close/refresh\n *\n * @example\n * ```tsx\n * // Root layout — once\n * <ActionBarProvider>{children}</ActionBarProvider>\n *\n * // Any form component\n * useActionBar('profile', { hasChanges, saving, onSave, onReset });\n * ```\n */\nfunction ActionBarProvider({\n children,\n message = \"You have unsaved changes\",\n pluralMessage = (count) => `${count} unsaved changes`,\n blockedMessage = \"Save or reset your changes before leaving this page\",\n className,\n}: ActionBarProviderProps) {\n const entriesRef = React.useRef(new Map<string, ActionBarEntry>())\n const [, forceUpdate] = React.useState(0)\n const [jiggleTransform, setJiggleTransform] = React.useState<string | null>(\n null\n )\n const jiggleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null\n )\n const contentRef = React.useRef<HTMLDivElement>(null)\n const isMac = useIsMac()\n const prefersReducedMotion = useReducedMotion()\n const reducedMotionRef = React.useRef(prefersReducedMotion)\n reducedMotionRef.current = prefersReducedMotion\n // Assertive announcement for blocked navigation. This is the *only*\n // feedback channel that works for screen-reader and reduced-motion\n // users (the visual jiggle is suppressed for them), so it is driven\n // independently of the shake.\n const [blockedNotice, setBlockedNotice] = React.useState(\"\")\n\n // --- Registry ---\n\n const register = React.useCallback((id: string, entry: ActionBarEntry) => {\n const prev = entriesRef.current.get(id)\n entriesRef.current.set(id, entry)\n if (\n !prev ||\n prev.hasChanges !== entry.hasChanges ||\n prev.saving !== entry.saving\n ) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const unregister = React.useCallback((id: string) => {\n if (entriesRef.current.delete(id)) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const hasDirty = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) return true\n }\n return false\n }, [])\n\n // --- Jiggle ---\n\n const jiggle = React.useCallback(() => {\n // Announce to assistive tech on every blocked attempt. Clearing\n // first guarantees a text change so an identical message\n // re-announces on repeat presses.\n setBlockedNotice(\"\")\n requestAnimationFrame(() => setBlockedNotice(blockedMessage))\n\n // Vestibular safety: the random-translate shake is purely a motion\n // cue, so skip it under prefers-reduced-motion. The assertive\n // announcement above is the accessible equivalent.\n if (reducedMotionRef.current) return\n if (jiggleTimerRef.current) return\n\n let step = 0\n\n function tick() {\n step++\n if (step >= JIGGLE_STEPS) {\n setJiggleTransform(null)\n jiggleTimerRef.current = null\n return\n }\n const x = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n const y = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n setJiggleTransform(\n `translate3d(${x.toFixed(5)}px, ${y.toFixed(5)}px, 0px)`\n )\n jiggleTimerRef.current = setTimeout(tick, JIGGLE_INTERVAL)\n }\n\n tick()\n }, [blockedMessage])\n\n // --- Aggregated state ---\n\n const dirty: ActionBarEntry[] = []\n let anySaving = false\n\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) dirty.push(entry)\n if (entry.saving) anySaving = true\n }\n\n const showBar = dirty.length > 0 || anySaving\n const dirtyCount = dirty.length\n const displayMessage =\n dirtyCount > 1 ? pluralMessage(dirtyCount) : message\n\n // --- Save / Reset ---\n\n const handleSaveAll = React.useCallback(async () => {\n const saves = [...entriesRef.current.values()]\n .filter((e) => e.hasChanges)\n .map((e) => e.onSave())\n await Promise.all(saves)\n }, [])\n\n const handleResetAll = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) entry.onReset()\n }\n }, [])\n\n // --- ⌘S / Ctrl+S ---\n\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === \"s\") {\n e.preventDefault()\n if (hasDirty()) {\n handleSaveAll()\n }\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => document.removeEventListener(\"keydown\", handleKeyDown)\n }, [hasDirty, handleSaveAll])\n\n // --- beforeunload ---\n\n React.useEffect(() => {\n function handleBeforeUnload(e: BeforeUnloadEvent) {\n if (hasDirty()) {\n e.preventDefault()\n }\n }\n\n window.addEventListener(\"beforeunload\", handleBeforeUnload)\n return () => window.removeEventListener(\"beforeunload\", handleBeforeUnload)\n }, [hasDirty])\n\n // --- Context ---\n\n const ctx = React.useMemo(\n () => ({ register, unregister, hasDirty, jiggle }),\n [register, unregister, hasDirty, jiggle]\n )\n\n return (\n <ActionBarContext.Provider value={ctx}>\n <div\n data-slot=\"action-bar-shell\"\n ref={contentRef}\n style={jiggleTransform ? { transform: jiggleTransform } : undefined}\n >\n {children}\n </div>\n\n {/*\n Persistent SR-only live regions. They are always mounted so the\n announcement fires reliably when their text changes — a region\n mounted already-populated is not announced consistently across\n screen reader / browser combinations.\n */}\n <span data-slot=\"action-bar-status\" aria-live=\"polite\" className=\"sr-only\">\n {showBar ? displayMessage : \"\"}\n </span>\n <span\n data-slot=\"action-bar-blocked-status\"\n aria-live=\"assertive\"\n className=\"sr-only\"\n >\n {blockedNotice}\n </span>\n\n <AnimatePresence>\n {showBar && (\n <motion.div\n data-slot=\"action-bar\"\n initial={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n animate={{ opacity: 1, y: 0, x: \"-50%\" }}\n exit={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n transition={{ duration: 0.2, ease: \"easeOut\" }}\n className=\"fixed bottom-6 left-1/2 z-50\"\n >\n <div\n data-slot=\"action-bar-content\"\n className={cn(\n \"flex items-center gap-6 rounded-xl border border-border/60 bg-card px-5 py-2.5 shadow-lg ring-1 ring-black/5 backdrop-blur-sm dark:ring-white/5\",\n className\n )}\n >\n <span\n data-slot=\"action-bar-message\"\n className=\"whitespace-nowrap text-sm font-medium text-foreground\"\n >\n {displayMessage}\n </span>\n\n <div\n data-slot=\"action-bar-actions\"\n className=\"flex items-center gap-1.5\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n disabled={anySaving}\n onClick={handleResetAll}\n className=\"h-9 px-3 text-sm hover:text-destructive\"\n >\n Reset\n </Button>\n\n <Button\n type=\"button\"\n variant=\"default\"\n disabled={anySaving}\n onClick={handleSaveAll}\n className=\"h-9 px-4 text-sm\"\n >\n {anySaving ? (\n <>\n <Spinner className=\"mr-1.5 h-3.5 w-3.5\" />\n Saving\n </>\n ) : (\n <>\n Save\n <Kbd className=\"ml-1.5\">\n {isMac ? \"⌘S\" : \"Ctrl+S\"}\n </Kbd>\n </>\n )}\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </ActionBarContext.Provider>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Hook — register form state\n// ---------------------------------------------------------------------------\n\n/**\n * Register a form's unsaved state with the global ActionBar.\n *\n * Unregisters automatically on unmount.\n *\n * @param id — Unique key (e.g., 'profile', 'notifications')\n * @param entry — Current form state\n */\nfunction useActionBar(id: string, entry: ActionBarEntry) {\n const ctx = React.useContext(ActionBarContext)\n if (!ctx)\n throw new Error(\"useActionBar must be used within <ActionBarProvider>\")\n\n const { register, unregister } = ctx\n\n // Intentionally no dependency array: `entry` (and its `onSave`/\n // `onReset` closures) is rebuilt by the caller every render, so we\n // must re-register each render to keep the latest closures. The\n // `register` impl diffs the entry and only forces an update when\n // `hasChanges`/`saving` actually changed, so this can't loop.\n React.useEffect(() => {\n register(id, entry)\n })\n\n React.useEffect(() => {\n return () => unregister(id)\n }, [id, unregister])\n}\n\n// ---------------------------------------------------------------------------\n// Navigation guard hook\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a function that guards navigation.\n * If unsaved changes exist, triggers the jiggle and blocks navigation.\n * Otherwise navigates normally via the provided callback.\n *\n * @param navigate — Your navigation function (e.g., `router.push`)\n *\n * @example\n * ```tsx\n * const router = useRouter();\n * const guardedPush = useActionBarGuard(router.push);\n * <button onClick={() => guardedPush('/settings')}>Settings</button>\n * ```\n */\nfunction useActionBarGuard(navigate: (href: string) => void) {\n const ctx = React.useContext(ActionBarContext)\n\n return React.useCallback(\n (href: string) => {\n if (ctx?.hasDirty()) {\n ctx.jiggle()\n return false\n }\n navigate(href)\n return true\n },\n [ctx, navigate]\n )\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\nexport {\n ActionBarProvider,\n useActionBar,\n useActionBarGuard,\n}\n"],"mappings":"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"}
|