@alpic-ai/ui 0.0.0-dev.85c8341 → 0.0.0-dev.862c47d

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 (34) hide show
  1. package/dist/components/avatar.d.mts +1 -1
  2. package/dist/components/button.d.mts +3 -1
  3. package/dist/components/button.mjs +20 -6
  4. package/dist/components/combobox.mjs +1 -1
  5. package/dist/components/dialog.mjs +2 -2
  6. package/dist/components/github-button.d.mts +13 -0
  7. package/dist/components/github-button.mjs +24 -0
  8. package/dist/components/page-loader.d.mts +11 -0
  9. package/dist/components/page-loader.mjs +122 -0
  10. package/dist/components/select.d.mts +1 -9
  11. package/dist/components/select.mjs +8 -28
  12. package/dist/components/shimmer-text.d.mts +12 -0
  13. package/dist/components/shimmer-text.mjs +22 -0
  14. package/dist/components/sidebar.mjs +65 -21
  15. package/dist/components/table.d.mts +10 -1
  16. package/dist/components/table.mjs +4 -4
  17. package/dist/components/typography.d.mts +34 -0
  18. package/dist/components/typography.mjs +29 -0
  19. package/package.json +15 -15
  20. package/src/components/button.tsx +13 -9
  21. package/src/components/combobox.tsx +19 -7
  22. package/src/components/dialog.tsx +2 -2
  23. package/src/components/github-button.tsx +34 -0
  24. package/src/components/page-loader.tsx +59 -0
  25. package/src/components/select.tsx +2 -35
  26. package/src/components/shimmer-text.tsx +23 -0
  27. package/src/components/sidebar.tsx +64 -27
  28. package/src/components/table.tsx +17 -4
  29. package/src/components/typography.tsx +19 -0
  30. package/src/hooks/use-copy-to-clipboard.ts +6 -2
  31. package/src/stories/button.stories.tsx +23 -1
  32. package/src/stories/sidebar.stories.tsx +11 -8
  33. package/src/stories/table.stories.tsx +2 -2
  34. package/src/styles/tokens.css +217 -0
@@ -0,0 +1,34 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/components/typography.d.ts
5
+ declare function H1({
6
+ children,
7
+ className
8
+ }: {
9
+ children: ReactNode;
10
+ className?: string;
11
+ }): _$react_jsx_runtime0.JSX.Element;
12
+ declare function H2({
13
+ children,
14
+ className
15
+ }: {
16
+ children: ReactNode;
17
+ className?: string;
18
+ }): _$react_jsx_runtime0.JSX.Element;
19
+ declare function H3({
20
+ children,
21
+ className
22
+ }: {
23
+ children: ReactNode;
24
+ className?: string;
25
+ }): _$react_jsx_runtime0.JSX.Element;
26
+ declare function H4({
27
+ children,
28
+ className
29
+ }: {
30
+ children: ReactNode;
31
+ className?: string;
32
+ }): _$react_jsx_runtime0.JSX.Element;
33
+ //#endregion
34
+ export { H1, H2, H3, H4 };
@@ -0,0 +1,29 @@
1
+ import { cn } from "../lib/cn.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/typography.tsx
4
+ function H1({ children, className }) {
5
+ return /* @__PURE__ */ jsx("h1", {
6
+ className: cn("scroll-m-20 type-display-sm font-bold", className),
7
+ children
8
+ });
9
+ }
10
+ function H2({ children, className }) {
11
+ return /* @__PURE__ */ jsx("h2", {
12
+ className: cn("scroll-m-20 type-display-xs font-semibold", className),
13
+ children
14
+ });
15
+ }
16
+ function H3({ children, className }) {
17
+ return /* @__PURE__ */ jsx("h3", {
18
+ className: cn("scroll-m-20 type-text-lg font-semibold", className),
19
+ children
20
+ });
21
+ }
22
+ function H4({ children, className }) {
23
+ return /* @__PURE__ */ jsx("h4", {
24
+ className: cn("scroll-m-20 type-text-md font-semibold", className),
25
+ children
26
+ });
27
+ }
28
+ //#endregion
29
+ export { H1, H2, H3, H4 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0-dev.85c8341",
3
+ "version": "0.0.0-dev.862c47d",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,13 +23,13 @@
23
23
  "src"
24
24
  ],
