beez-ui 0.9.1 → 0.11.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.
@@ -2,3 +2,5 @@
2
2
  export declare function useSidebarPersistence(storageKey?: string): boolean | null;
3
3
  /** Stores an optional preference while retaining usable navigation in private mode. */
4
4
  export declare function saveSidebarPreference(storageKey: string | undefined, open: boolean): void;
5
+ /** Writes the cookie that server entrypoints read to render the sidebar in its last state. */
6
+ export declare function saveSidebarCookie(open: boolean, cookieName?: string, maxAgeSeconds?: number): void;
@@ -2,7 +2,7 @@
2
2
  import { c as _c } from "react/compiler-runtime";
3
3
  /** Keeps optional browser persistence separate from the sidebar rendering contract. */
4
4
  import { useSyncExternalStore } from "react";
5
- import { SIDEBAR_COOKIE_COLLAPSED_VALUE, SIDEBAR_COOKIE_OPEN_VALUE } from "../constants/sidebar.js";
5
+ import { SIDEBAR_COOKIE_COLLAPSED_VALUE, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_COOKIE_OPEN_VALUE } from "../constants/sidebar.js";
6
6
  /** Reads a preference without making unavailable browser storage a render failure. */
7
7
  function readSidebarPreference(storageKey) {
8
8
  if (!storageKey || typeof window === "undefined") return null;
@@ -43,3 +43,10 @@ export function saveSidebarPreference(storageKey, open) {
43
43
  window.localStorage.setItem(storageKey, open ? SIDEBAR_COOKIE_OPEN_VALUE : SIDEBAR_COOKIE_COLLAPSED_VALUE);
44
44
  } catch {}
45
45
  }
46
+ /** Writes the cookie that server entrypoints read to render the sidebar in its last state. */
47
+ export function saveSidebarCookie(open, cookieName = SIDEBAR_COOKIE_NAME, maxAgeSeconds = SIDEBAR_COOKIE_MAX_AGE) {
48
+ const value = open ? SIDEBAR_COOKIE_OPEN_VALUE : SIDEBAR_COOKIE_COLLAPSED_VALUE;
49
+ try {
50
+ document.cookie = `${cookieName}=${value}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax`;
51
+ } catch {}
52
+ }
@@ -35,19 +35,6 @@ export declare const GLIDE_PRESETS: {
35
35
  readonly mass: 1.2;
36
36
  };
37
37
  };
38
- sidebar: {
39
- itemSelector: string;
40
- containerSelector: string;
41
- attributes: string[];
42
- isActive: (item: Element) => boolean;
43
- surface: () => "background";
44
- spring: {
45
- readonly type: "spring";
46
- readonly stiffness: 360;
47
- readonly damping: 32;
48
- readonly mass: 0.6;
49
- };
50
- };
51
38
  pagination: {
52
39
  itemSelector: string;
53
40
  containerSelector: string;
@@ -14,14 +14,6 @@ export const GLIDE_PRESETS = {
14
14
  surface: (container) => container.getAttribute("data-variant") === "line" ? "after" : "background",
15
15
  spring: SPRING_TABS
16
16
  },
17
- sidebar: {
18
- itemSelector: "[data-slot=\"sidebar-menu-button\"]",
19
- containerSelector: "[data-slot=\"sidebar-menu\"]",
20
- attributes: ["data-active"],
21
- isActive: (item) => item.getAttribute("data-active") === "true",
22
- surface: () => "background",
23
- spring: SPRING_LAYOUT
24
- },
25
17
  pagination: {
26
18
  itemSelector: "[data-slot=\"pagination-link\"]",
27
19
  containerSelector: "[data-slot=\"pagination-content\"]",
@@ -248,7 +240,7 @@ export function attachGlideIndicator(container, preset) {
248
240
  });
249
241
  }
