@techsio/ui-kit 0.41.2 → 0.42.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.
Files changed (40) hide show
  1. package/dist/internal/molecules/drawer.context.js +133 -0
  2. package/dist/internal/molecules/drawer.styles.js +107 -0
  3. package/dist/internal/organisms/sidebar.context.js +223 -0
  4. package/dist/internal/organisms/sidebar.focus.js +169 -0
  5. package/dist/internal/organisms/sidebar.styles.js +146 -0
  6. package/dist/molecules/drawer.js +262 -0
  7. package/dist/organisms/sidebar.js +448 -0
  8. package/dist/src/internal/molecules/drawer.context.d.ts +53 -0
  9. package/dist/src/internal/molecules/drawer.context.d.ts.map +1 -0
  10. package/dist/src/internal/molecules/drawer.styles.d.ts +115 -0
  11. package/dist/src/internal/molecules/drawer.styles.d.ts.map +1 -0
  12. package/dist/src/internal/organisms/sidebar.context.d.ts +83 -0
  13. package/dist/src/internal/organisms/sidebar.context.d.ts.map +1 -0
  14. package/dist/src/internal/organisms/sidebar.focus.d.ts +16 -0
  15. package/dist/src/internal/organisms/sidebar.focus.d.ts.map +1 -0
  16. package/dist/src/internal/organisms/sidebar.styles.d.ts +149 -0
  17. package/dist/src/internal/organisms/sidebar.styles.d.ts.map +1 -0
  18. package/dist/src/molecules/drawer.d.ts +112 -0
  19. package/dist/src/molecules/drawer.d.ts.map +1 -0
  20. package/dist/src/molecules/switch.d.ts +3 -3
  21. package/dist/src/molecules/toast.d.ts +6 -6
  22. package/dist/src/organisms/sidebar.d.ts +93 -0
  23. package/dist/src/organisms/sidebar.d.ts.map +1 -0
  24. package/dist/src/types/zag.d.ts +1 -0
  25. package/dist/src/types/zag.d.ts.map +1 -1
  26. package/dist/stories/changelog/changelog.stories.d.ts.map +1 -1
  27. package/dist/stories/molecules/drawer.stories.d.ts +158 -0
  28. package/dist/stories/molecules/drawer.stories.d.ts.map +1 -0
  29. package/dist/stories/organisms/sidebar.stories.d.ts +66 -0
  30. package/dist/stories/organisms/sidebar.stories.d.ts.map +1 -0
  31. package/dist/test/drawer.behavior.spec.d.ts +2 -0
  32. package/dist/test/drawer.behavior.spec.d.ts.map +1 -0
  33. package/dist/test/sidebar.behavior.spec.d.ts +2 -0
  34. package/dist/test/sidebar.behavior.spec.d.ts.map +1 -0
  35. package/dist/test/sidebar.ssr.bun.d.ts +2 -0
  36. package/dist/test/sidebar.ssr.bun.d.ts.map +1 -0
  37. package/package.json +25 -22
  38. package/src/tokens/components/components.css +2 -0
  39. package/src/tokens/components/molecules/_drawer.css +369 -0
  40. package/src/tokens/components/organisms/_sidebar.css +93 -0
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { normalizeProps, useMachine } from "@zag-js/react";
4
+ import { createContext, useContext, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { drawerVariants } from "./drawer.styles.js";
6
+ import * as __rspack_external__zag_js_drawer_a300f837 from "@zag-js/drawer";
7
+ import * as __rspack_external__zag_js_presence_229d2f05 from "@zag-js/presence";
8
+ const DrawerContext = /*#__PURE__*/ createContext(null);
9
+ const DrawerStackStoreContext = /*#__PURE__*/ createContext(void 0);
10
+ const DrawerStackContext = /*#__PURE__*/ createContext(null);
11
+ function useDrawer(props = {}) {
12
+ const generatedId = useId();
13
+ const inheritedStack = useContext(DrawerStackStoreContext);
14
+ const { defaultSnapPoint, id = generatedId, stack = inheritedStack, ...machineProps } = props;
15
+ const machineOptions = {
16
+ id,
17
+ stack,
18
+ ...machineProps
19
+ };
20
+ if ("defaultSnapPoint" in props) Object.assign(machineOptions, {
21
+ defaultSnapPoint
22
+ });
23
+ const service = useMachine(__rspack_external__zag_js_drawer_a300f837.machine, machineOptions);
24
+ return __rspack_external__zag_js_drawer_a300f837.connect(service, normalizeProps);
25
+ }
26
+ function useDrawerContext() {
27
+ const context = useContext(DrawerContext);
28
+ if (!context) throw new Error("Drawer components must be used within Drawer.Root");
29
+ return context;
30
+ }
31
+ function useDrawerStackContext() {
32
+ const context = useContext(DrawerStackContext);
33
+ if (!context) throw new Error("Drawer stack components must be used within Drawer.Stack");
34
+ return context;
35
+ }
36
+ function useDrawerPresence({ immediate, lazyMount = true, onEnterComplete, onExitComplete, present, skipAnimationOnMount = false, unmountOnExit = true }) {
37
+ const wasEverPresent = useRef(false);
38
+ const service = useMachine(__rspack_external__zag_js_presence_229d2f05.machine, {
39
+ immediate,
40
+ onEnterComplete,
41
+ onExitComplete,
42
+ present
43
+ });
44
+ const api = __rspack_external__zag_js_presence_229d2f05.connect(service, normalizeProps);
45
+ const latestSetNode = useRef(api.setNode);
46
+ latestSetNode.current = api.setNode;
47
+ const presenceRef = useRef((node)=>{
48
+ latestSetNode.current(node);
49
+ return ()=>{
50
+ latestSetNode.current(null);
51
+ };
52
+ }).current;
53
+ if (api.present) wasEverPresent.current = true;
54
+ const unmounted = !(api.present || wasEverPresent.current) && lazyMount || !api.present && wasEverPresent.current && unmountOnExit;
55
+ return {
56
+ api,
57
+ ref: presenceRef,
58
+ skipAnimationOnMount,
59
+ unmounted
60
+ };
61
+ }
62
+ function DrawerProvider({ api, children, immediate, lazyMount = true, onEnterComplete, onExitComplete, size, skipAnimationOnMount = false, unmountOnExit = true }) {
63
+ const presenceOptions = {
64
+ immediate,
65
+ lazyMount,
66
+ skipAnimationOnMount,
67
+ unmountOnExit
68
+ };
69
+ const contentPresence = useDrawerPresence({
70
+ ...presenceOptions,
71
+ onEnterComplete,
72
+ onExitComplete,
73
+ present: api.open
74
+ });
75
+ const styles = drawerVariants({
76
+ size
77
+ });
78
+ return /*#__PURE__*/ jsx(DrawerContext.Provider, {
79
+ value: {
80
+ api,
81
+ presence: contentPresence,
82
+ presenceOptions,
83
+ styles
84
+ },
85
+ children: children
86
+ });
87
+ }
88
+ function getDrawerPresenceProps(open, presenceState) {
89
+ let dataState = null;
90
+ if (!(presenceState.api.skip && presenceState.skipAnimationOnMount)) dataState = open ? "open" : "closed";
91
+ return {
92
+ "data-state": dataState,
93
+ hidden: !presenceState.api.present
94
+ };
95
+ }
96
+ function composeRefs(...refs) {
97
+ return (node)=>{
98
+ const cleanups = [];
99
+ for (const ref of refs)if ("function" == typeof ref) {
100
+ const cleanup = ref(node);
101
+ cleanups.push("function" == typeof cleanup ? cleanup : ()=>ref(null));
102
+ } else if (ref) {
103
+ ref.current = node;
104
+ cleanups.push(()=>{
105
+ ref.current = null;
106
+ });
107
+ }
108
+ return ()=>{
109
+ for (const cleanup of cleanups)cleanup();
110
+ };
111
+ };
112
+ }
113
+ function useComposedRefs(firstRef, secondRef) {
114
+ return useMemo(()=>composeRefs(firstRef, secondRef), [
115
+ firstRef,
116
+ secondRef
117
+ ]);
118
+ }
119
+ function DrawerStackProvider({ children }) {
120
+ const [stack] = useState(()=>__rspack_external__zag_js_drawer_a300f837.createStack());
121
+ const snapshot = useSyncExternalStore(stack.subscribe, stack.getSnapshot, stack.getSnapshot);
122
+ const api = useMemo(()=>__rspack_external__zag_js_drawer_a300f837.connectStack(snapshot, normalizeProps), [
123
+ snapshot
124
+ ]);
125
+ return /*#__PURE__*/ jsx(DrawerStackStoreContext.Provider, {
126
+ value: stack,
127
+ children: /*#__PURE__*/ jsx(DrawerStackContext.Provider, {
128
+ value: api,
129
+ children: children
130
+ })
131
+ });
132
+ }
133
+ export { DrawerProvider, DrawerStackProvider, getDrawerPresenceProps, useComposedRefs, useDrawer, useDrawerContext, useDrawerPresence, useDrawerStackContext };
@@ -0,0 +1,107 @@
1
+ import { tv } from "../../utils.js";
2
+ const drawerVariants = tv({
3
+ slots: {
4
+ backdrop: [
5
+ "drawer-backdrop-layer fixed inset-0 bg-drawer-backdrop-bg",
6
+ "drawer-backdrop-motion"
7
+ ],
8
+ positioner: [
9
+ "drawer-physical-axis drawer-positioner-layer pointer-events-none fixed inset-0 flex",
10
+ "data-[swipe-direction=up]:items-start",
11
+ "data-[swipe-direction=down]:items-end",
12
+ "data-[swipe-direction=left]:justify-start",
13
+ "data-[swipe-direction=right]:justify-end"
14
+ ],
15
+ content: [
16
+ "drawer-content-layer drawer-content-bleed pointer-events-auto relative flex max-h-full max-w-full flex-col",
17
+ "bg-drawer-content-bg text-drawer-content-fg shadow-drawer-content",
18
+ "border-(length:--border-width-drawer) border-drawer-content-border",
19
+ "focus-visible:drawer-focus-ring",
20
+ "drawer-content-motion",
21
+ "data-[swipe-direction=up]:drawer-content-top data-[swipe-direction=up]:w-full data-[swipe-direction=up]:border-t-0",
22
+ "data-[swipe-direction=down]:drawer-content-bottom data-[swipe-direction=down]:w-full data-[swipe-direction=down]:border-b-0",
23
+ "data-[swipe-direction=left]:drawer-content-left data-[swipe-direction=left]:h-full data-[swipe-direction=left]:border-l-0",
24
+ "data-[swipe-direction=right]:drawer-content-right data-[swipe-direction=right]:h-full data-[swipe-direction=right]:border-r-0"
25
+ ],
26
+ header: [
27
+ "flex shrink-0 flex-col gap-drawer-header p-drawer-header",
28
+ "border-b-(length:--border-width-drawer) border-drawer-content-border"
29
+ ],
30
+ body: "flex min-h-0 flex-1 flex-col overflow-y-auto p-drawer-body",
31
+ footer: [
32
+ "flex shrink-0 items-center justify-end gap-drawer-footer p-drawer-footer",
33
+ "border-t-(length:--border-width-drawer) border-drawer-content-border"
34
+ ],
35
+ title: "drawer-title-typography font-drawer-title text-drawer-title-fg",
36
+ description: "drawer-description-typography text-drawer-description-fg",
37
+ trigger: "",
38
+ closeTrigger: "",
39
+ grabber: [
40
+ "group absolute flex items-center justify-center p-drawer-grabber",
41
+ "data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:bottom-0",
42
+ "data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:top-0",
43
+ "data-[swipe-direction=start]:inset-y-0 data-[swipe-direction=start]:end-0",
44
+ "data-[swipe-direction=end]:inset-y-0 data-[swipe-direction=end]:start-0"
45
+ ],
46
+ grabberIndicator: [
47
+ "rounded-drawer-grabber bg-drawer-grabber-bg",
48
+ "group-data-[swipe-direction=up]:h-drawer-grabber-thickness group-data-[swipe-direction=up]:w-drawer-grabber-length",
49
+ "group-data-[swipe-direction=down]:h-drawer-grabber-thickness group-data-[swipe-direction=down]:w-drawer-grabber-length",
50
+ "group-data-[swipe-direction=start]:h-drawer-grabber-length group-data-[swipe-direction=start]:w-drawer-grabber-thickness",
51
+ "group-data-[swipe-direction=end]:h-drawer-grabber-length group-data-[swipe-direction=end]:w-drawer-grabber-thickness"
52
+ ],
53
+ swipeArea: [
54
+ "drawer-swipe-area-layer fixed",
55
+ "data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:bottom-0 data-[swipe-direction=up]:h-drawer-swipe-area",
56
+ "data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:top-0 data-[swipe-direction=down]:h-drawer-swipe-area",
57
+ "data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:right-0 data-[swipe-direction=left]:w-drawer-swipe-area",
58
+ "data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:left-0 data-[swipe-direction=right]:w-drawer-swipe-area"
59
+ ],
60
+ indent: "drawer-indent",
61
+ indentBackground: "drawer-indent-background"
62
+ },
63
+ variants: {
64
+ size: {
65
+ xs: {
66
+ content: [
67
+ "data-[swipe-direction=down]:h-drawer-xs data-[swipe-direction=up]:h-drawer-xs",
68
+ "data-[swipe-direction=left]:w-drawer-xs data-[swipe-direction=right]:w-drawer-xs"
69
+ ]
70
+ },
71
+ sm: {
72
+ content: [
73
+ "data-[swipe-direction=down]:h-drawer-sm data-[swipe-direction=up]:h-drawer-sm",
74
+ "data-[swipe-direction=left]:w-drawer-sm data-[swipe-direction=right]:w-drawer-sm"
75
+ ]
76
+ },
77
+ md: {
78
+ content: [
79
+ "data-[swipe-direction=down]:h-drawer-md data-[swipe-direction=up]:h-drawer-md",
80
+ "data-[swipe-direction=left]:w-drawer-md data-[swipe-direction=right]:w-drawer-md"
81
+ ]
82
+ },
83
+ lg: {
84
+ content: [
85
+ "data-[swipe-direction=down]:h-drawer-lg data-[swipe-direction=up]:h-drawer-lg",
86
+ "data-[swipe-direction=left]:w-drawer-lg data-[swipe-direction=right]:w-drawer-lg"
87
+ ]
88
+ },
89
+ xl: {
90
+ content: [
91
+ "data-[swipe-direction=down]:h-drawer-xl data-[swipe-direction=up]:h-drawer-xl",
92
+ "data-[swipe-direction=left]:w-drawer-xl data-[swipe-direction=right]:w-drawer-xl"
93
+ ]
94
+ },
95
+ full: {
96
+ content: [
97
+ "data-[swipe-direction=down]:h-full data-[swipe-direction=up]:h-full",
98
+ "data-[swipe-direction=left]:w-full data-[swipe-direction=right]:w-full"
99
+ ]
100
+ }
101
+ }
102
+ },
103
+ defaultVariants: {
104
+ size: "md"
105
+ }
106
+ });
107
+ export { drawerVariants };
@@ -0,0 +1,223 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
4
+ import { useDrawer } from "../../molecules/drawer.js";
5
+ const SidebarContext = /*#__PURE__*/ createContext(null);
6
+ const SidebarPanelContext = /*#__PURE__*/ createContext(null);
7
+ const SIDES = [
8
+ "start",
9
+ "end"
10
+ ];
11
+ function createSidebarFocusRegistry() {
12
+ const origins = {
13
+ end: null,
14
+ start: null
15
+ };
16
+ const revisions = {
17
+ end: 0,
18
+ start: 0
19
+ };
20
+ const clear = (side, revision)=>{
21
+ const origin = origins[side];
22
+ if (origin && (void 0 === revision || origin.revision === revision)) origins[side] = null;
23
+ };
24
+ return {
25
+ blur: ({ mode, node, relatedTarget, side })=>{
26
+ if (relatedTarget && node.contains(relatedTarget)) return;
27
+ const document = node.ownerDocument;
28
+ const clearBlurredOrigin = ()=>{
29
+ const origin = origins[side];
30
+ if (!origin || origin.mode !== mode || origin.node !== node) return;
31
+ if (node.contains(document.activeElement)) return;
32
+ if (node.isConnected && node.getClientRects().length > 0) return void clear(side, origin.revision);
33
+ const expireLease = ()=>{
34
+ const leasedOrigin = origins[side];
35
+ if (leasedOrigin?.revision === origin.revision && leasedOrigin.mode === mode && (!node.isConnected || 0 === node.getClientRects().length)) origins[side] = null;
36
+ };
37
+ const view = document.defaultView;
38
+ if (view) view.requestAnimationFrame(expireLease);
39
+ else globalThis.queueMicrotask(expireLease);
40
+ };
41
+ if (relatedTarget) return void clearBlurredOrigin();
42
+ const view = document.defaultView;
43
+ if (view) view.queueMicrotask(clearBlurredOrigin);
44
+ else globalThis.queueMicrotask(clearBlurredOrigin);
45
+ },
46
+ capture: ({ mode, node, side, triggerValue })=>{
47
+ revisions[side] += 1;
48
+ origins[side] = {
49
+ document: node.ownerDocument,
50
+ mode,
51
+ node,
52
+ revision: revisions[side],
53
+ side,
54
+ triggerValue
55
+ };
56
+ },
57
+ claim: (side, fromMode)=>{
58
+ const origin = origins[side];
59
+ if (origin?.mode !== fromMode) return null;
60
+ origins[side] = null;
61
+ return origin;
62
+ },
63
+ clear,
64
+ getRevision: (side)=>revisions[side],
65
+ peek: (side)=>origins[side]
66
+ };
67
+ }
68
+ function getDesktopServerSnapshot() {
69
+ return true;
70
+ }
71
+ function createSidebarBreakpointStore() {
72
+ let node = null;
73
+ return {
74
+ getSnapshot: ()=>node ? "none" !== getComputedStyle(node).display : true,
75
+ ref: (nextNode)=>{
76
+ node = nextNode;
77
+ },
78
+ subscribe: (listener)=>{
79
+ const target = node?.ownerDocument.defaultView;
80
+ let subscribed = true;
81
+ target?.addEventListener("resize", listener);
82
+ target?.queueMicrotask(()=>{
83
+ if (subscribed) listener();
84
+ });
85
+ return ()=>{
86
+ subscribed = false;
87
+ target?.removeEventListener("resize", listener);
88
+ };
89
+ }
90
+ };
91
+ }
92
+ function useSidebarDesktopBreakpoint() {
93
+ const [store] = useState(createSidebarBreakpointStore);
94
+ const isDesktop = useSyncExternalStore(store.subscribe, store.getSnapshot, getDesktopServerSnapshot);
95
+ return {
96
+ breakpointRef: store.ref,
97
+ isDesktop
98
+ };
99
+ }
100
+ function normalizeExpanded(sides) {
101
+ return SIDES.filter((side)=>sides.includes(side));
102
+ }
103
+ function normalizeCollapsible(policy) {
104
+ return {
105
+ start: policy?.start ?? "icon",
106
+ end: policy?.end ?? "icon"
107
+ };
108
+ }
109
+ function useSidebarState({ collapsible: collapsiblePolicy, defaultExpanded = SIDES, defaultMobileOpen = null, dir, expanded: controlledExpanded, id, mobileOpen: controlledMobileOpen, onExpandedChange, onMobileOpenChange } = {}) {
110
+ const generatedId = useId();
111
+ const rootId = id ?? generatedId;
112
+ const resolvedDir = dir ?? "ltr";
113
+ const collapsible = normalizeCollapsible(collapsiblePolicy);
114
+ const [focus] = useState(createSidebarFocusRegistry);
115
+ const { breakpointRef, isDesktop } = useSidebarDesktopBreakpoint();
116
+ const wasDesktop = useRef(isDesktop);
117
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState(()=>normalizeExpanded(defaultExpanded));
118
+ const [uncontrolledMobileOpen, setUncontrolledMobileOpen] = useState(defaultMobileOpen);
119
+ const expanded = normalizeExpanded(controlledExpanded ?? uncontrolledExpanded);
120
+ const effectiveExpanded = normalizeExpanded([
121
+ ...expanded,
122
+ ...SIDES.filter((side)=>"none" === collapsible[side])
123
+ ]);
124
+ const mobileOpen = void 0 === controlledMobileOpen ? uncontrolledMobileOpen : controlledMobileOpen;
125
+ const changeMobileOpen = (open)=>{
126
+ if (void 0 === controlledMobileOpen) setUncontrolledMobileOpen(open);
127
+ if (open !== mobileOpen) onMobileOpenChange?.({
128
+ open
129
+ });
130
+ };
131
+ const setExpanded = (side, nextExpanded)=>{
132
+ if ("none" === collapsible[side]) return;
133
+ const next = normalizeExpanded(nextExpanded ? [
134
+ ...expanded,
135
+ side
136
+ ] : expanded.filter((candidate)=>candidate !== side));
137
+ if (void 0 === controlledExpanded) setUncontrolledExpanded(next);
138
+ if (next.length !== expanded.length) onExpandedChange?.({
139
+ expanded: [
140
+ ...next
141
+ ]
142
+ });
143
+ };
144
+ useLayoutEffect(()=>{
145
+ if (isDesktop && !wasDesktop.current && null !== mobileOpen) {
146
+ if (void 0 === controlledMobileOpen) setUncontrolledMobileOpen(null);
147
+ onMobileOpenChange?.({
148
+ open: null
149
+ });
150
+ }
151
+ wasDesktop.current = isDesktop;
152
+ }, [
153
+ controlledMobileOpen,
154
+ isDesktop,
155
+ mobileOpen,
156
+ onMobileOpenChange
157
+ ]);
158
+ const createOpenChangeHandler = (side)=>({ open })=>{
159
+ if (open) changeMobileOpen(side);
160
+ else if (mobileOpen === side) changeMobileOpen(null);
161
+ };
162
+ const startDrawer = useDrawer({
163
+ dir: resolvedDir,
164
+ id: `${rootId}-start-drawer`,
165
+ onOpenChange: createOpenChangeHandler("start"),
166
+ open: !isDesktop && "start" === mobileOpen,
167
+ swipeDirection: "start"
168
+ });
169
+ const endDrawer = useDrawer({
170
+ dir: resolvedDir,
171
+ id: `${rootId}-end-drawer`,
172
+ onOpenChange: createOpenChangeHandler("end"),
173
+ open: !isDesktop && "end" === mobileOpen,
174
+ swipeDirection: "end"
175
+ });
176
+ return {
177
+ breakpointRef,
178
+ closeMobile: ()=>changeMobileOpen(null),
179
+ dir: resolvedDir,
180
+ drawer: {
181
+ start: startDrawer,
182
+ end: endDrawer
183
+ },
184
+ expanded: effectiveExpanded,
185
+ focus,
186
+ getCollapsible: (side)=>collapsible[side],
187
+ getPanelId: (side)=>`${rootId}-${side}-panel`,
188
+ isDesktop,
189
+ isExpanded: (side)=>effectiveExpanded.includes(side),
190
+ isFixed: (side)=>"none" === collapsible[side],
191
+ mobileOpen,
192
+ openMobile: (side)=>changeMobileOpen(side),
193
+ rootId,
194
+ setExpanded,
195
+ toggle: (side)=>{
196
+ if (isDesktop) setExpanded(side, !expanded.includes(side));
197
+ else changeMobileOpen(mobileOpen === side ? null : side);
198
+ }
199
+ };
200
+ }
201
+ function SidebarProvider({ children, value }) {
202
+ return /*#__PURE__*/ jsx(SidebarContext.Provider, {
203
+ value: value,
204
+ children: children
205
+ });
206
+ }
207
+ function SidebarPanelProvider({ children, value }) {
208
+ return /*#__PURE__*/ jsx(SidebarPanelContext.Provider, {
209
+ value: value,
210
+ children: children
211
+ });
212
+ }
213
+ function useSidebarContext() {
214
+ const context = useContext(SidebarContext);
215
+ if (!context) throw new Error("Sidebar components must be used within Sidebar.Root");
216
+ return context;
217
+ }
218
+ function useSidebarPanelContext() {
219
+ const context = useContext(SidebarPanelContext);
220
+ if (!context) throw new Error("Sidebar panel parts must be used within Sidebar.Panel");
221
+ return context;
222
+ }
223
+ export { SidebarPanelProvider, SidebarProvider, useSidebarContext, useSidebarPanelContext, useSidebarState };
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ import { useLayoutEffect, useRef } from "react";
3
+ function getPanelFocusRegion(document, panelId, isDesktop) {
4
+ const panel = document.getElementById(panelId);
5
+ if (isDesktop || !panel) return panel;
6
+ return panel.closest('[data-scope="drawer"][data-part="content"]') ?? panel;
7
+ }
8
+ function getPanelFocusTarget({ document, panelId, rootId, side, triggerValue }) {
9
+ const root = document.getElementById(rootId);
10
+ const triggers = root ? Array.from(root.querySelectorAll("button[data-sidebar-trigger]")).filter((candidate)=>candidate.closest('[data-scope="sidebar"][data-part="root"]') === root && candidate.dataset.side === side && !candidate.disabled && candidate.getClientRects().length > 0) : [];
11
+ const exactTrigger = triggerValue ? triggers.find((candidate)=>candidate.dataset.sidebarTriggerValue === triggerValue) : void 0;
12
+ const target = exactTrigger ?? triggers[0];
13
+ if (target) return target;
14
+ const panel = document.getElementById(panelId);
15
+ return panel && !panel.inert && panel.getClientRects().length > 0 ? panel : null;
16
+ }
17
+ function hasMeaningfulFocus(document) {
18
+ const activeElement = document.activeElement;
19
+ return Boolean(activeElement && activeElement !== document.body && activeElement !== document.documentElement && activeElement.isConnected);
20
+ }
21
+ function queueDocumentMicrotask(document, callback) {
22
+ const view = document.defaultView;
23
+ if (view) return void view.queueMicrotask(callback);
24
+ globalThis.queueMicrotask(callback);
25
+ }
26
+ function captureActiveFocus({ activeElement, focus, focusRegion, mode, origin, side, triggerValue }) {
27
+ if (activeElement && focusRegion?.contains(activeElement)) {
28
+ focus.capture({
29
+ mode,
30
+ node: focusRegion,
31
+ side,
32
+ triggerValue
33
+ });
34
+ return true;
35
+ }
36
+ if (activeElement && origin.node.contains(activeElement)) {
37
+ focus.capture({
38
+ mode,
39
+ node: origin.node,
40
+ side,
41
+ triggerValue: origin.triggerValue
42
+ });
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ function captureTransferredTarget({ document, focus, mode, origin, side, target, triggerValue }) {
48
+ if (!target || document.activeElement !== target) return;
49
+ const capturedOrigin = focus.peek(side);
50
+ if (capturedOrigin?.mode === mode && capturedOrigin.node === target) return;
51
+ focus.capture({
52
+ mode,
53
+ node: target,
54
+ side,
55
+ triggerValue: target.dataset.sidebarTriggerValue ?? origin.triggerValue ?? triggerValue
56
+ });
57
+ }
58
+ function runQueuedFocusTransfer({ document, focus, isCurrent, isDesktop, mode, origin, panelId, rootId, side, triggerValue }) {
59
+ const stale = !isCurrent() || focus.getRevision(side) !== origin.revision || !document.hasFocus();
60
+ if (stale) return;
61
+ const focusRegion = getPanelFocusRegion(document, panelId, isDesktop);
62
+ const activeElement = document.activeElement;
63
+ if (activeElement && focusRegion?.contains(activeElement)) return;
64
+ if (hasMeaningfulFocus(document)) return;
65
+ const target = getPanelFocusTarget({
66
+ document,
67
+ panelId,
68
+ rootId,
69
+ side,
70
+ triggerValue
71
+ });
72
+ target?.focus({
73
+ preventScroll: true
74
+ });
75
+ captureTransferredTarget({
76
+ document,
77
+ focus,
78
+ mode,
79
+ origin,
80
+ side,
81
+ target,
82
+ triggerValue
83
+ });
84
+ }
85
+ function cancelFocusTransfer(transition, transitionId) {
86
+ if (transition.current === transitionId) transition.current += 1;
87
+ }
88
+ function getPendingFocusOrigin(focus, side, fromMode) {
89
+ const origin = focus.peek(side);
90
+ return origin?.mode === fromMode ? origin : null;
91
+ }
92
+ function useSidebarPanelFocusTransfer({ focus, isDesktop, panelId, rootId, side, triggerValue }) {
93
+ const wasDesktop = useRef(isDesktop);
94
+ const transition = useRef(0);
95
+ const latestTriggerValue = useRef(triggerValue);
96
+ useLayoutEffect(()=>{
97
+ latestTriggerValue.current = triggerValue;
98
+ }, [
99
+ triggerValue
100
+ ]);
101
+ useLayoutEffect(()=>{
102
+ if (wasDesktop.current === isDesktop) return;
103
+ wasDesktop.current = isDesktop;
104
+ const transitionId = transition.current + 1;
105
+ transition.current = transitionId;
106
+ const mode = isDesktop ? "desktop" : "mobile";
107
+ const fromMode = isDesktop ? "mobile" : "desktop";
108
+ const pendingOrigin = getPendingFocusOrigin(focus, side, fromMode);
109
+ if (!pendingOrigin) return;
110
+ const { document } = pendingOrigin;
111
+ const focusRegion = getPanelFocusRegion(document, panelId, isDesktop);
112
+ const activeElement = document.activeElement;
113
+ const nextTriggerValue = pendingOrigin.triggerValue ?? latestTriggerValue.current;
114
+ if (captureActiveFocus({
115
+ activeElement,
116
+ focus,
117
+ focusRegion,
118
+ mode,
119
+ origin: pendingOrigin,
120
+ side,
121
+ triggerValue: nextTriggerValue
122
+ })) return;
123
+ if (hasMeaningfulFocus(document)) return void focus.clear(side, pendingOrigin.revision);
124
+ const origin = focus.claim(side, fromMode);
125
+ if (!origin) return;
126
+ queueDocumentMicrotask(document, ()=>{
127
+ runQueuedFocusTransfer({
128
+ document,
129
+ focus,
130
+ isCurrent: ()=>transition.current === transitionId,
131
+ isDesktop,
132
+ mode,
133
+ origin,
134
+ panelId,
135
+ rootId,
136
+ side,
137
+ triggerValue: origin.triggerValue ?? latestTriggerValue.current
138
+ });
139
+ });
140
+ return ()=>cancelFocusTransfer(transition, transitionId);
141
+ }, [
142
+ focus,
143
+ isDesktop,
144
+ panelId,
145
+ rootId,
146
+ side
147
+ ]);
148
+ const onFocusCapture = (event)=>{
149
+ focus.capture({
150
+ mode: isDesktop ? "desktop" : "mobile",
151
+ node: event.currentTarget,
152
+ side,
153
+ triggerValue: null
154
+ });
155
+ };
156
+ const onBlurCapture = (event)=>{
157
+ focus.blur({
158
+ mode: isDesktop ? "desktop" : "mobile",
159
+ node: event.currentTarget,
160
+ relatedTarget: event.relatedTarget,
161
+ side
162
+ });
163
+ };
164
+ return {
165
+ onBlurCapture,
166
+ onFocusCapture
167
+ };
168
+ }
169
+ export { useSidebarPanelFocusTransfer };