25
25
  "peerDependencies": {
26
- "lucide-react": "^1.0.0",
27
- "react": "^19.0.0",
28
- "react-dom": "^19.0.0",
29
- "react-hook-form": "^7.72.1",
30
- "sonner": "^2.0.0",
31
- "tailwindcss": "^4.0.0",
32
- "tw-animate-css": "^1.0.0"
26
+ "lucide-react": "^1.14.0",
27
+ "react": "^19.2.6",
28
+ "react-dom": "^19.2.6",
29
+ "react-hook-form": "^7.75.0",
30
+ "sonner": "^2.0.7",
31
+ "tailwindcss": "^4.3.0",
32
+ "tw-animate-css": "^1.4.0"
33
33
  },
34
34
  "dependencies": {
35
35
  "@radix-ui/react-accordion": "^1.2.12",
@@ -52,21 +52,21 @@
52
52
  "class-variance-authority": "^0.7.1",
53
53
  "clsx": "^2.1.1",
54
54
  "cmdk": "^1.1.1",
55
- "tailwind-merge": "^3.5.0"
55
+ "tailwind-merge": "^3.6.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@ladle/react": "^5.1.1",
59
- "@tailwindcss/postcss": "^4.2.2",
59
+ "@tailwindcss/postcss": "^4.3.0",
60
60
  "@types/react": "19.2.14",
61
61
  "@types/react-dom": "19.2.3",
62
- "lucide-react": "^1.7.0",
63
- "react-hook-form": "^7.72.1",
62
+ "lucide-react": "^1.14.0",
63
+ "react-hook-form": "^7.75.0",
64
64
  "shx": "^0.4.0",
65
65
  "sonner": "^2.0.7",
66
- "tailwindcss": "^4.2.2",
67
- "tsdown": "^0.21.7",
66
+ "tailwindcss": "^4.3.0",
67
+ "tsdown": "^0.22.0",
68
68
  "tw-animate-css": "^1.4.0",
69
- "typescript": "^6.0.2"
69
+ "typescript": "^6.0.3"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "shx rm -rf dist && tsdown",
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Slot } from "@radix-ui/react-slot";
3
+ import { Slot, Slottable } from "@radix-ui/react-slot";
4
4
  import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { Loader2 } from "lucide-react";
6
6
  import type * as React from "react";
@@ -46,6 +46,13 @@ const buttonVariants = cva(
46
46
  "bg-destructive text-destructive-foreground",
47
47
  "[@media(hover:hover)]:hover:bg-destructive-hover",
48
48
  ].join(" "),
49
+ cta: [
50
+ "button-cta",
51
+ "h-9 px-4 gap-2 rounded-md",
52
+ "dark:bg-inverted text-foreground",
53
+ "transition-[transform,filter] duration-300 ease-out",
54
+ "active:scale-[0.99]",
55
+ ].join(" "),
49
56
  },
50
57
  size: {
51
58
  default: "type-text-sm",
@@ -65,6 +72,7 @@ interface ButtonProps extends React.ComponentProps<"button">, VariantProps<typeo
65
72
  asChild?: boolean;
66
73
  loading?: boolean;
67
74
  icon?: React.ReactNode;
75
+ iconTrailing?: React.ReactNode;
68
76
  }
69
77
 
