sh-ui-cli 0.45.2 → 0.45.3

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 (91) hide show
  1. package/data/changelog/versions.json +13 -0
  2. package/data/registry/react/components/accordion/index.tailwind.tsx +5 -7
  3. package/data/registry/react/components/accordion/index.tsx +5 -7
  4. package/data/registry/react/components/avatar/index.tailwind.tsx +4 -6
  5. package/data/registry/react/components/avatar/index.tsx +4 -6
  6. package/data/registry/react/components/badge/index.tailwind.tsx +2 -4
  7. package/data/registry/react/components/badge/index.tsx +2 -4
  8. package/data/registry/react/components/breadcrumb/index.tailwind.tsx +8 -10
  9. package/data/registry/react/components/breadcrumb/index.tsx +8 -10
  10. package/data/registry/react/components/button/index.tailwind.tsx +2 -1
  11. package/data/registry/react/components/button/index.tsx +3 -4
  12. package/data/registry/react/components/calendar/index.tailwind.tsx +10 -12
  13. package/data/registry/react/components/calendar/index.tsx +9 -11
  14. package/data/registry/react/components/card/index.tailwind.tsx +8 -10
  15. package/data/registry/react/components/card/index.tsx +8 -10
  16. package/data/registry/react/components/carousel/index.tailwind.tsx +7 -9
  17. package/data/registry/react/components/carousel/index.tsx +7 -9
  18. package/data/registry/react/components/checkbox/index.tailwind.tsx +3 -5
  19. package/data/registry/react/components/checkbox/index.tsx +3 -5
  20. package/data/registry/react/components/code-editor/index.tailwind.tsx +2 -4
  21. package/data/registry/react/components/code-editor/index.tsx +2 -4
  22. package/data/registry/react/components/code-panel/index.tailwind.tsx +5 -7
  23. package/data/registry/react/components/code-panel/index.tsx +5 -7
  24. package/data/registry/react/components/color-picker/index.tailwind.tsx +7 -6
  25. package/data/registry/react/components/color-picker/index.tsx +7 -6
  26. package/data/registry/react/components/combobox/index.tailwind.tsx +8 -10
  27. package/data/registry/react/components/combobox/index.tsx +8 -10
  28. package/data/registry/react/components/context-menu/index.tailwind.tsx +10 -12
  29. package/data/registry/react/components/context-menu/index.tsx +10 -12
  30. package/data/registry/react/components/date-picker/index.tailwind.tsx +7 -9
  31. package/data/registry/react/components/date-picker/index.tsx +7 -9
  32. package/data/registry/react/components/dialog/index.tailwind.tsx +6 -8
  33. package/data/registry/react/components/dialog/index.tsx +6 -8
  34. package/data/registry/react/components/dropdown-menu/index.tailwind.tsx +10 -12
  35. package/data/registry/react/components/dropdown-menu/index.tsx +10 -12
  36. package/data/registry/react/components/file-upload/index.tailwind.tsx +6 -8
  37. package/data/registry/react/components/file-upload/index.tsx +6 -8
  38. package/data/registry/react/components/form/field.tailwind.tsx +2 -1
  39. package/data/registry/react/components/form/field.tsx +2 -3
  40. package/data/registry/react/components/header/index.tailwind.tsx +17 -19
  41. package/data/registry/react/components/header/index.tsx +17 -19
  42. package/data/registry/react/components/input/index.tailwind.tsx +4 -6
  43. package/data/registry/react/components/input/index.tsx +4 -6
  44. package/data/registry/react/components/label/index.tailwind.tsx +6 -8
  45. package/data/registry/react/components/label/index.tsx +6 -8
  46. package/data/registry/react/components/markdown-editor/index.tailwind.tsx +2 -4
  47. package/data/registry/react/components/markdown-editor/index.tsx +2 -4
  48. package/data/registry/react/components/menubar/index.tailwind.tsx +2 -4
  49. package/data/registry/react/components/menubar/index.tsx +2 -4
  50. package/data/registry/react/components/numeric-input/index.tailwind.tsx +2 -4
  51. package/data/registry/react/components/numeric-input/index.tsx +2 -4
  52. package/data/registry/react/components/page-toc/index.tailwind.tsx +3 -2
  53. package/data/registry/react/components/page-toc/index.tsx +2 -3
  54. package/data/registry/react/components/pagination/index.tailwind.tsx +8 -10
  55. package/data/registry/react/components/pagination/index.tsx +8 -10
  56. package/data/registry/react/components/popover/index.tailwind.tsx +4 -6
  57. package/data/registry/react/components/popover/index.tsx +4 -6
  58. package/data/registry/react/components/progress/index.tailwind.tsx +3 -5
  59. package/data/registry/react/components/progress/index.tsx +2 -4
  60. package/data/registry/react/components/radio/index.tailwind.tsx +3 -5
  61. package/data/registry/react/components/radio/index.tsx +3 -5
  62. package/data/registry/react/components/rich-text-editor/index.tailwind.tsx +3 -5
  63. package/data/registry/react/components/rich-text-editor/index.tsx +3 -5
  64. package/data/registry/react/components/select/index.tailwind.tsx +8 -10
  65. package/data/registry/react/components/select/index.tsx +8 -10
  66. package/data/registry/react/components/separator/index.tailwind.tsx +2 -4
  67. package/data/registry/react/components/separator/index.tsx +2 -4
  68. package/data/registry/react/components/sidebar/index.tailwind.tsx +32 -43
  69. package/data/registry/react/components/sidebar/index.tsx +29 -46
  70. package/data/registry/react/components/skeleton/index.tailwind.tsx +2 -4
  71. package/data/registry/react/components/skeleton/index.tsx +2 -4
  72. package/data/registry/react/components/slider/index.tailwind.tsx +5 -7
  73. package/data/registry/react/components/slider/index.tsx +5 -7
  74. package/data/registry/react/components/spinner/index.tailwind.tsx +3 -5
  75. package/data/registry/react/components/spinner/index.tsx +2 -4
  76. package/data/registry/react/components/switch/index.tailwind.tsx +3 -5
  77. package/data/registry/react/components/switch/index.tsx +2 -4
  78. package/data/registry/react/components/tabs/index.tailwind.tsx +6 -8
  79. package/data/registry/react/components/tabs/index.tsx +6 -8
  80. package/data/registry/react/components/textarea/index.tailwind.tsx +2 -4
  81. package/data/registry/react/components/textarea/index.tsx +2 -4
  82. package/data/registry/react/components/toggle/index.tailwind.tsx +4 -6
  83. package/data/registry/react/components/toggle/index.tsx +4 -6
  84. package/data/registry/react/components/tooltip/index.tailwind.tsx +2 -4
  85. package/data/registry/react/components/tooltip/index.tsx +2 -4
  86. package/data/registry/react/lib/cn.tailwind.ts +17 -0
  87. package/data/registry/react/peer-versions.json +3 -1
  88. package/data/registry/react/registry.json +159 -43
  89. package/package.json +1 -1
  90. package/src/add.mjs +25 -1
  91. package/templates/ui-app-template/sh-ui.config.json +5 -0