250
242
  const observer = new MutationObserver(() => {
251
- // Items can be remounted (a sidebar button wrapped in a tooltip after hydration); the
243
+ // Items can be remounted (a button wrapped in a tooltip trigger after hydration); the
252
244
  // highlight then belongs to the item that replaced the detached one.
253
245
  if (lastActive && !lastActive.isConnected) {
254
246
  lastActive = items().find(preset.isActive) ?? null;
@@ -0,0 +1,16 @@
1
+ import { type ComponentProps } from "react";
2
+ export type SharedLayoutBgProps = ComponentProps<"ul"> & {
3
+ /** Classes of the moving pill. */
4
+ pillClassName?: string;
5
+ /** Positioning override for the pill wrapper inside each item. */
6
+ pillContainerClassName?: string;
7
+ /** Horizontal distance, in pixels, the pill extends past each row. */
8
+ inset?: number;
9
+ };
10
+ /**
11
+ * Renders a list whose items share one hover pill that glides between them.
12
+ * Each child element receives the pill behind its content, so children should be list items.
13
+ * @param props - List attributes, pill styling and the items to decorate.
14
+ * @returns The decorated list.
15
+ */
16
+ export declare function SharedLayoutBg({ children, className, onMouseLeave, pillClassName, pillContainerClassName, inset, ...props }: SharedLayoutBgProps): import("react").JSX.Element;
@@ -0,0 +1,181 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /** Follows the hovered item of a list with one shared background pill, as in beui.dev. */
4
+ import { AnimatePresence, motion } from "motion/react";
5
+ import { Children, cloneElement, isValidElement, useId, useState } from "react";
6
+ import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
7
+ import { cn } from "../lib/utils.js";
8
+ import { MOTION_REVEAL_BLUR_PX, SPRING_LAYOUT } from "./tokens.js";
9
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
10
+ /** Horizontal distance, in pixels, the pill extends past each row by default. */
11
+ const DEFAULT_PILL_INSET_PX = 20;
12
+ const PILL_BLUR = `blur(${MOTION_REVEAL_BLUR_PX}px)`;
13
+ /** The pill fades in blurred, and only blurs out when the pointer leaves the whole list. */
14
+ const PILL_VARIANTS = {
15
+ initial: {
16
+ opacity: 0,
17
+ filter: PILL_BLUR
18
+ },
19
+ animate: {
20
+ opacity: 1,
21
+ filter: "blur(0px)"
22
+ },
23
+ exit: (isHovering) => isHovering ? {} : {
24
+ opacity: 0,
25
+ filter: PILL_BLUR
26
+ }
27
+ };
28
+ /** With reduced motion the pill appears and leaves at once, like other pointer highlights. */
29
+ const REDUCED_PILL_VARIANTS = {
30
+ initial: { opacity: 0 },
31
+ animate: {
32
+ opacity: 1,
33
+ transition: { duration: 0 }
34
+ },
35
+ exit: (isHovering) => isHovering ? {} : {
36
+ opacity: 0,
37
+ transition: { duration: 0 }
38
+ }
39
+ };
40
+ /**
41
+ * Renders a list whose items share one hover pill that glides between them.
42
+ * Each child element receives the pill behind its content, so children should be list items.
43
+ * @param props - List attributes, pill styling and the items to decorate.
44
+ * @returns The decorated list.
45
+ */
46
+ export function SharedLayoutBg(t0) {
47
+ const $ = _c(34);
48
+ let children;
49
+ let className;
50
+ let onMouseLeave;
51
+ let pillClassName;
52
+ let pillContainerClassName;
53
+ let props;
54
+ let t1;
55
+ if ($[0] !== t0) {
56
+ ({children, className, onMouseLeave, pillClassName, pillContainerClassName, inset: t1, ...props} = t0);
57
+ $[0] = t0;
58
+ $[1] = children;
59
+ $[2] = className;
60
+ $[3] = onMouseLeave;
61
+ $[4] = pillClassName;
62
+ $[5] = pillContainerClassName;
63
+ $[6] = props;
64
+ $[7] = t1;
65
+ } else {
66
+ children = $[1];
67
+ className = $[2];
68
+ onMouseLeave = $[3];
69
+ pillClassName = $[4];
70
+ pillContainerClassName = $[5];
71
+ props = $[6];
72
+ t1 = $[7];
73
+ }
74
+ const inset = t1 === undefined ? DEFAULT_PILL_INSET_PX : t1;
75
+ const [hoveredKey, setHoveredKey] = useState(null);
76
+ const layoutId = useId();
77
+ const shouldReduceMotion = usePrefersReducedMotion();
78
+ const isHovering = hoveredKey !== null;
79
+ let t2;
80
+ if ($[8] !== children || $[9] !== hoveredKey || $[10] !== inset || $[11] !== isHovering || $[12] !== layoutId || $[13] !== pillClassName || $[14] !== pillContainerClassName || $[15] !== shouldReduceMotion) {
81
+ let t3;
82
+ if ($[17] !== hoveredKey || $[18] !== inset || $[19] !== isHovering || $[20] !== layoutId || $[21] !== pillClassName || $[22] !== pillContainerClassName || $[23] !== shouldReduceMotion) {
83
+ t3 = (child, index) => {
84
+ const item = child;
85
+ const itemKey = item.key === null ? `item-${index}` : String(item.key);
86
+ return cloneElement(item, {
87
+ key: itemKey,
88
+ className: cn("relative", item.props.className),
89
+ onMouseEnter: (event) => {
90
+ item.props.onMouseEnter?.(event);
91
+ setHoveredKey(itemKey);
92
+ }
93
+ }, /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx(AnimatePresence, {
94
+ custom: isHovering,
95
+ children: isHovering ? /* @__PURE__ */ _jsx(motion.div, {
96
+ variants: shouldReduceMotion ? REDUCED_PILL_VARIANTS : PILL_VARIANTS,
97
+ initial: "initial",
98
+ animate: "animate",
99
+ exit: "exit",
100
+ custom: isHovering,
101
+ className: cn("pointer-events-none absolute inset-y-0", pillContainerClassName),
102
+ style: {
103
+ left: -inset,
104
+ right: -inset
105
+ },
106
+ children: hoveredKey === itemKey ? /* @__PURE__ */ _jsx(motion.div, {
107
+ "data-slot": "shared-layout-pill",
108
+ layoutId: `shared-layout-bg-${layoutId}`,
109
+ transition: shouldReduceMotion ? { duration: 0 } : SPRING_LAYOUT,
110
+ className: cn("pointer-events-none size-full rounded-2xl bg-muted/80", pillClassName)
111
+ }) : null
112
+ }) : null
113
+ }), /* @__PURE__ */ _jsx("div", {
114
+ className: "relative z-10",
115
+ children: item.props.children
116
+ })] }));
117
+ };
118
+ $[17] = hoveredKey;
119
+ $[18] = inset;
120
+ $[19] = isHovering;
121
+ $[20] = layoutId;
122
+ $[21] = pillClassName;
123
+ $[22] = pillContainerClassName;
124
+ $[23] = shouldReduceMotion;
125
+ $[24] = t3;
126
+ } else {
127
+ t3 = $[24];
128
+ }
129
+ t2 = Children.toArray(children).filter(isValidElement).map(t3);
130
+ $[8] = children;
131
+ $[9] = hoveredKey;
132
+ $[10] = inset;
133
+ $[11] = isHovering;
134
+ $[12] = layoutId;
135
+ $[13] = pillClassName;
136
+ $[14] = pillContainerClassName;
137
+ $[15] = shouldReduceMotion;
138
+ $[16] = t2;
139
+ } else {
140
+ t2 = $[16];
141
+ }
142
+ const items = t2;
143
+ const t3 = props;
144
+ let t4;
145
+ if ($[25] !== onMouseLeave) {
146
+ t4 = (event_0) => {
147
+ setHoveredKey(null);
148
+ onMouseLeave?.(event_0);
149
+ };
150
+ $[25] = onMouseLeave;
151
+ $[26] = t4;
152
+ } else {
153
+ t4 = $[26];
154
+ }
155
+ let t5;
156
+ if ($[27] !== className) {
157
+ t5 = cn("flex w-full flex-col", className);
158
+ $[27] = className;
159
+ $[28] = t5;
160
+ } else {
161
+ t5 = $[28];
162
+ }
163
+ let t6;
164
+ if ($[29] !== items || $[30] !== t3 || $[31] !== t4 || $[32] !== t5) {
165
+ t6 = /* @__PURE__ */ _jsx(motion.ul, {
166
+ ...t3,
167
+ layoutRoot: true,
168
+ onMouseLeave: t4,
169
+ className: t5,
170
+ children: items
171
+ });
172
+ $[29] = items;
173
+ $[30] = t3;
174
+ $[31] = t4;
175
+ $[32] = t5;
176
+ $[33] = t6;
177
+ } else {
178
+ t6 = $[33];
179
+ }
180
+ return t6;
181
+ }