70
78
  function Button({
@@ -75,6 +83,7 @@ function Button({
75
83
  asChild = false,
76
84
  loading = false,
77
85
  icon,
86
+ iconTrailing,
78
87
  disabled,
79
88
  children,
80
89
  ...props
@@ -90,14 +99,9 @@ function Button({
90
99
  aria-busy={loading || undefined}
91
100
  {...props}
92
101
  >
93
- {asChild ? (
94
- children
95
- ) : (
96
- <>
97
- {loading ? <Loader2 className="motion-safe:animate-spin" /> : icon}
98
- {children}
99
- </>
100
- )}
102
+ {loading ? <Loader2 className="motion-safe:animate-spin" /> : icon}
103
+ {asChild ? <Slottable>{children}</Slottable> : children}
104
+ {!loading && iconTrailing ? <span data-cta-icon-trailing>{iconTrailing}</span> : null}
101
105
  </Comp>
102
106
  );
103
107
  }
@@ -84,7 +84,9 @@ function Combobox(props: ComboboxProps) {
84
84
 
85
85
  const onOpenChange = useCallback(
86
86
  (newOpen: boolean) => {
87
- if (!isOpenControlled) setUncontrolledOpen(newOpen);
87
+ if (!isOpenControlled) {
88
+ setUncontrolledOpen(newOpen);
89
+ }
88
90
  controlledOnOpenChange?.(newOpen);
89
91
  },
90
92
  [isOpenControlled, controlledOnOpenChange],
@@ -105,7 +107,9 @@ function Combobox(props: ComboboxProps) {
105
107
 
106
108
  const isSelected = useCallback(
107
109
  (itemValue: string) => {
108
- if (multiple) return multiValues.includes(itemValue);
110
+ if (multiple) {
111
+ return multiValues.includes(itemValue);
112
+ }
109
113
  return singleValue === itemValue;
110
114
  },
111
115
  [multiple, singleValue, multiValues],
@@ -123,11 +127,15 @@ function Combobox(props: ComboboxProps) {
123
127
  const next = multiValues.includes(itemValue)
124
128
  ? multiValues.filter((val) => val !== itemValue)
125
129
  : [...multiValues, itemValue];
126
- if (!isControlledMulti) setUncontrolledMultiValue(next);
130
+ if (!isControlledMulti) {
131
+ setUncontrolledMultiValue(next);
132
+ }
127
133
  onValueChangeMulti?.(next);
128
134
  // Stay open in multi mode
129
135
  } else {
130
- if (!isControlledSingle) setUncontrolledSingleValue(itemValue);
136
+ if (!isControlledSingle) {
137
+ setUncontrolledSingleValue(itemValue);
138
+ }
131
139
  onValueChangeSingle?.(itemValue);
132
140
  onOpenChange(false);
133
141
  }
@@ -145,9 +153,13 @@ function Combobox(props: ComboboxProps) {
145
153
 
146
154
  const onDeselect = useCallback(
147
155
  (itemValue: string) => {
148
- if (!multiple) return;
156
+ if (!multiple) {
157
+ return;
158
+ }
149
159
  const next = multiValues.filter((val) => val !== itemValue);
150
- if (!isControlledMulti) setUncontrolledMultiValue(next);
160
+ if (!isControlledMulti) {
161
+ setUncontrolledMultiValue(next);
162
+ }
151
163
  onValueChangeMulti?.(next);
152
164
  },
153
165
  [multiple, isControlledMulti, onValueChangeMulti, multiValues],
@@ -349,7 +361,7 @@ function ComboboxItem({ className, children, itemValue, ...props }: ComboboxItem
349
361
  data-selected-item={selected || undefined}
350
362
  onSelect={() => onSelect(itemValue)}
351
363
  className={cn(
352
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-2.5 outline-hidden select-none",
364
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-hidden select-none",
353
365
  "type-text-md font-medium text-foreground mx-1.5 my-px",
354
366
  "data-[selected=true]:bg-background-hover",
355
367
  "data-[selected-item]:bg-accent",
@@ -37,7 +37,7 @@ function DialogOverlay({ className, ...props }: React.ComponentProps<typeof Dial
37
37
  const dialogContentVariants = cva(
38
38
  [
39
39
  "bg-background fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2",
40
- "overflow-y-auto rounded-2xl px-6 shadow-lg outline-none",
40
+ "max-h-[calc(100vh-4rem)] overflow-hidden rounded-2xl px-6 shadow-lg outline-none",
41
41
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
42
42
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
43
43
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -93,7 +93,7 @@ function DialogContent({ className, children, size, showCloseButton = true, ...p
93
93
  }
94
94
 
95
95
  function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
96
- return <div data-slot="dialog-header" className={cn("flex flex-col gap-0.5 pt-6 pr-10", className)} {...props} />;
96
+ return <div data-slot="dialog-header" className={cn("flex flex-col gap-0.5 py-6 pr-10", className)} {...props} />;
97
97
  }
98
98
 
99
99
  const dialogFooterVariants = cva("pb-6 pt-6", {
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+
5
+ import { cn } from "../lib/cn";
6
+ import { Button } from "./button";
7
+
8
+ const GITHUB_ICON_PATH =
9
+ "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12";
10
+
11
+ function GitHubIcon() {
12
+ return (
13
+ <svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
14
+ <path d={GITHUB_ICON_PATH} />
15
+ </svg>
16
+ );
17
+ }
18
+
19
+ type GitHubButtonProps = Omit<ComponentProps<typeof Button>, "variant" | "icon">;
20
+
21
+ function GitHubButton({ className, children, ...props }: GitHubButtonProps) {
22
+ return (
23
+ <Button
24
+ {...props}
25
+ icon={<GitHubIcon />}
26
+ className={cn("bg-foreground text-background [@media(hover:hover)]:hover:bg-foreground/90", className)}
27
+ >
28
+ {children}
29
+ </Button>
30
+ );
31
+ }
32
+
33
+ export type { GitHubButtonProps };
34
+ export { GitHubButton };
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { cn } from "../lib/cn";
4
+
5
+ const CABLE_CAR_SVG = (
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ viewBox="0 0 120 130"
9
+ width="240"
10
+ height="260"
11
+ aria-hidden="true"
12
+ className="block h-auto w-full"
13
+ >
14
+ <line x1="60" y1="3" x2="60" y2="58" stroke="#333" strokeWidth="4" />
15
+ <circle cx="60" cy="11" r="10" fill="#555" />
16
+ <rect x="5" y="58" width="110" height="64" rx="4" fill="#e90060" />
17
+ <rect x="5" y="58" width="110" height="20" rx="4" fill="#F5F0E8" />
18
+ <rect x="5" y="68" width="110" height="10" fill="#F5F0E8" />
19
+ <rect x="14" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
20
+ <rect x="47" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
21
+ <rect x="80" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
22
+ <rect x="5" y="115" width="110" height="7" rx="3" fill="#9f0042" />
23
+ </svg>
24
+ );
25
+
26
+ interface PageLoaderProps {
27
+ className?: string;
28
+ }
29
+
30
+ function PageLoader({ className }: PageLoaderProps) {
31
+ return (
32
+ <div
33
+ className={cn("flex min-h-screen items-center justify-center bg-background", className)}
34
+ role="status"
35
+ aria-label="Loading Alpic…"
36
+ >
37
+ <div className="relative h-[120px] w-[200px]">
38
+ <div
39
+ className="absolute top-[30px] left-0 h-[3px] w-full rounded-sm bg-[#6c6c77]"
40
+ style={{ transform: "rotate(-15deg)", transformOrigin: "left center" }}
41
+ />
42
+ <div className="absolute top-[33px] -left-[45px] w-[45px] motion-safe:animate-[alpic-ride_4s_linear_infinite]">
43
+ {CABLE_CAR_SVG}
44
+ </div>
45
+ <div
46
+ className="pointer-events-none absolute -top-[40px] -left-[45px] z-10 h-[200px] w-[95px]"
47
+ style={{ background: "linear-gradient(to right, var(--color-background) 47%, transparent)" }}
48
+ />
49
+ <div
50
+ className="pointer-events-none absolute -top-[40px] left-[142px] z-10 h-[200px] w-[95px]"
51
+ style={{ background: "linear-gradient(to right, transparent, var(--color-background) 53%)" }}
52
+ />
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export type { PageLoaderProps };
59
+ export { PageLoader };
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as SelectPrimitive from "@radix-ui/react-select";
4
4
  import type { VariantProps } from "class-variance-authority";
5
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
5
+ import { CheckIcon, ChevronDownIcon } from "lucide-react";
6
6
  import type * as React from "react";
7
7
 
8
8
  import { cn } from "../lib/cn";
@@ -75,7 +75,6 @@ function SelectContent({
75
75
  position={position}
76
76
  {...props}
77
77
  >
78
- <SelectScrollUpButton />
79
78
  <SelectPrimitive.Viewport
80
79
  className={cn(
81
80
  "p-1",
@@ -85,7 +84,6 @@ function SelectContent({
85
84
  >
86
85
  {children}
87
86
  </SelectPrimitive.Viewport>
88
- <SelectScrollDownButton />
89
87
  </SelectPrimitive.Content>
90
88
  </SelectPrimitive.Portal>
91
89
  );
@@ -110,7 +108,7 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
110
108
  <SelectPrimitive.Item
111
109
  data-slot="select-item"
112
110
  className={cn(
113
- "relative flex w-full cursor-default items-center gap-2 rounded-sm py-2 pr-8 pl-2.5 outline-hidden select-none",
111
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none",
114
112
  "type-text-md font-medium text-foreground mx-1 my-px",
115
113
  "[@media(hover:hover)]:hover:bg-background-hover",
116
114
  "focus:bg-background-hover",
@@ -142,43 +140,12 @@ function SelectSeparator({ className, ...props }: React.ComponentProps<typeof Se
142
140
  );
143
141
  }
144
142
 
145
- /* ── Scroll buttons ──────────────────────────────────────────────────────── */
146
-
147
- function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
148
- return (
149
- <SelectPrimitive.ScrollUpButton
150
- data-slot="select-scroll-up-button"
151
- className={cn("flex cursor-default items-center justify-center py-1", className)}
152
- {...props}
153
- >
154
- <ChevronUpIcon className="size-4" />
155
- </SelectPrimitive.ScrollUpButton>
156
- );
157
- }
158
-
159
- function SelectScrollDownButton({
160
- className,
161
- ...props
162
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
163
- return (
164
- <SelectPrimitive.ScrollDownButton
165
- data-slot="select-scroll-down-button"
166
- className={cn("flex cursor-default items-center justify-center py-1", className)}
167
- {...props}
168
- >
169
- <ChevronDownIcon className="size-4" />
170
- </SelectPrimitive.ScrollDownButton>
171
- );
172
- }
173
-
174
143
  export {
175
144
  Select,
176
145
  SelectContent,
177
146
  SelectGroup,
178
147
  SelectItem,
179
148
  SelectLabel,
180
- SelectScrollDownButton,
181
- SelectScrollUpButton,
182
149
  SelectSeparator,
183
150
  SelectTrigger,
184
151
  SelectValue,
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import type React from "react";
4
+
5
+ import { cn } from "../lib/cn";
6
+
7
+ const shimmerStyle: React.CSSProperties = {
8
+ background: `linear-gradient(90deg, #0000 calc(50% - 60px), var(--color-foreground), #0000 calc(50% + 60px)), linear-gradient(color-mix(in oklab, var(--color-muted-foreground) 70%, transparent), color-mix(in oklab, var(--color-muted-foreground) 70%, transparent))`,
9
+ backgroundSize: "250% 100%, auto",
10
+ backgroundRepeat: "no-repeat, padding-box",
11
+ WebkitBackgroundClip: "text",
12
+ WebkitTextFillColor: "transparent",
13
+ backgroundClip: "text",
14
+ animation: "shimmer-text 2.5s linear infinite",
15
+ };
16
+
17
+ export function ShimmerText({ children, className }: { children: string | number; className?: string }) {
18
+ return (
19
+ <span className={cn(className)} style={shimmerStyle}>
20
+ {children}
21
+ </span>
22
+ );
23
+ }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { Slot } from "@radix-ui/react-slot";
4
4
  import { cva, type VariantProps } from "class-variance-authority";
5
- import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
6
5
  import * as React from "react";
7
6
 
8
7
  import { useIsMobile } from "../hooks/use-mobile";
@@ -20,6 +19,19 @@ const SIDEBAR_WIDTH_MOBILE = "16rem";
20
19
  const SIDEBAR_WIDTH_ICON = "3.5rem";
21
20
  const SIDEBAR_KEYBOARD_SHORTCUT = "b";
22
21
 
22
+ const INTERACTIVE_SIDEBAR_ELEMENT_SELECTOR = [
23
+ "a",
24
+ "button",
25
+ "input",
26
+ "select",
27
+ "textarea",
28
+ "[role='button']",
29
+ "[role='link']",
30
+ "[role='menuitem']",
31
+ "[contenteditable='true']",
32
+ "[tabindex]:not([tabindex='-1'])",
33
+ ].join(", ");
34
+
23
35
  type SidebarContextProps = {
24
36
  state: "expanded" | "collapsed";
25
37
  open: boolean;
@@ -143,13 +155,31 @@ function Sidebar({
143
155
  variant?: "sidebar" | "floating" | "inset";
144
156
  collapsible?: "offcanvas" | "icon" | "none";
145
157
  }) {
146
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
158
+ const { isMobile, state, openMobile, setOpenMobile, open, setOpen } = useSidebar();
159
+
160
+ function handleSurfaceClickCapture(event: React.MouseEvent<HTMLDivElement>) {
161
+ const clickedInteractiveElement =
162
+ event.target instanceof Element && event.target.closest(INTERACTIVE_SIDEBAR_ELEMENT_SELECTOR);
163
+
164
+ if (clickedInteractiveElement) {
165
+ return;
166
+ }
167
+
168
+ if (!open) {
169
+ event.preventDefault();
170
+ event.stopPropagation();
171
+ setOpen(true);
172
+ return;
173
+ }
174
+
175
+ setOpen(false);
176
+ }
147
177
 
148
178
  if (collapsible === "none") {
149
179
  return (
150
180
  <div
151
181
  data-slot="sidebar"
152
- className={cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)}
182
+ className={cn("bg-sidebar-surface text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)}
153
183
  {...props}
154
184
  >
155
185
  {children}
@@ -164,7 +194,7 @@ function Sidebar({
164
194
  data-sidebar="sidebar"
165
195
  data-slot="sidebar"
166
196
  data-mobile="true"
167
- className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
197
+ className="bg-sidebar-surface text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
168
198
  style={
169
199
  {
170
200
  "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -213,7 +243,7 @@ function Sidebar({
213
243
  // Adjust the padding for floating and inset variants.
214
244
  variant === "floating" || variant === "inset"
215
245
  ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
216
- : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
246
+ : "border-sidebar-border group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
217
247
  className,
218
248
  )}
219
249
  {...props}
@@ -221,7 +251,8 @@ function Sidebar({
221
251
  <div
222
252
  data-sidebar="sidebar"
223
253
  data-slot="sidebar-inner"
224
- className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
254
+ className="bg-sidebar-surface group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
255
+ onClickCapture={handleSurfaceClickCapture}
225
256
  >
226
257
  {children}
227
258
  </div>
@@ -231,9 +262,7 @@ function Sidebar({
231
262
  }
232
263
 
233
264
  function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {
234
- const { state, isMobile, openMobile, toggleSidebar } = useSidebar();
235
-
236
- const isOpen = isMobile ? openMobile : state === "expanded";
265
+ const { toggleSidebar } = useSidebar();
237
266
 
238
267
  return (
239
268
  <Button
@@ -248,12 +277,21 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
248
277
  }}
249
278
  {...props}
250
279
  >
251
- {isOpen ? <PanelLeftClose className="size-4.5" /> : <PanelLeftOpen className="size-4.5" />}
280
+ <SidebarToggleIcon className="size-4.5" />
252
281
  <span className="sr-only">Toggle Sidebar</span>
253
282
  </Button>
254
283
  );
255
284
  }
256
285
 
286
+ function SidebarToggleIcon({ className, ...props }: React.ComponentProps<"svg">) {
287
+ return (
288
+ <svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={className} {...props}>
289
+ <rect x="2.75" y="2.75" width="14.5" height="14.5" rx="4" stroke="currentColor" strokeWidth="1.5" />
290
+ <path d="M10 4.75V15.25" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
291
+ </svg>
292
+ );
293
+ }
294
+
257
295
  function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
258
296
  const { toggleSidebar } = useSidebar();
259
297
 
@@ -266,9 +304,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
266
304
  onClick={toggleSidebar}
267
305
  title="Toggle Sidebar"
268
306
  className={cn(
269
- "[@media(hover:hover)]:hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
270
- "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
271
- "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
307
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
272
308
  "[@media(hover:hover)]:hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
273
309
  "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
274
310
  "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
@@ -316,21 +352,22 @@ function SidebarHeader({ className, icon, title, children, ...props }: SidebarHe
316
352
  <div
317
353
  data-slot="sidebar-header"
318
354
  data-sidebar="header"
319
- className={cn("flex flex-col gap-2 p-2 group-data-[collapsible=icon]:items-center", className)}
355
+ className={cn("flex flex-col gap-2 py-2", className)}
320
356
  {...props}
321
357
  >
322
- <div className="flex h-8 items-center gap-2 px-2 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:gap-0 group-data-[collapsible=icon]:px-0">
323
- <div className="relative shrink-0">
324
- <span className="transition-opacity group-data-[collapsible=icon]:group-hover:opacity-0">{icon}</span>
325
- <div className="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-data-[collapsible=icon]:group-hover:opacity-100">
326
- <SidebarTrigger className="!size-4 !p-0" />
327
- </div>
328
- </div>
329
- {title && (
330
- <span className="text-foreground text-md min-w-0 truncate font-medium group-data-[collapsible=icon]:hidden">
331
- {title}
358
+ <div className="flex h-8 items-center gap-2 px-3">
359
+ <span className="relative flex size-8 shrink-0 items-center justify-center">
360
+ <span className="transition-opacity duration-200 group-data-[collapsible=icon]:group-hover:opacity-0">
361
+ {icon}
332
362
  </span>
333
- )}
363
+ <SidebarTrigger
364
+ tabIndex={-1}
365
+ className="absolute inset-0 opacity-0 transition-opacity duration-200 group-data-[collapsible=icon]:group-hover:opacity-100"
366
+ />
367
+ </span>
368
+ <span className="text-foreground text-md min-w-0 truncate font-medium group-data-[collapsible=icon]:hidden">
369
+ {title}
370
+ </span>
334
371
  <SidebarTrigger className="ml-auto shrink-0 group-data-[collapsible=icon]:hidden" />
335
372
  </div>
336
373
  {children}
@@ -376,7 +413,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
376
413
  <div
377
414
  data-slot="sidebar-group"
378
415
  data-sidebar="group"
379
- className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
416
+ className={cn("relative flex w-full min-w-0 flex-col", className)}
380
417
  {...props}
381
418
  />
382
419
  );
@@ -453,7 +490,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
453
490
  <li
454
491
  data-slot="sidebar-menu-item"
455
492
  data-sidebar="menu-item"
456
- className={cn("group/menu-item relative", className)}
493
+ className={cn("group/menu-item relative px-3", className)}
457
494
  {...props}
458
495
  />
459
496
  );