@warmio/ui 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # `@warmio/ui`
2
2
 
3
- Portable React components shared by Warm products. Install this package with the exact
4
- `@warmio/design@1.0.0` CSS contract, import `@warmio/design/globals.css` once, and add the package's
5
- compiled files to Tailwind's source detection:
3
+ Portable React components shared by Warm products. Install `@warmio/ui@1.3.0` with the exact
4
+ `@warmio/design@1.5.0` CSS/primitive contract, import `@warmio/design/globals.css` once, and add
5
+ the package's compiled files to Tailwind's source detection:
6
6
 
7
7
  ## Why these files are separate
8
8
 
@@ -19,10 +19,13 @@ deployment permission to publish packages.
19
19
  @source '../node_modules/@warmio/ui/dist';
20
20
  ```
21
21
 
22
- The `1.1.0` public surface contains the Warm badge, logo, Button, Text and `StackedDisplay`,
23
- DropdownMenu, Sheet, and complete responsive Sidebar family including `SidebarBrand` and
24
- `SidebarAccountTrigger`. Styling is sealed: use documented semantic presentations and layout
25
- wrappers, never `className` or `style` overrides. DropdownMenu interaction feedback requires the
26
- exact public peer `cuelume@0.1.0`.
22
+ The `1.3.0` public UI surface contains the Warm badge, Text and `StackedDisplay`, DropdownMenu,
23
+ Sheet, the complete responsive Sidebar family, and the `ProductHeader` composition. `Button`,
24
+ `ButtonLink`, `Logo`, and `WarmLogoIcon` are directly owned by `@warmio/design@1.5.0` and are
25
+ re-exported through UI where applicable. `ProductHeader` owns the responsive frame,
26
+ breadcrumb/title semantics, leading slot, action slot, and optional portal targets used by Warm
27
+ product shells. Styling is sealed: use documented semantic properties and layout wrappers, never
28
+ `className` or `style` overrides. DropdownMenu interaction feedback requires the exact public peer
29
+ `cuelume@0.1.0`.
27
30
 
28
31
  The published artifact is standalone and never imports private `warm-app` workspace packages.
package/dist/button.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import {
2
2
  Button,
3
3
  ButtonLink
4
- } from "./chunks/chunk-KCEDMF2I.js";
5
- import "./chunks/chunk-T2TXMN3G.js";
6
- import "./chunks/chunk-VIB7OX7D.js";
4
+ } from "./chunks/chunk-GKEYV2XW.js";
7
5
  export {
8
6
  Button,
9
7
  ButtonLink
@@ -0,0 +1,81 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-VIB7OX7D.js";
4
+
5
+ // ../ui/src/warmio/product-header.tsx
6
+ import { Fragment, memo } from "react";
7
+ import { ChevronRight } from "lucide-react";
8
+ import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
9
+ function getTitleSegments(pageTitle, productName) {
10
+ return pageTitle?.split(">").reduce((segments, rawSegment) => {
11
+ const segment = rawSegment.trim();
12
+ if (!segment || segments.length === 0 && segment === productName) return segments;
13
+ segments.push(segment);
14
+ return segments;
15
+ }, []);
16
+ }
17
+ function ProductHeaderBreadcrumb({
18
+ pageTitle,
19
+ productName = "Warm"
20
+ }) {
21
+ const titleSegments = getTitleSegments(pageTitle, productName);
22
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
23
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: productName }),
24
+ titleSegments?.map((segment, index) => {
25
+ const isLast = index === titleSegments.length - 1;
26
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 text-foreground" }),
28
+ isLast ? /* @__PURE__ */ jsx("h1", { "aria-current": "page", children: segment }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: segment })
29
+ ] }, `${segment}-${index}`);
30
+ })
31
+ ] });
32
+ }
33
+ var ProductHeader = memo(function ProductHeader2({
34
+ actions,
35
+ actionsTargetRef,
36
+ desktopShell = false,
37
+ leading,
38
+ pageTitle,
39
+ productName,
40
+ titleTargetRef,
41
+ visibility = "desktop"
42
+ }) {
43
+ return /* @__PURE__ */ jsxs(
44
+ "header",
45
+ {
46
+ className: cn(
47
+ "sticky top-0 z-20 h-[var(--header-height)] shrink-0 items-center border-b border-border bg-sidebar",
48
+ visibility === "always" ? "flex" : visibility === "container" ? "hidden @3xl/main:flex" : "hidden md:flex",
49
+ desktopShell && "select-none bg-sidebar/95 supports-[backdrop-filter]:bg-sidebar/85 supports-[backdrop-filter]:backdrop-blur"
50
+ ),
51
+ "data-tauri-drag-region": desktopShell ? "deep" : void 0,
52
+ children: [
53
+ /* @__PURE__ */ jsxs(
54
+ "div",
55
+ {
56
+ ref: titleTargetRef,
57
+ className: "flex min-w-0 items-center gap-2 px-4 [&_h1]:text-sm [&_h1]:text-foreground [&:has(>[data-page-chrome-content])>[data-page-chrome-fallback]]:hidden",
58
+ children: [
59
+ leading,
60
+ /* @__PURE__ */ jsx("div", { "data-page-chrome-fallback": true, className: "contents", children: /* @__PURE__ */ jsx(ProductHeaderBreadcrumb, { pageTitle, productName }) })
61
+ ]
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx(
65
+ "div",
66
+ {
67
+ ref: actionsTargetRef,
68
+ className: "ml-auto flex items-center gap-2 px-4 [&:has(>[data-page-chrome-content])>[data-page-chrome-fallback]]:hidden",
69
+ children: actions ? /* @__PURE__ */ jsx("div", { "data-page-chrome-fallback": true, className: "contents", children: actions }) : null
70
+ }
71
+ )
72
+ ]
73
+ }
74
+ );
75
+ });
76
+ ProductHeader.displayName = "ProductHeader";
77
+
78
+ export {
79
+ ProductHeaderBreadcrumb,
80
+ ProductHeader
81
+ };
@@ -8,17 +8,25 @@ import { jsx, jsxs } from "react/jsx-runtime";
8
8
  var textClassNameByVariant = {
9
9
  display: "block text-[1.35rem] font-normal leading-snug tracking-normal [font-family:var(--font-display)] @3xl/main:text-[1.875rem]",
10
10
  displayCompact: "block text-xl font-normal leading-snug tracking-normal [font-family:var(--font-display)] @3xl/main:text-2xl",
11
+ displayHero: "block text-3xl font-bold leading-tight tracking-normal text-foreground sm:text-4xl lg:text-5xl",
12
+ metricValue: "block text-2xl",
13
+ metricValueLead: "block text-3xl",
14
+ metricDisplay: "block text-5xl font-bold leading-none tracking-tight",
11
15
  overline: "block font-mono text-xs font-bold uppercase tracking-[0.18em]",
12
- micro: "text-[10px] font-normal leading-tight",
13
- microStrong: "text-[10px] font-bold leading-tight",
16
+ micro: "text-xs font-normal leading-snug",
17
+ microStrong: "text-xs font-bold leading-snug",
14
18
  body: "text-sm",
15
19
  caption: "text-xs",
16
20
  helper: "text-xs",
17
- sectionTitle: "text-sm font-bold leading-6"
21
+ sectionTitle: "text-base font-bold leading-normal"
18
22
  };
19
23
  var defaultTextElementByVariant = {
20
24
  display: "span",
21
25
  displayCompact: "span",
26
+ displayHero: "h1",
27
+ metricValue: "span",
28
+ metricValueLead: "span",
29
+ metricDisplay: "span",
22
30
  overline: "span",
23
31
  micro: "span",
24
32
  microStrong: "span",
@@ -33,7 +41,10 @@ var textClassNameByTone = {
33
41
  foreground: "text-foreground",
34
42
  primary: "text-primary",
35
43
  success: "text-success",
36
- warning: "text-warning"
44
+ warning: "text-warning",
45
+ financeCash: "text-finance-cash",
46
+ financeDebt: "text-finance-debt",
47
+ financeInvestment: "text-finance-investment"
37
48
  };
38
49
  var textClassNameByAlign = {
39
50
  start: "text-left",
@@ -46,7 +57,9 @@ function Text({
46
57
  tone = "default",
47
58
  align,
48
59
  numeric = false,
60
+ mono = false,
49
61
  truncate = false,
62
+ animateNumber = false,
50
63
  className: _className,
51
64
  style: _style,
52
65
  ...props
@@ -54,11 +67,14 @@ function Text({
54
67
  const Component = as ?? defaultTextElementByVariant[variant];
55
68
  return React.createElement(Component, {
56
69
  ...props,
70
+ "data-animate-number": animateNumber || void 0,
57
71
  className: cn(
58
72
  textClassNameByVariant[variant],
73
+ variant === "sectionTitle" && Component === "h2" && "text-lg leading-snug",
59
74
  textClassNameByTone[tone],
60
75
  align && textClassNameByAlign[align],
61
76
  numeric && "tabular-nums",
77
+ mono && "font-mono",
62
78
  truncate && "truncate"
63
79
  )
64
80
  });
@@ -6,7 +6,7 @@ import {
6
6
  SheetHeader,
7
7
  SheetTitle,
8
8
  SheetTrigger
9
- } from "./chunk-OWDC2VC6.js";
9
+ } from "./chunk-YTDMREDR.js";
10
10
 
11
11
  // ../ui/src/warmio/sheet.tsx
12
12
  import { jsx } from "react/jsx-runtime";
@@ -41,22 +41,16 @@ function SheetContent2({
41
41
  }
42
42
  );
43
43
  }
44
- function SheetFooter2({
45
- className: _className,
46
- spacing = "default",
47
- style: _style,
48
- ...props
49
- }) {
50
- return /* @__PURE__ */ jsx(SheetFooter, { ...props, className: spacing === "roomy" ? "gap-4 p-6" : void 0 });
44
+ function SheetFooter2({ className: _className, style: _style, ...props }) {
45
+ return /* @__PURE__ */ jsx(SheetFooter, { ...props });
51
46
  }
52
47
  function SheetHeader2({
53
48
  className: _className,
54
49
  presentation,
55
- spacing = "default",
56
50
  style: _style,
57
51
  ...props
58
52
  }) {
59
- const className = presentation === "navigation" ? "border-b border-border bg-background p-0" : spacing === "roomy" ? "gap-2 p-6" : void 0;
53
+ const className = presentation === "navigation" ? "border-b border-border bg-background p-0" : void 0;
60
54
  return /* @__PURE__ */ jsx(SheetHeader, { ...props, className });
61
55
  }
62
56
  function SheetTitle2({
@@ -0,0 +1,10 @@
1
+ // ../ui/src/warmio/button.tsx
2
+ import {
3
+ Button,
4
+ ButtonLink
5
+ } from "@warmio/design/button";
6
+
7
+ export {
8
+ Button,
9
+ ButtonLink
10
+ };
@@ -11,11 +11,11 @@ import { CheckIcon } from "lucide-react";
11
11
  import { play, setEnabled } from "cuelume";
12
12
 
13
13
  // ../ui/src/lib/haptic.ts
14
- var supportsHaptic = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : false;
14
+ var supportsHaptic = globalThis.window?.matchMedia?.("(pointer: coarse)").matches ?? false;
15
15
  function getWarmCapacitor(win) {
16
16
  return win?.Capacitor;
17
17
  }
18
- function isIosCapacitorApp(win = typeof window === "undefined" ? void 0 : window) {
18
+ function isIosCapacitorApp(win = globalThis.window) {
19
19
  const capacitor = getWarmCapacitor(win);
20
20
  return capacitor?.getPlatform?.() === "ios" && capacitor?.isNativePlatform?.() === true;
21
21
  }
@@ -23,7 +23,7 @@ function hasVibrate(nav) {
23
23
  return "vibrate" in nav && typeof nav.vibrate === "function";
24
24
  }
25
25
  function triggerIosCapacitorHaptic() {
26
- const haptics = getWarmCapacitor(typeof window === "undefined" ? void 0 : window)?.Plugins?.Haptics;
26
+ const haptics = getWarmCapacitor(globalThis.window)?.Plugins?.Haptics;
27
27
  void haptics?.impact?.({ style: "LIGHT" }).catch(() => {
28
28
  });
29
29
  }
@@ -68,7 +68,7 @@ function triggerInteractionFeedback(intent, options = {}) {
68
68
  haptic();
69
69
  }
70
70
  if (!soundsEnabled) return;
71
- if (typeof document !== "undefined" && document.hidden) return;
71
+ if (globalThis.document?.hidden) return;
72
72
  try {
73
73
  play(soundByIntent[intent]);
74
74
  } catch {
@@ -164,7 +164,7 @@ function DropdownMenuItem({
164
164
  "data-slot": "dropdown-menu-item",
165
165
  "data-inset": inset,
166
166
  "data-variant": variant,
167
- className: "group/dropdown-menu-item relative flex w-full cursor-default items-center gap-2 rounded-lg px-2 py-2 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:bg-sidebar-accent not-data-[variant=destructive]:focus:text-sidebar-accent-foreground not-data-[variant=destructive]:focus:**:text-sidebar-accent-foreground data-inset:pl-6 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
167
+ className: "group/dropdown-menu-item relative flex w-full cursor-default items-center gap-2 rounded-lg px-2 py-2 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:bg-sidebar-accent not-data-[variant=destructive]:focus:text-sidebar-accent-foreground not-data-[variant=destructive]:focus:**:text-sidebar-accent-foreground data-inset:pl-4 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
168
168
  ...props
169
169
  }
170
170
  );
@@ -186,7 +186,7 @@ function DropdownMenuLinkItem({
186
186
  "data-slot": "dropdown-menu-item",
187
187
  "data-inset": inset,
188
188
  "data-variant": variant,
189
- className: "group/dropdown-menu-item relative flex w-full cursor-default items-center gap-2 rounded-lg px-2 py-2 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:bg-sidebar-accent not-data-[variant=destructive]:focus:text-sidebar-accent-foreground not-data-[variant=destructive]:focus:**:text-sidebar-accent-foreground data-inset:pl-6 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
189
+ className: "group/dropdown-menu-item relative flex w-full cursor-default items-center gap-2 rounded-lg px-2 py-2 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:bg-sidebar-accent not-data-[variant=destructive]:focus:text-sidebar-accent-foreground not-data-[variant=destructive]:focus:**:text-sidebar-accent-foreground data-inset:pl-4 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
190
190
  render: /* @__PURE__ */ jsx("a", { href, children }),
191
191
  ...props
192
192
  }
@@ -212,7 +212,7 @@ function DropdownMenuCheckboxItem({
212
212
  ref,
213
213
  "data-slot": "dropdown-menu-checkbox-item",
214
214
  "data-inset": inset,
215
- className: "relative flex cursor-default items-center gap-2 rounded-lg py-2 pr-6 pl-2 text-sm outline-hidden select-none focus:bg-sidebar-accent focus:text-sidebar-accent-foreground focus:**:text-sidebar-accent-foreground data-inset:pl-6 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
215
+ className: "relative flex cursor-default items-center gap-2 rounded-lg py-2 pr-4 pl-2 text-sm outline-hidden select-none focus:bg-sidebar-accent focus:text-sidebar-accent-foreground focus:**:text-sidebar-accent-foreground data-inset:pl-4 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
216
216
  checked,
217
217
  onCheckedChange: handleCheckedChange,
218
218
  ...props,
@@ -265,7 +265,7 @@ function DropdownMenuRadioItem({
265
265
  "data-slot": "dropdown-menu-radio-item",
266
266
  "data-inset": inset,
267
267
  className: cn(
268
- "relative flex cursor-default items-center gap-2 rounded-lg py-2 pr-6 pl-2 text-sm outline-hidden select-none focus:bg-sidebar-accent focus:text-sidebar-accent-foreground focus:**:text-sidebar-accent-foreground data-inset:pl-6 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
268
+ "relative flex cursor-default items-center gap-2 rounded-lg py-2 pr-4 pl-2 text-sm outline-hidden select-none focus:bg-sidebar-accent focus:text-sidebar-accent-foreground focus:**:text-sidebar-accent-foreground data-inset:pl-4 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
269
269
  presentation === "selectMenu" && "min-h-12 text-base"
270
270
  ),
271
271
  ...props,
@@ -296,7 +296,7 @@ function DropdownMenuLabel({
296
296
  ref,
297
297
  "data-slot": "dropdown-menu-label",
298
298
  "data-inset": inset,
299
- className: "px-2 py-2 text-xs font-bold text-muted-foreground data-inset:pl-6",
299
+ className: "px-2 py-2 text-xs font-bold text-muted-foreground data-inset:pl-4",
300
300
  ...props
301
301
  }
302
302
  );
@@ -1,10 +1,11 @@
1
1
  import {
2
+ Button,
2
3
  Sheet,
3
4
  SheetContent,
4
5
  SheetDescription,
5
6
  SheetHeader,
6
7
  SheetTitle
7
- } from "./chunk-OWDC2VC6.js";
8
+ } from "./chunk-YTDMREDR.js";
8
9
  import {
9
10
  cn
10
11
  } from "./chunk-VIB7OX7D.js";
@@ -76,25 +77,36 @@ function TooltipContent({
76
77
  className,
77
78
  side = "top",
78
79
  align = "center",
79
- sideOffset = 0,
80
+ alignOffset = 0,
81
+ sideOffset = 4,
80
82
  children,
81
83
  ...props
82
84
  }) {
83
- return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Positioner, { side, align, sideOffset, children: /* @__PURE__ */ jsxs(
84
- TooltipPrimitive.Popup,
85
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(
86
+ TooltipPrimitive.Positioner,
85
87
  {
86
- "data-slot": "tooltip-content",
87
- className: cn(
88
- "z-[var(--z-popover)] inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg 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-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-[calc(var(--z-popover)+1)] **:data-[slot=kbd]:rounded-lg 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",
89
- className
90
- ),
91
- ...props,
92
- children: [
93
- children,
94
- /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "z-[calc(var(--z-popover)+1)] size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-lg bg-foreground fill-foreground" })
95
- ]
88
+ side,
89
+ align,
90
+ alignOffset,
91
+ sideOffset,
92
+ className: "isolate z-[var(--z-popover)]",
93
+ children: /* @__PURE__ */ jsxs(
94
+ TooltipPrimitive.Popup,
95
+ {
96
+ "data-slot": "tooltip-content",
97
+ className: cn(
98
+ "z-[var(--z-popover)] inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md ring-1 ring-foreground/10 has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-[calc(var(--z-popover)+1)] **:data-[slot=kbd]:rounded-lg 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",
99
+ className
100
+ ),
101
+ ...props,
102
+ children: [
103
+ children,
104
+ /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "z-[calc(var(--z-popover)+1)] size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-lg bg-popover fill-popover data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })
105
+ ]
106
+ }
107
+ )
96
108
  }
97
- ) }) });
109
+ ) });
98
110
  }
99
111
 
100
112
  // ../ui/src/components/sidebar.tsx
@@ -102,6 +114,11 @@ import { MenuIcon, PanelLeftIcon } from "lucide-react";
102
114
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
103
115
  var SIDEBAR_COOKIE_NAME = "sidebar_state";
104
116
  var SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
117
+ function readSidebarCookie() {
118
+ const match = document.cookie.match(/(?:^|; )sidebar_state=(true|false)(?:;|$)/);
119
+ if (!match) return null;
120
+ return match[1] === "true";
121
+ }
105
122
  var SIDEBAR_KEYBOARD_SHORTCUT = "b";
106
123
  var TABLET_ICON_SIDEBAR_QUERY = "(min-width: 768px) and (max-width: 1279px)";
107
124
  var getServerForceIconSidebarSnapshot = () => false;
@@ -146,10 +163,16 @@ function SidebarProvider({
146
163
  const isMobile = useIsMobile();
147
164
  const forceIcon = useForceIconSidebar(forceIconAtTablet);
148
165
  const [openMobile, setOpenMobile] = React4.useState(false);
166
+ const [sidebarMotionEnabled, setSidebarMotionEnabled] = React4.useState(false);
149
167
  const [_open, _setOpen] = React4.useReducer(
150
168
  (_, nextOpen) => nextOpen,
151
169
  defaultOpen
152
170
  );
171
+ React4.useLayoutEffect(() => {
172
+ const stored = readSidebarCookie();
173
+ if (stored === null) return;
174
+ _setOpen(stored);
175
+ }, []);
153
176
  const open = openProp ?? _open;
154
177
  const setOpen = React4.useCallback(
155
178
  (value) => {
@@ -170,6 +193,7 @@ function SidebarProvider({
170
193
  if (!allowUserCollapse) {
171
194
  return;
172
195
  }
196
+ setSidebarMotionEnabled(true);
173
197
  return isMobile ? setOpenMobile((open2) => !open2) : setOpen((open2) => !open2);
174
198
  }, [allowUserCollapse, isMobile, setOpen, setOpenMobile]);
175
199
  const toggleSidebarEvent = React4.useEffectEvent(toggleSidebar);
@@ -195,9 +219,10 @@ function SidebarProvider({
195
219
  isMobile,
196
220
  openMobile,
197
221
  setOpenMobile,
198
- toggleSidebar
222
+ toggleSidebar,
223
+ sidebarMotionEnabled
199
224
  }),
200
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
225
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, sidebarMotionEnabled]
201
226
  );
202
227
  return /* @__PURE__ */ jsx2(SidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(
203
228
  "div",
@@ -222,7 +247,9 @@ function Sidebar({
222
247
  dir,
223
248
  ...props
224
249
  }) {
225
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
250
+ const { isMobile, state, openMobile, setOpenMobile, sidebarMotionEnabled } = useSidebar();
251
+ const sidebarWidthMotionClassName = sidebarMotionEnabled ? "transition-[width] duration-300 ease-linear motion-reduce:transition-none" : null;
252
+ const sidebarPositionMotionClassName = sidebarMotionEnabled ? "transition-[left,right,width] duration-300 ease-linear motion-reduce:transition-none" : null;
226
253
  if (collapsible === "none") {
227
254
  return /* @__PURE__ */ jsx2(
228
255
  "div",
@@ -272,7 +299,8 @@ function Sidebar({
272
299
  {
273
300
  "data-slot": "sidebar-gap",
274
301
  className: cn(
275
- "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
302
+ "relative w-(--sidebar-width) bg-transparent",
303
+ sidebarWidthMotionClassName,
276
304
  "group-data-[collapsible=offcanvas]:w-0",
277
305
  "group-data-[side=right]:rotate-180",
278
306
  variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
@@ -285,7 +313,8 @@ function Sidebar({
285
313
  "data-slot": "sidebar-container",
286
314
  "data-side": side,
287
315
  className: cn(
288
- "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
316
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
317
+ sidebarPositionMotionClassName,
289
318
  // Adjust the padding for floating and inset variants.
290
319
  variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
291
320
  className
@@ -306,6 +335,28 @@ function Sidebar({
306
335
  }
307
336
  );
308
337
  }
338
+ function SidebarTrigger({ className, onClick, ...props }) {
339
+ const { isMobile, toggleSidebar } = useSidebar();
340
+ return /* @__PURE__ */ jsxs2(
341
+ Button,
342
+ {
343
+ "data-sidebar": "trigger",
344
+ "data-slot": "sidebar-trigger",
345
+ variant: "ghost",
346
+ size: "icon-sm",
347
+ className: cn(className),
348
+ onClick: (event) => {
349
+ onClick?.(event);
350
+ toggleSidebar();
351
+ },
352
+ ...props,
353
+ children: [
354
+ isMobile ? /* @__PURE__ */ jsx2(MenuIcon, {}) : /* @__PURE__ */ jsx2(PanelLeftIcon, {}),
355
+ /* @__PURE__ */ jsx2("span", { className: "sr-only", children: "Toggle Sidebar" })
356
+ ]
357
+ }
358
+ );
359
+ }
309
360
  function SidebarInset({ className, ...props }) {
310
361
  return /* @__PURE__ */ jsx2(
311
362
  "main",
@@ -381,7 +432,7 @@ function SidebarGroupLabel({
381
432
  "data-slot": "sidebar-group-label",
382
433
  "data-sidebar": "group-label",
383
434
  className: cn(
384
- "flex h-8 shrink-0 items-center rounded-lg px-2 text-xs font-bold text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
435
+ "flex h-8 shrink-0 items-center rounded-lg px-2 text-xs font-bold text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:mt-0 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
385
436
  className
386
437
  ),
387
438
  ...props
@@ -400,14 +451,10 @@ function SidebarGroupContent({ className, ...props }) {
400
451
  );
401
452
  }
402
453
  var sidebarMenuSpacingClassName = {
403
- default: "gap-1",
454
+ default: "gap-2",
404
455
  roomy: "gap-2"
405
456
  };
406
- function SidebarMenu({
407
- className,
408
- spacing = "default",
409
- ...props
410
- }) {
457
+ function SidebarMenu({ className, spacing = "default", ...props }) {
411
458
  return /* @__PURE__ */ jsx2(
412
459
  "ul",
413
460
  {
@@ -434,7 +481,7 @@ function SidebarMenuItem({ className, ...props }) {
434
481
  );
435
482
  }
436
483
  var sidebarMenuButtonVariants = cva(
437
- "peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-lg p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-bold data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
484
+ "peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-lg p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-4 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-bold data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
438
485
  {
439
486
  variants: {
440
487
  variant: {
@@ -506,7 +553,7 @@ function SidebarMenuBadge({ className, ...props }) {
506
553
  "data-slot": "sidebar-menu-badge",
507
554
  "data-sidebar": "menu-badge",
508
555
  className: cn(
509
- "pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-lg px-1 text-xs font-bold text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
556
+ "pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-lg px-2 text-xs font-bold text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
510
557
  className
511
558
  ),
512
559
  ...props
@@ -526,15 +573,15 @@ function SidebarBrand({ actions, collapsed, expanded }) {
526
573
  React5.cloneElement(collapsed, {
527
574
  className: "hidden size-8 shrink-0 items-center justify-center rounded-lg text-sidebar-foreground transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring focus-visible:outline-hidden group-data-[collapsible=icon]:flex"
528
575
  }),
529
- actions ? /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-1 group-data-[collapsible=icon]:hidden", children: actions }) : null
576
+ actions ? /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2 group-data-[collapsible=icon]:hidden", children: actions }) : null
530
577
  ] });
531
578
  }
532
579
  var SidebarAccountTrigger = React5.forwardRef(function SidebarAccountTrigger2({ avatar, className: _className, email, label, style: _style, ...props }, ref) {
533
580
  return /* @__PURE__ */ jsxs3(SidebarMenuButton2, { ...props, ref, presentation: "account", children: [
534
- /* @__PURE__ */ jsx3("div", { className: "grid size-8 shrink-0 place-items-center rounded-lg bg-sidebar-primary text-xs font-bold text-sidebar-primary-foreground", children: avatar ?? /* @__PURE__ */ jsx3(UserRound, { className: "size-4", "aria-hidden": "true" }) }),
581
+ /* @__PURE__ */ jsx3("div", { className: "grid size-8 shrink-0 place-items-center rounded-lg bg-sidebar-primary text-xs font-bold text-sidebar-primary-foreground group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:rounded-full", children: avatar ?? /* @__PURE__ */ jsx3(UserRound, { className: "size-4", "aria-hidden": "true" }) }),
535
582
  /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1 text-left group-data-[collapsible=icon]:hidden", children: [
536
- /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-bold", children: label }),
537
- /* @__PURE__ */ jsx3("div", { className: "truncate text-xs text-sidebar-foreground/60", children: email })
583
+ /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-bold", title: label, children: label }),
584
+ /* @__PURE__ */ jsx3("div", { className: "truncate text-xs text-sidebar-foreground/60", title: email, children: email })
538
585
  ] }),
539
586
  /* @__PURE__ */ jsx3(ChevronUp, { className: "ml-auto size-4 group-data-[collapsible=icon]:hidden" })
540
587
  ] });
@@ -549,7 +596,7 @@ function SidebarProvider2({
549
596
  style: _style,
550
597
  ...props
551
598
  }) {
552
- const presentationClassName = presentation === "mockup" ? "min-h-0" : presentation === "preview" ? "min-h-full" : void 0;
599
+ const presentationClassName = presentation === "mockup" ? "min-h-0 [--sidebar-width:16rem]" : presentation === "preview" ? "min-h-full" : void 0;
553
600
  return /* @__PURE__ */ jsx4(SidebarProvider, { ...props, className: presentationClassName });
554
601
  }
555
602
  function Sidebar2({
@@ -589,7 +636,7 @@ function SidebarContent2({
589
636
  style: _style,
590
637
  ...props
591
638
  }) {
592
- const className = presentation === "product" ? "pb-0" : presentation === "mockup" ? "px-1 py-3" : void 0;
639
+ const className = presentation === "product" ? "pb-0" : presentation === "mockup" ? "px-2 py-4" : void 0;
593
640
  return /* @__PURE__ */ jsx4(SidebarContent, { ...props, className });
594
641
  }
595
642
  function SidebarFooter2({
@@ -598,13 +645,8 @@ function SidebarFooter2({
598
645
  style: _style,
599
646
  ...props
600
647
  }) {
601
- return /* @__PURE__ */ jsx4(
602
- SidebarFooter,
603
- {
604
- ...props,
605
- className: presentation === "product" ? "border-t border-border p-2" : void 0
606
- }
607
- );
648
+ const className = presentation === "product" ? "border-t border-border p-2" : presentation === "collapsedUtilities" ? "hidden border-t border-border p-2 group-data-[collapsible=icon]:flex group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:items-center" : void 0;
649
+ return /* @__PURE__ */ jsx4(SidebarFooter, { ...props, className });
608
650
  }
609
651
  function SidebarGroup2({
610
652
  className: _className,
@@ -613,7 +655,7 @@ function SidebarGroup2({
613
655
  style: _style,
614
656
  ...props
615
657
  }) {
616
- const className = presentation === "navigation" || presentation === "navigation-primary" ? `${collapsed ? "items-center " : ""}${presentation === "navigation-primary" ? "px-2 pt-3 pb-1" : "px-2 py-1"}` : presentation === "mockup" ? "py-1" : void 0;
658
+ const className = presentation === "navigation" || presentation === "navigation-primary" ? `${collapsed ? "items-center " : ""}${presentation === "navigation-primary" ? "px-2 pt-4 pb-2" : "px-2 py-2"}` : presentation === "mockup" ? "py-2" : void 0;
617
659
  return /* @__PURE__ */ jsx4(SidebarGroup, { ...props, className });
618
660
  }
619
661
  function SidebarGroupContent2({
@@ -652,8 +694,8 @@ var SidebarMenuButton2 = React6.forwardRef(
652
694
  ref,
653
695
  className: cn(
654
696
  (collapsed || presentation === "previewCollapsed") && "mx-auto size-8 gap-0 p-2",
655
- presentation === "account" && "h-12",
656
- presentation === "navigation" && "[&>span:last-child]:text-sidebar-foreground/50 [&>span:last-child]:transition-colors hover:[&>span:last-child]:text-sidebar-accent-foreground data-active:[&>span:last-child]:text-sidebar-accent-foreground",
697
+ presentation === "account" && "h-12 group-data-[collapsible=icon]:mx-auto group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:gap-0! group-data-[collapsible=icon]:p-0!",
698
+ presentation === "navigation" && "text-sidebar-foreground hover:text-sidebar-foreground active:text-sidebar-foreground data-open:hover:text-sidebar-foreground data-active:text-sidebar-foreground",
657
699
  (disabled || presentation === "disabled") && "text-muted-foreground/60"
658
700
  )
659
701
  }
@@ -671,10 +713,20 @@ function SidebarMenuBadge2({
671
713
  SidebarMenuBadge,
672
714
  {
673
715
  ...props,
674
- className: presentation === "attention" ? "animate-pulse" : void 0
716
+ className: cn(
717
+ "text-sidebar-foreground peer-hover/menu-button:text-sidebar-foreground peer-data-active/menu-button:text-sidebar-foreground",
718
+ presentation === "attention" && "animate-pulse"
719
+ )
675
720
  }
676
721
  );
677
722
  }
723
+ function SidebarTrigger2({
724
+ className: _className,
725
+ style: _style,
726
+ ...props
727
+ }) {
728
+ return /* @__PURE__ */ jsx4(SidebarTrigger, { ...props });
729
+ }
678
730
 
679
731
  export {
680
732
  useSidebar,
@@ -692,5 +744,6 @@ export {
692
744
  SidebarMenu2 as SidebarMenu,
693
745
  SidebarMenuItem2 as SidebarMenuItem,
694
746
  SidebarMenuButton2 as SidebarMenuButton,
695
- SidebarMenuBadge2 as SidebarMenuBadge
747
+ SidebarMenuBadge2 as SidebarMenuBadge,
748
+ SidebarTrigger2 as SidebarTrigger
696
749
  };