@@ -5,6 +5,7 @@ import { useEditor, EditorContent, type Editor } from "@tiptap/react";
5
5
  import StarterKit from "@tiptap/starter-kit";
6
6
  import Placeholder from "@tiptap/extension-placeholder";
7
7
  import Link from "@tiptap/extension-link";
8
+ import { cn } from "@SH_UI_UTILS@";
8
9
  import {
9
10
  BoldIcon, ItalicIcon, StrikethroughIcon,
10
11
  Heading1Icon, Heading2Icon, Heading3Icon,
@@ -26,9 +27,6 @@ export interface RichTextEditorProps {
26
27
  "aria-label"?: string;
27
28
  }
28
29
 
29
- function cx(...args: (string | undefined | false | null)[]) {
30
- return args.filter(Boolean).join(" ");
31
- }
32
30
 
33
31
  export function RichTextEditor({
34
32
  value: valueProp, defaultValue, onChange, placeholder, readOnly = false, hideToolbar = false,
@@ -68,7 +66,7 @@ export function RichTextEditor({
68
66
 
69
67
  return (
70
68
  <div
71
- className={cx(
69
+ className={cn(
72
70
  "sh-ui-rte flex flex-col border border-border rounded-[var(--radius)] bg-background overflow-hidden transition-[border-color] duration-[var(--duration-fast)] focus-within:border-foreground focus-within:outline-[length:var(--border-width-strong)] focus-within:outline-foreground focus-within:outline-offset-2 data-[readonly]:bg-background-subtle motion-reduce:transition-none",
73
71
  className,
74
72
  )}
@@ -158,7 +156,7 @@ function ToolbarButton({ editor, label, icon, isActive, canRun, run, disabled }:
158
156
  return (
159
157
  <button
160
158
  type="button"
161
- className={cx(
159
+ className={cn(
162
160
  "inline-flex items-center justify-center w-7 h-7 p-0 bg-transparent text-foreground-muted border border-transparent rounded-[calc(var(--radius)-2px)] cursor-pointer transition-[color,background-color,border-color] duration-[var(--duration-fast)] hover:not-disabled:text-foreground hover:not-disabled:bg-background hover:not-disabled:border-border focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-1 disabled:opacity-50 disabled:cursor-not-allowed motion-reduce:transition-none",
163
161
  isActive && "text-foreground bg-background border-border-strong",
164
162
  )}
@@ -5,6 +5,7 @@ import { useEditor, EditorContent, type Editor } from "@tiptap/react";
5
5
  import StarterKit from "@tiptap/starter-kit";
6
6
  import Placeholder from "@tiptap/extension-placeholder";
7
7
  import Link from "@tiptap/extension-link";
8
+ import { cn } from "@SH_UI_UTILS@";
8
9
  import {
9
10
  BoldIcon,
10
11
  ItalicIcon,
@@ -51,9 +52,6 @@ export interface RichTextEditorProps {
51
52
  "aria-label"?: string;
52
53
  }
53
54
 
54
- function cx(...args: (string | undefined | false | null)[]) {
55
- return args.filter(Boolean).join(" ");
56
- }
57
55
 
58
56
  /**
59
57
  * Tiptap 기반 리치 텍스트 에디터.
@@ -117,7 +115,7 @@ export function RichTextEditor({
117
115
 
118
116
  return (
119
117
  <div
120
- className={cx("sh-ui-rte", className)}
118
+ className={cn("sh-ui-rte", className)}
121
119
  data-readonly={readOnly || undefined}
122
120
  style={
123
121
  {
@@ -330,7 +328,7 @@ function ToolbarButton({
330
328
  return (
331
329
  <button
332
330
  type="button"
333
- className={cx("sh-ui-rte__btn", isActive && "is-active")}
331
+ className={cn("sh-ui-rte__btn", isActive && "is-active")}
334
332
  aria-label={label}
335
333
  aria-pressed={isActive || undefined}
336
334
  title={label}
@@ -3,17 +3,15 @@
3
3
  import * as React from "react";
4
4
  import { Select as BaseSelect } from "@base-ui/react/select";
5
5
 
6
- function cx(...args: (string | undefined | false)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
6
 
7
+ import { cn } from "@SH_UI_UTILS@";
10
8
  export const Select = BaseSelect.Root;
11
9
 
12
10
  export function SelectValue({ placeholder, className, ...props }: { placeholder?: string; className?: string } & Omit<
13
11
  React.ComponentPropsWithoutRef<typeof BaseSelect.Value>, "children"
14
12
  >) {
15
13
  return (
16
- <BaseSelect.Value className={cx("flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap", className)} {...props}>
14
+ <BaseSelect.Value className={cn("flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap", className)} {...props}>
17
15
  {(value) =>
18
16
  value !== null && value !== undefined && value !== "" ? (
19
17
  (value as React.ReactNode)
@@ -31,7 +29,7 @@ export const SelectTrigger = React.forwardRef<
31
29
  >(({ className, children, ...props }, ref) => (
32
30
  <BaseSelect.Trigger
33
31
  ref={ref}
34
- className={cx(
32
+ className={cn(
35
33
  "inline-flex items-center justify-between gap-[var(--space-2)] min-w-40 h-[var(--control-md)] px-[var(--space-3)] bg-background text-foreground border border-border rounded-[var(--radius)] text-[length:var(--text-sm)] leading-none cursor-pointer transition-[border-color,background-color] duration-[var(--duration-fast)] select-none hover:not-disabled:border-border-strong focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 data-[popup-open]:border-border-strong disabled:opacity-[var(--opacity-disabled)] disabled:pointer-events-none",
36
34
  className,
37
35
  )}
@@ -61,7 +59,7 @@ export const SelectContent = React.forwardRef<
61
59
  <BaseSelect.Positioner className="outline-none z-[var(--z-dropdown)]" sideOffset={4} align="start">
62
60
  <BaseSelect.Popup
63
61
  ref={ref}
64
- className={cx(
62
+ className={cn(
65
63
  "min-w-[var(--anchor-width,10rem)] max-h-[min(24rem,var(--available-height,24rem))] overflow-y-auto p-[var(--space-1)] bg-background text-foreground border border-border rounded-[var(--radius)] shadow-[0_4px_6px_-1px_rgba(0,0,0,0.08),0_2px_4px_-2px_rgba(0,0,0,0.05)] text-[length:var(--text-sm)] origin-[var(--transform-origin)] animate-[sh-ui-select-in_140ms_ease-out] data-[ending-style]:animate-[sh-ui-select-out_100ms_ease-in_forwards] motion-reduce:animate-none motion-reduce:data-[ending-style]:animate-none",
66
64
  className,
67
65
  )}
@@ -82,7 +80,7 @@ export const SelectLabel = React.forwardRef<
82
80
  >(({ className, ...props }, ref) => (
83
81
  <BaseSelect.GroupLabel
84
82
  ref={ref}
85
- className={cx(
83
+ className={cn(
86
84
  "py-[var(--space-2)] px-[var(--space-2)] pb-[var(--space-1)] text-[length:var(--text-xs)] font-semibold text-foreground-muted uppercase tracking-[0.04em]",
87
85
  className,
88
86
  )}
@@ -97,7 +95,7 @@ export const SelectItem = React.forwardRef<
97
95
  >(({ className, children, ...props }, ref) => (
98
96
  <BaseSelect.Item
99
97
  ref={ref}
100
- className={cx(
98
+ className={cn(
101
99
  "flex items-center gap-[var(--space-2)] py-2 px-3 rounded-[calc(var(--radius)-2px)] cursor-pointer outline-none select-none transition-colors duration-[80ms] data-[highlighted]:bg-background-muted hover:bg-background-muted data-[disabled]:opacity-[var(--opacity-disabled)] data-[disabled]:pointer-events-none",
102
100
  className,
103
101
  )}
@@ -122,7 +120,7 @@ export const SelectSeparator = React.forwardRef<
122
120
  >(({ className, ...props }, ref) => (
123
121
  <BaseSelect.Separator
124
122
  ref={ref}
125
- className={cx("h-px bg-border my-[var(--space-1)]", className)}
123
+ className={cn("h-px bg-border my-[var(--space-1)]", className)}
126
124
  {...props}
127
125
  />
128
126
  ));
@@ -178,7 +176,7 @@ export function MultiSelectValue({
178
176
  } & Omit<React.ComponentPropsWithoutRef<typeof BaseSelect.Value>, "children" | "render">) {
179
177
  const { remove, clear } = useMultiSelect();
180
178
  return (
181
- <BaseSelect.Value className={cx("flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap", className)} {...props}>
179
+ <BaseSelect.Value className={cn("flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap", className)} {...props}>
182
180
  {(value) => {
183
181
  const arr = Array.isArray(value) ? (value as string[]) : [];
184
182
  if (arr.length === 0) return <span className="text-foreground-subtle">{placeholder}</span>;
@@ -4,10 +4,8 @@ import * as React from "react";
4
4
  import { Select as BaseSelect } from "@base-ui/react/select";
5
5
  import "./styles.css";
6
6
 
7
- function cx(...args: (string | undefined | false)[]) {
8
- return args.filter(Boolean).join(" ");
9
- }
10
7
 
8
+ import { cn } from "@SH_UI_UTILS@";
11
9
  export const Select = BaseSelect.Root;
12
10
 
13
11
  /** shadcn 호환: <SelectValue placeholder="..." /> */
@@ -20,7 +18,7 @@ export function SelectValue({
20
18
  "children"
21
19
  >) {
22
20
  return (
23
- <BaseSelect.Value className={cx("sh-ui-select__value", className)} {...props}>
21
+ <BaseSelect.Value className={cn("sh-ui-select__value", className)} {...props}>
24
22
  {(value) =>
25
23
  value !== null && value !== undefined && value !== "" ? (
26
24
  (value as React.ReactNode)
@@ -38,7 +36,7 @@ export const SelectTrigger = React.forwardRef<
38
36
  >(({ className, children, ...props }, ref) => (
39
37
  <BaseSelect.Trigger
40
38
  ref={ref}
41
- className={cx("sh-ui-select__trigger", className)}
39
+ className={cn("sh-ui-select__trigger", className)}
42
40
  {...props}
43
41
  >
44
42
  {children}
@@ -74,7 +72,7 @@ export const SelectContent = React.forwardRef<
74
72
  >
75
73
  <BaseSelect.Popup
76
74
  ref={ref}
77
- className={cx("sh-ui-select__content", className)}
75
+ className={cn("sh-ui-select__content", className)}
78
76
  {...props}
79
77
  >
80
78
  {children}
@@ -92,7 +90,7 @@ export const SelectLabel = React.forwardRef<
92
90
  >(({ className, ...props }, ref) => (
93
91
  <BaseSelect.GroupLabel
94
92
  ref={ref}
95
- className={cx("sh-ui-select__label", className)}
93
+ className={cn("sh-ui-select__label", className)}
96
94
  {...props}
97
95
  />
98
96
  ));
@@ -104,7 +102,7 @@ export const SelectItem = React.forwardRef<
104
102
  >(({ className, children, ...props }, ref) => (
105
103
  <BaseSelect.Item
106
104
  ref={ref}
107
- className={cx("sh-ui-select__item", className)}
105
+ className={cn("sh-ui-select__item", className)}
108
106
  {...props}
109
107
  >
110
108
  <BaseSelect.ItemIndicator className="sh-ui-select__indicator" aria-hidden>
@@ -131,7 +129,7 @@ export const SelectSeparator = React.forwardRef<
131
129
  >(({ className, ...props }, ref) => (
132
130
  <BaseSelect.Separator
133
131
  ref={ref}
134
- className={cx("sh-ui-select__separator", className)}
132
+ className={cn("sh-ui-select__separator", className)}
135
133
  {...props}
136
134
  />
137
135
  ));
@@ -223,7 +221,7 @@ export function MultiSelectValue({
223
221
  >) {
224
222
  const { remove, clear } = useMultiSelect();
225
223
  return (
226
- <BaseSelect.Value className={cx("sh-ui-select__value", className)} {...props}>
224
+ <BaseSelect.Value className={cn("sh-ui-select__value", className)} {...props}>
227
225
  {(value) => {
228
226
  const arr = Array.isArray(value) ? (value as string[]) : [];
229
227
  if (arr.length === 0) {
@@ -1,9 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
- function cx(...args: (string | undefined | false | null)[]) {
4
- return args.filter(Boolean).join(" ");
5
- }
6
3
 
4
+ import { cn } from "@SH_UI_UTILS@";
7
5
  export type SeparatorOrientation = "horizontal" | "vertical";
8
6
 
9
7
  export interface SeparatorProps
@@ -33,7 +31,7 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
33
31
  aria-orientation={decorative ? undefined : orientation}
34
32
  aria-hidden={decorative || undefined}
35
33
  data-orientation={orientation}
36
- className={cx("bg-border shrink-0", sizing, className)}
34
+ className={cn("bg-border shrink-0", sizing, className)}
37
35
  {...props}
38
36
  />
39
37
  );
@@ -1,10 +1,8 @@
1
1
  import * as React from "react";
2
2
  import "./styles.css";
3
3
 
4
- function cx(...args: (string | undefined | false | null)[]) {
5
- return args.filter(Boolean).join(" ");
6
- }
7
4
 
5
+ import { cn } from "@SH_UI_UTILS@";
8
6
  export type SeparatorOrientation = "horizontal" | "vertical";
9
7
 
10
8
  export interface SeparatorProps
@@ -35,7 +33,7 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
35
33
  aria-orientation={decorative ? undefined : orientation}
36
34
  aria-hidden={decorative || undefined}
37
35
  data-orientation={orientation}
38
- className={cx(
36
+ className={cn(
39
37
  "sh-ui-separator",
40
38
  `sh-ui-separator--${orientation}`,
41
39
  className,
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
+ import { cn } from "@SH_UI_UTILS@";
4
5
  import { ChevronRightIcon, PanelLeftIcon } from "lucide-react";
5
6
  import { Popover, PopoverContent, PopoverTrigger } from "../popover";
6
7
 
@@ -116,14 +117,12 @@ export function SidebarProvider({
116
117
  return (
117
118
  <SidebarContext.Provider value={value}>
118
119
  <div
119
- className={[
120
- "[--sidebar-width:16rem] [--sidebar-width-icon:3rem] [--sidebar-width-mobile:18rem]",
120
+ className={cn("[--sidebar-width:16rem] [--sidebar-width-icon:3rem] [--sidebar-width-mobile:18rem]",
121
121
  "[--sidebar-bg:var(--background-subtle)] [--sidebar-fg:var(--foreground)] [--sidebar-border:var(--border)]",
122
122
  "[--sidebar-accent:var(--background-muted)] [--sidebar-accent-fg:var(--foreground)]",
123
123
  "flex w-full",
124
124
  embedded ? "min-h-0 h-full" : "min-h-[100svh]",
125
- className,
126
- ].filter(Boolean).join(" ")}
125
+ className)}
127
126
  style={style}
128
127
  data-embedded={embedded || undefined}
129
128
  data-panel-open={activePanel ? "true" : undefined}
@@ -160,7 +159,7 @@ export function Sidebar({ side = "left", variant = "sidebar", collapsible = "off
160
159
  if (collapsible === "none") {
161
160
  return wrap(
162
161
  <aside
163
- className={[sidebarRoot, "h-[100svh] sticky top-0", className].filter(Boolean).join(" ")}
162
+ className={cn(sidebarRoot, "h-[100svh] sticky top-0", className)}
164
163
  data-side={side}
165
164
  data-variant={variant}
166
165
  {...props}
@@ -180,7 +179,7 @@ export function Sidebar({ side = "left", variant = "sidebar", collapsible = "off
180
179
 
181
180
  return wrap(
182
181
  <aside
183
- className={[sidebarRoot, className].filter(Boolean).join(" ")}
182
+ className={cn(sidebarRoot, className)}
184
183
  data-state={state}
185
184
  data-collapsible={state === "collapsed" ? collapsible : ""}
186
185
  data-variant={variant}
@@ -208,13 +207,11 @@ function MobileSidebar({ side, className, openMobile, setOpenMobile, children, .
208
207
  )}
209
208
  <aside
210
209
  ref={asideRef}
211
- className={[
212
- "fixed top-0 bottom-0 w-[var(--sidebar-width-mobile)] z-[var(--z-overlay)] transition-transform duration-[var(--duration-slow)] flex flex-col bg-[var(--sidebar-bg)] text-[var(--sidebar-fg)] motion-reduce:transition-none",
210
+ className={cn("fixed top-0 bottom-0 w-[var(--sidebar-width-mobile)] z-[var(--z-overlay)] transition-transform duration-[var(--duration-slow)] flex flex-col bg-[var(--sidebar-bg)] text-[var(--sidebar-fg)] motion-reduce:transition-none",
213
211
  side === "left"
214
212
  ? "left-0 border-r border-[var(--sidebar-border)] data-[state=open]:translate-x-0 data-[state=closed]:-translate-x-full"
215
213
  : "right-0 border-l border-[var(--sidebar-border)] data-[state=open]:translate-x-0 data-[state=closed]:translate-x-full",
216
- className,
217
- ].filter(Boolean).join(" ")}
214
+ className)}
218
215
  data-side={side}
219
216
  data-state={openMobile ? "open" : "closed"}
220
217
  role="dialog"
@@ -236,10 +233,8 @@ export function SidebarTrigger({ className, onClick, ...props }: SidebarTriggerP
236
233
  <button
237
234
  type="button"
238
235
  aria-label="Toggle Sidebar"
239
- className={[
240
- "inline-flex items-center justify-center w-8 h-8 border border-transparent bg-transparent text-foreground-muted rounded-[calc(var(--radius)-2px)] cursor-pointer transition-[background-color,color,border-color] duration-[var(--duration-fast)] hover:bg-[var(--sidebar-accent)] hover:text-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 motion-reduce:transition-none",
241
- className,
242
- ].filter(Boolean).join(" ")}
236
+ className={cn("inline-flex items-center justify-center w-8 h-8 border border-transparent bg-transparent text-foreground-muted rounded-[calc(var(--radius)-2px)] cursor-pointer transition-[background-color,color,border-color] duration-[var(--duration-fast)] hover:bg-[var(--sidebar-accent)] hover:text-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 motion-reduce:transition-none",
237
+ className)}
243
238
  onClick={(e) => { onClick?.(e); toggleSidebar(); }}
244
239
  {...props}
245
240
  >
@@ -267,10 +262,8 @@ export function SidebarPanel({ id, className, children, ...props }: SidebarPanel
267
262
  return (
268
263
  <aside
269
264
  ref={ref}
270
- className={[
271
- "[--sidebar-panel-width:20rem] flex flex-col w-[var(--sidebar-panel-width)] shrink-0 bg-background border-r border-[var(--sidebar-border)] relative z-[4] overflow-hidden animate-[sh-ui-sidebar-panel-in_180ms_ease-out] data-[state=closed]:hidden max-md:fixed max-md:top-0 max-md:bottom-0 max-md:left-0 max-md:w-[min(var(--sidebar-panel-width),90vw)] max-md:z-[var(--z-modal)] max-md:shadow-[0_10px_30px_rgba(0,0,0,0.15)] motion-reduce:animate-none",
272
- className,
273
- ].filter(Boolean).join(" ")}
265
+ className={cn("[--sidebar-panel-width:20rem] flex flex-col w-[var(--sidebar-panel-width)] shrink-0 bg-background border-r border-[var(--sidebar-border)] relative z-[4] overflow-hidden animate-[sh-ui-sidebar-panel-in_180ms_ease-out] data-[state=closed]:hidden max-md:fixed max-md:top-0 max-md:bottom-0 max-md:left-0 max-md:w-[min(var(--sidebar-panel-width),90vw)] max-md:z-[var(--z-modal)] max-md:shadow-[0_10px_30px_rgba(0,0,0,0.15)] motion-reduce:animate-none",
266
+ className)}
274
267
  data-state={open ? "open" : "closed"}
275
268
  role={isMobile ? "dialog" : undefined}
276
269
  aria-modal={open && isMobile ? "true" : undefined}
@@ -293,7 +286,7 @@ export function SidebarPanel({ id, className, children, ...props }: SidebarPanel
293
286
  export function SidebarPanelHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
294
287
  return (
295
288
  <div
296
- className={["flex items-center gap-[var(--space-2)] py-3.5 px-[var(--space-4)] border-b border-[var(--sidebar-border)] font-semibold text-[0.9375rem]", className].filter(Boolean).join(" ")}
289
+ className={cn("flex items-center gap-[var(--space-2)] py-3.5 px-[var(--space-4)] border-b border-[var(--sidebar-border)] font-semibold text-[0.9375rem]", className)}
297
290
  {...props}
298
291
  />
299
292
  );
@@ -302,58 +295,56 @@ export function SidebarPanelHeader({ className, ...props }: React.HTMLAttributes
302
295
  export function SidebarPanelContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
303
296
  return (
304
297
  <div
305
- className={["flex-1 min-h-0 overflow-y-auto py-[var(--space-3)] px-[var(--space-4)] pb-[var(--space-4)]", className].filter(Boolean).join(" ")}
298
+ className={cn("flex-1 min-h-0 overflow-y-auto py-[var(--space-3)] px-[var(--space-4)] pb-[var(--space-4)]", className)}
306
299
  {...props}
307
300
  />
308
301
  );
309
302
  }
310
303
 
311
304
  export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
312
- return <main className={["flex-1 min-w-0 bg-background flex flex-col", className].filter(Boolean).join(" ")} {...props} />;
305
+ return <main className={cn("flex-1 min-w-0 bg-background flex flex-col", className)} {...props} />;
313
306
  }
314
307
 
315
308
  export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
316
- return <div className={["flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className].filter(Boolean).join(" ")} {...props} />;
309
+ return <div className={cn("flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className)} {...props} />;
317
310
  }
318
311
 
319
312
  export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
320
- return <div className={["flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className].filter(Boolean).join(" ")} {...props} />;
313
+ return <div className={cn("flex flex-col gap-[var(--space-2)] p-[var(--space-2)] overflow-hidden", className)} {...props} />;
321
314
  }
322
315
 
323
316
  export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
324
- return <div className={["flex flex-col flex-1 min-h-0 overflow-y-auto gap-0", className].filter(Boolean).join(" ")} {...props} />;
317
+ return <div className={cn("flex flex-col flex-1 min-h-0 overflow-y-auto gap-0", className)} {...props} />;
325
318
  }
326
319
 
327
320
  export function SidebarSeparator({ className, ...props }: React.HTMLAttributes<HTMLHRElement>) {
328
- return <hr className={["my-[var(--space-1)] mx-[var(--space-2)] border-0 border-t border-[var(--sidebar-border)] w-auto", className].filter(Boolean).join(" ")} {...props} />;
321
+ return <hr className={cn("my-[var(--space-1)] mx-[var(--space-2)] border-0 border-t border-[var(--sidebar-border)] w-auto", className)} {...props} />;
329
322
  }
330
323
 
331
324
  export function SidebarGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
332
- return <div className={["flex flex-col p-[var(--space-2)] min-w-0", className].filter(Boolean).join(" ")} {...props} />;
325
+ return <div className={cn("flex flex-col p-[var(--space-2)] min-w-0", className)} {...props} />;
333
326
  }
334
327
 
335
328
  export function SidebarGroupLabel({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
336
329
  return (
337
330
  <div
338
- className={[
339
- "flex items-center h-8 px-[var(--space-2)] text-[length:var(--text-xs)] font-medium text-foreground-muted rounded-[calc(var(--radius)-2px)] [[data-state=collapsed][data-collapsible=icon]_&]:hidden",
340
- className,
341
- ].filter(Boolean).join(" ")}
331
+ className={cn("flex items-center h-8 px-[var(--space-2)] text-[length:var(--text-xs)] font-medium text-foreground-muted rounded-[calc(var(--radius)-2px)] [[data-state=collapsed][data-collapsible=icon]_&]:hidden",
332
+ className)}
342
333
  {...props}
343
334
  />
344
335
  );
345
336
  }
346
337
 
347
338
  export function SidebarGroupContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
348
- return <div className={["w-full text-[length:var(--text-sm)]", className].filter(Boolean).join(" ")} {...props} />;
339
+ return <div className={cn("w-full text-[length:var(--text-sm)]", className)} {...props} />;
349
340
  }
350
341
 
351
342
  export function SidebarMenu({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) {
352
- return <ul className={["list-none m-0 p-0 flex flex-col min-w-0 gap-0", className].filter(Boolean).join(" ")} {...props} />;
343
+ return <ul className={cn("list-none m-0 p-0 flex flex-col min-w-0 gap-0", className)} {...props} />;
353
344
  }
354
345
 
355
346
  export function SidebarMenuItem({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) {
356
- return <li className={["relative m-0", className].filter(Boolean).join(" ")} {...props} />;
347
+ return <li className={cn("relative m-0", className)} {...props} />;
357
348
  }
358
349
 
359
350
  export interface SidebarMenuButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -385,14 +376,14 @@ export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenu
385
376
  if (!e.defaultPrevented && panelId != null && ctx) ctx.setActivePanel(panelId);
386
377
  }, [onClick, panelId, ctx]);
387
378
 
388
- const cls = [menuButtonBase, menuButtonSize[size], className].filter(Boolean).join(" ");
379
+ const cls = cn(menuButtonBase, menuButtonSize[size], className);
389
380
 
390
381
  if (asChild && React.isValidElement(children)) {
391
382
  const child = children as React.ReactElement<Record<string, unknown>>;
392
383
  const merged: Record<string, unknown> = {
393
384
  ...props,
394
385
  onClick: handleClick,
395
- className: [(child.props.className as string) || "", cls].filter(Boolean).join(" "),
386
+ className: cn((child.props.className as string) || "", cls),
396
387
  "data-active": resolvedIsActive || undefined,
397
388
  };
398
389
  return React.cloneElement(child, merged);
@@ -409,17 +400,15 @@ export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenu
409
400
  export function SidebarMenuSub({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) {
410
401
  return (
411
402
  <ul
412
- className={[
413
- "list-none mt-0.5 ml-3.5 pt-0.5 pr-0 pb-0.5 pl-2.5 border-l border-[var(--sidebar-border)] flex flex-col gap-0.5 min-w-0 [[data-state=collapsed][data-collapsible=icon]_&]:hidden",
414
- className,
415
- ].filter(Boolean).join(" ")}
403
+ className={cn("list-none mt-0.5 ml-3.5 pt-0.5 pr-0 pb-0.5 pl-2.5 border-l border-[var(--sidebar-border)] flex flex-col gap-0.5 min-w-0 [[data-state=collapsed][data-collapsible=icon]_&]:hidden",
404
+ className)}
416
405
  {...props}
417
406
  />
418
407
  );
419
408
  }
420
409
 
421
410
  export function SidebarMenuSubItem({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) {
422
- return <li className={["relative", className].filter(Boolean).join(" ")} {...props} />;
411
+ return <li className={cn("relative", className)} {...props} />;
423
412
  }
424
413
 
425
414
  export interface SidebarMenuSubButtonProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
@@ -436,13 +425,13 @@ export const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarM
436
425
  function SidebarMenuSubButton({ className, isActive, size = "md", asChild, sectionId, children, ...props }, ref) {
437
426
  const tocActive = useTOCActiveId();
438
427
  const resolvedIsActive = isActive ?? (sectionId != null ? tocActive === sectionId : undefined);
439
- const cls = [menuSubButtonBase, size === "sm" && "text-[length:var(--text-xs)]", className].filter(Boolean).join(" ");
428
+ const cls = cn(menuSubButtonBase, size === "sm" && "text-[length:var(--text-xs)]", className);
440
429
 
441
430
  if (asChild && React.isValidElement(children)) {
442
431
  const child = children as React.ReactElement<Record<string, unknown>>;
443
432
  const merged: Record<string, unknown> = {
444
433
  ...props,
445
- className: [(child.props.className as string) || "", cls].filter(Boolean).join(" "),
434
+ className: cn((child.props.className as string) || "", cls),
446
435
  "data-active": resolvedIsActive || undefined,
447
436
  };
448
437
  return React.cloneElement(child, merged);
@@ -503,7 +492,7 @@ export interface SidebarCollapsibleTriggerProps extends React.ButtonHTMLAttribut
503
492
 
504
493
  export function SidebarCollapsibleTrigger({ className, size = "md", children, onClick, ...props }: SidebarCollapsibleTriggerProps) {
505
494
  const { open, toggle, flyoutMode, flyoutOpen } = useCollapsible();
506
- const cls = [menuButtonBase, menuButtonSize[size], className].filter(Boolean).join(" ");
495
+ const cls = cn(menuButtonBase, menuButtonSize[size], className);
507
496
  const isOpen = flyoutMode ? flyoutOpen : open;
508
497
 
509
498
  const content = (