sh-ui-cli 0.45.2 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/data/changelog/versions.json +26 -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.module.tsx +45 -0
  11. package/data/registry/react/components/button/index.tailwind.tsx +2 -1
  12. package/data/registry/react/components/button/index.tsx +3 -4
  13. package/data/registry/react/components/button/styles.module.css +92 -0
  14. package/data/registry/react/components/calendar/index.tailwind.tsx +10 -12
  15. package/data/registry/react/components/calendar/index.tsx +9 -11
  16. package/data/registry/react/components/card/index.module.tsx +63 -0
  17. package/data/registry/react/components/card/index.tailwind.tsx +8 -10
  18. package/data/registry/react/components/card/index.tsx +8 -10
  19. package/data/registry/react/components/card/styles.module.css +73 -0
  20. package/data/registry/react/components/carousel/index.tailwind.tsx +7 -9
  21. package/data/registry/react/components/carousel/index.tsx +7 -9
  22. package/data/registry/react/components/checkbox/index.tailwind.tsx +3 -5
  23. package/data/registry/react/components/checkbox/index.tsx +3 -5
  24. package/data/registry/react/components/code-editor/index.tailwind.tsx +2 -4
  25. package/data/registry/react/components/code-editor/index.tsx +2 -4
  26. package/data/registry/react/components/code-panel/index.tailwind.tsx +5 -7
  27. package/data/registry/react/components/code-panel/index.tsx +5 -7
  28. package/data/registry/react/components/color-picker/index.tailwind.tsx +7 -6
  29. package/data/registry/react/components/color-picker/index.tsx +7 -6
  30. package/data/registry/react/components/combobox/index.tailwind.tsx +8 -10
  31. package/data/registry/react/components/combobox/index.tsx +8 -10
  32. package/data/registry/react/components/context-menu/index.tailwind.tsx +10 -12
  33. package/data/registry/react/components/context-menu/index.tsx +10 -12
  34. package/data/registry/react/components/date-picker/index.tailwind.tsx +7 -9
  35. package/data/registry/react/components/date-picker/index.tsx +7 -9
  36. package/data/registry/react/components/dialog/index.tailwind.tsx +6 -8
  37. package/data/registry/react/components/dialog/index.tsx +6 -8
  38. package/data/registry/react/components/dropdown-menu/index.tailwind.tsx +10 -12
  39. package/data/registry/react/components/dropdown-menu/index.tsx +10 -12
  40. package/data/registry/react/components/file-upload/index.tailwind.tsx +6 -8
  41. package/data/registry/react/components/file-upload/index.tsx +6 -8
  42. package/data/registry/react/components/form/field.tailwind.tsx +2 -1
  43. package/data/registry/react/components/form/field.tsx +2 -3
  44. package/data/registry/react/components/header/index.tailwind.tsx +17 -19
  45. package/data/registry/react/components/header/index.tsx +17 -19
  46. package/data/registry/react/components/input/index.module.tsx +486 -0
  47. package/data/registry/react/components/input/index.tailwind.tsx +4 -6
  48. package/data/registry/react/components/input/index.tsx +4 -6
  49. package/data/registry/react/components/input/styles.module.css +200 -0
  50. package/data/registry/react/components/label/index.tailwind.tsx +6 -8
  51. package/data/registry/react/components/label/index.tsx +6 -8
  52. package/data/registry/react/components/markdown-editor/index.tailwind.tsx +2 -4
  53. package/data/registry/react/components/markdown-editor/index.tsx +2 -4
  54. package/data/registry/react/components/menubar/index.tailwind.tsx +2 -4
  55. package/data/registry/react/components/menubar/index.tsx +2 -4
  56. package/data/registry/react/components/numeric-input/index.tailwind.tsx +2 -4
  57. package/data/registry/react/components/numeric-input/index.tsx +2 -4
  58. package/data/registry/react/components/page-toc/index.tailwind.tsx +3 -2
  59. package/data/registry/react/components/page-toc/index.tsx +2 -3
  60. package/data/registry/react/components/pagination/index.tailwind.tsx +8 -10
  61. package/data/registry/react/components/pagination/index.tsx +8 -10
  62. package/data/registry/react/components/popover/index.tailwind.tsx +4 -6
  63. package/data/registry/react/components/popover/index.tsx +4 -6
  64. package/data/registry/react/components/progress/index.tailwind.tsx +3 -5
  65. package/data/registry/react/components/progress/index.tsx +2 -4
  66. package/data/registry/react/components/radio/index.tailwind.tsx +3 -5
  67. package/data/registry/react/components/radio/index.tsx +3 -5
  68. package/data/registry/react/components/rich-text-editor/index.tailwind.tsx +3 -5
  69. package/data/registry/react/components/rich-text-editor/index.tsx +3 -5
  70. package/data/registry/react/components/select/index.tailwind.tsx +8 -10
  71. package/data/registry/react/components/select/index.tsx +8 -10
  72. package/data/registry/react/components/separator/index.tailwind.tsx +2 -4
  73. package/data/registry/react/components/separator/index.tsx +2 -4
  74. package/data/registry/react/components/sidebar/index.tailwind.tsx +32 -43
  75. package/data/registry/react/components/sidebar/index.tsx +29 -46
  76. package/data/registry/react/components/skeleton/index.tailwind.tsx +2 -4
  77. package/data/registry/react/components/skeleton/index.tsx +2 -4
  78. package/data/registry/react/components/slider/index.tailwind.tsx +5 -7
  79. package/data/registry/react/components/slider/index.tsx +5 -7
  80. package/data/registry/react/components/spinner/index.tailwind.tsx +3 -5
  81. package/data/registry/react/components/spinner/index.tsx +2 -4
  82. package/data/registry/react/components/switch/index.tailwind.tsx +3 -5
  83. package/data/registry/react/components/switch/index.tsx +2 -4
  84. package/data/registry/react/components/tabs/index.tailwind.tsx +6 -8
  85. package/data/registry/react/components/tabs/index.tsx +6 -8
  86. package/data/registry/react/components/textarea/index.tailwind.tsx +2 -4
  87. package/data/registry/react/components/textarea/index.tsx +2 -4
  88. package/data/registry/react/components/toggle/index.tailwind.tsx +4 -6
  89. package/data/registry/react/components/toggle/index.tsx +4 -6
  90. package/data/registry/react/components/tooltip/index.tailwind.tsx +2 -4
  91. package/data/registry/react/components/tooltip/index.tsx +2 -4
  92. package/data/registry/react/lib/cn.tailwind.ts +17 -0
  93. package/data/registry/react/peer-versions.json +3 -1
  94. package/data/registry/react/registry.json +202 -43
  95. package/data/tokens/build.mjs +4 -0
  96. package/package.json +1 -1
  97. package/src/add.mjs +37 -13
  98. package/templates/ui-app-template/sh-ui.config.json +5 -0
@@ -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 = (
@@ -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
  import "./styles.css";
@@ -205,7 +206,7 @@ export function SidebarProvider({
205
206
  [state, open, setOpen, isMobile, openMobile, toggleSidebar, activePanel, setActivePanel]
206
207
  );
207
208
 
208
- const classes = ["sh-ui-sidebar-wrapper", className].filter(Boolean).join(" ");
209
+ const classes = cn("sh-ui-sidebar-wrapper", className);
209
210
 
210
211
  return (
211
212
  <SidebarContext.Provider value={value}>
@@ -288,7 +289,7 @@ export function Sidebar({
288
289
  );
289
290
 
290
291
  if (collapsible === "none") {
291
- const classes = ["sh-ui-sidebar", "sh-ui-sidebar--static", className].filter(Boolean).join(" ");
292
+ const classes = cn("sh-ui-sidebar", "sh-ui-sidebar--static", className);
292
293
  return wrap(
293
294
  <aside className={classes} data-side={side} data-variant={variant} {...props}>
294
295
  {children}
@@ -312,7 +313,7 @@ export function Sidebar({
312
313
 
313
314
  return wrap(
314
315
  <aside
315
- className={["sh-ui-sidebar", className].filter(Boolean).join(" ")}
316
+ className={cn("sh-ui-sidebar", className)}
316
317
  data-state={state}
317
318
  data-collapsible={state === "collapsed" ? collapsible : ""}
318
319
  data-variant={variant}
@@ -351,9 +352,7 @@ function MobileSidebar({
351
352
  )}
352
353
  <aside
353
354
  ref={asideRef}
354
- className={["sh-ui-sidebar", "sh-ui-sidebar--mobile", className]
355
- .filter(Boolean)
356
- .join(" ")}
355
+ className={cn("sh-ui-sidebar", "sh-ui-sidebar--mobile", className)}
357
356
  data-side={side}
358
357
  data-state={openMobile ? "open" : "closed"}
359
358
  role="dialog"
@@ -378,7 +377,7 @@ export function SidebarTrigger({ className, onClick, ...props }: SidebarTriggerP
378
377
  <button
379
378
  type="button"
380
379
  aria-label="Toggle Sidebar"
381
- className={["sh-ui-sidebar__trigger", className].filter(Boolean).join(" ")}
380
+ className={cn("sh-ui-sidebar__trigger", className)}
382
381
  onClick={(e) => {
383
382
  onClick?.(e);
384
383
  toggleSidebar();
@@ -429,7 +428,7 @@ export function SidebarPanel({ id, className, children, ...props }: SidebarPanel
429
428
  return (
430
429
  <aside
431
430
  ref={ref}
432
- className={["sh-ui-sidebar__panel", className].filter(Boolean).join(" ")}
431
+ className={cn("sh-ui-sidebar__panel", className)}
433
432
  data-state={open ? "open" : "closed"}
434
433
  role={isMobile ? "dialog" : undefined}
435
434
  aria-modal={open && isMobile ? "true" : undefined}
@@ -453,7 +452,7 @@ export function SidebarPanel({ id, className, children, ...props }: SidebarPanel
453
452
  export function SidebarPanelHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
454
453
  return (
455
454
  <div
456
- className={["sh-ui-sidebar__panel-header", className].filter(Boolean).join(" ")}
455
+ className={cn("sh-ui-sidebar__panel-header", className)}
457
456
  {...props}
458
457
  />
459
458
  );
@@ -463,7 +462,7 @@ export function SidebarPanelHeader({ className, ...props }: React.HTMLAttributes
463
462
  export function SidebarPanelContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
464
463
  return (
465
464
  <div
466
- className={["sh-ui-sidebar__panel-content", className].filter(Boolean).join(" ")}
465
+ className={cn("sh-ui-sidebar__panel-content", className)}
467
466
  {...props}
468
467
  />
469
468
  );
@@ -475,7 +474,7 @@ export function SidebarPanelContent({ className, ...props }: React.HTMLAttribute
475
474
  export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
476
475
  return (
477
476
  <main
478
- className={["sh-ui-sidebar-inset", className].filter(Boolean).join(" ")}
477
+ className={cn("sh-ui-sidebar-inset", className)}
479
478
  {...props}
480
479
  />
481
480
  );
@@ -487,7 +486,7 @@ export function SidebarInset({ className, ...props }: React.HTMLAttributes<HTMLE
487
486
  export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
488
487
  return (
489
488
  <div
490
- className={["sh-ui-sidebar__header", className].filter(Boolean).join(" ")}
489
+ className={cn("sh-ui-sidebar__header", className)}
491
490
  {...props}
492
491
  />
493
492
  );
@@ -497,7 +496,7 @@ export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTML
497
496
  export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
498
497
  return (
499
498
  <div
500
- className={["sh-ui-sidebar__footer", className].filter(Boolean).join(" ")}
499
+ className={cn("sh-ui-sidebar__footer", className)}
501
500
  {...props}
502
501
  />
503
502
  );
@@ -507,7 +506,7 @@ export function SidebarFooter({ className, ...props }: React.HTMLAttributes<HTML
507
506
  export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
508
507
  return (
509
508
  <div
510
- className={["sh-ui-sidebar__content", className].filter(Boolean).join(" ")}
509
+ className={cn("sh-ui-sidebar__content", className)}
511
510
  {...props}
512
511
  />
513
512
  );
@@ -517,7 +516,7 @@ export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTM
517
516
  export function SidebarSeparator({ className, ...props }: React.HTMLAttributes<HTMLHRElement>) {
518
517
  return (
519
518
  <hr
520
- className={["sh-ui-sidebar__separator", className].filter(Boolean).join(" ")}
519
+ className={cn("sh-ui-sidebar__separator", className)}
521
520
  {...props}
522
521
  />
523
522
  );
@@ -529,7 +528,7 @@ export function SidebarSeparator({ className, ...props }: React.HTMLAttributes<H
529
528
  export function SidebarGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
530
529
  return (
531
530
  <div
532
- className={["sh-ui-sidebar__group", className].filter(Boolean).join(" ")}
531
+ className={cn("sh-ui-sidebar__group", className)}
533
532
  {...props}
534
533
  />
535
534
  );
@@ -539,7 +538,7 @@ export function SidebarGroup({ className, ...props }: React.HTMLAttributes<HTMLD
539
538
  export function SidebarGroupLabel({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
540
539
  return (
541
540
  <div
542
- className={["sh-ui-sidebar__group-label", className].filter(Boolean).join(" ")}
541
+ className={cn("sh-ui-sidebar__group-label", className)}
543
542
  {...props}
544
543
  />
545
544
  );
@@ -549,7 +548,7 @@ export function SidebarGroupLabel({ className, ...props }: React.HTMLAttributes<
549
548
  export function SidebarGroupContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
550
549
  return (
551
550
  <div
552
- className={["sh-ui-sidebar__group-content", className].filter(Boolean).join(" ")}
551
+ className={cn("sh-ui-sidebar__group-content", className)}
553
552
  {...props}
554
553
  />
555
554
  );
@@ -561,7 +560,7 @@ export function SidebarGroupContent({ className, ...props }: React.HTMLAttribute
561
560
  export function SidebarMenu({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) {
562
561
  return (
563
562
  <ul
564
- className={["sh-ui-sidebar__menu", className].filter(Boolean).join(" ")}
563
+ className={cn("sh-ui-sidebar__menu", className)}
565
564
  {...props}
566
565
  />
567
566
  );
@@ -571,7 +570,7 @@ export function SidebarMenu({ className, ...props }: React.HTMLAttributes<HTMLUL
571
570
  export function SidebarMenuItem({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) {
572
571
  return (
573
572
  <li
574
- className={["sh-ui-sidebar__menu-item", className].filter(Boolean).join(" ")}
573
+ className={cn("sh-ui-sidebar__menu-item", className)}
575
574
  {...props}
576
575
  />
577
576
  );
@@ -637,22 +636,16 @@ export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenu
637
636
  [onClick, panelId, ctx]
638
637
  );
639
638
 
640
- const cls = [
641
- "sh-ui-sidebar__menu-button",
639
+ const cls = cn("sh-ui-sidebar__menu-button",
642
640
  `sh-ui-sidebar__menu-button--${size}`,
643
- className,
644
- ]
645
- .filter(Boolean)
646
- .join(" ");
641
+ className);
647
642
 
648
643
  if (asChild && React.isValidElement(children)) {
649
644
  const child = children as React.ReactElement<Record<string, unknown>>;
650
645
  const merged: Record<string, unknown> = {
651
646
  ...props,
652
647
  onClick: handleClick,
653
- className: [(child.props.className as string) || "", cls]
654
- .filter(Boolean)
655
- .join(" "),
648
+ className: cn((child.props.className as string) || "", cls),
656
649
  "data-active": resolvedIsActive || undefined,
657
650
  };
658
651
  return React.cloneElement(child, merged);
@@ -679,7 +672,7 @@ export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenu
679
672
  export function SidebarMenuSub({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) {
680
673
  return (
681
674
  <ul
682
- className={["sh-ui-sidebar__menu-sub", className].filter(Boolean).join(" ")}
675
+ className={cn("sh-ui-sidebar__menu-sub", className)}
683
676
  {...props}
684
677
  />
685
678
  );
@@ -689,7 +682,7 @@ export function SidebarMenuSub({ className, ...props }: React.HTMLAttributes<HTM
689
682
  export function SidebarMenuSubItem({ className, ...props }: React.HTMLAttributes<HTMLLIElement>) {
690
683
  return (
691
684
  <li
692
- className={["sh-ui-sidebar__menu-sub-item", className].filter(Boolean).join(" ")}
685
+ className={cn("sh-ui-sidebar__menu-sub-item", className)}
693
686
  {...props}
694
687
  />
695
688
  );
@@ -721,21 +714,15 @@ export const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarM
721
714
  const tocActive = useTOCActiveId();
722
715
  const resolvedIsActive =
723
716
  isActive ?? (sectionId != null ? tocActive === sectionId : undefined);
724
- const cls = [
725
- "sh-ui-sidebar__menu-sub-button",
717
+ const cls = cn("sh-ui-sidebar__menu-sub-button",
726
718
  `sh-ui-sidebar__menu-sub-button--${size}`,
727
- className,
728
- ]
729
- .filter(Boolean)
730
- .join(" ");
719
+ className);
731
720
 
732
721
  if (asChild && React.isValidElement(children)) {
733
722
  const child = children as React.ReactElement<Record<string, unknown>>;
734
723
  const merged: Record<string, unknown> = {
735
724
  ...props,
736
- className: [(child.props.className as string) || "", cls]
737
- .filter(Boolean)
738
- .join(" "),
725
+ className: cn((child.props.className as string) || "", cls),
739
726
  "data-active": resolvedIsActive || undefined,
740
727
  };
741
728
  return React.cloneElement(child, merged);
@@ -859,14 +846,10 @@ export function SidebarCollapsibleTrigger({
859
846
  }: SidebarCollapsibleTriggerProps) {
860
847
  const { open, toggle, flyoutMode, flyoutOpen } = useCollapsible();
861
848
 
862
- const cls = [
863
- "sh-ui-sidebar__menu-button",
849
+ const cls = cn("sh-ui-sidebar__menu-button",
864
850
  `sh-ui-sidebar__menu-button--${size}`,
865
851
  "sh-ui-sidebar__collapsible-trigger",
866
- className,
867
- ]
868
- .filter(Boolean)
869
- .join(" ");
852
+ className);
870
853
 
871
854
  const isOpen = flyoutMode ? flyoutOpen : open;
872
855
 
@@ -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
  /**
8
6
  * 로딩 중 콘텐츠 자리를 채우는 placeholder 박스 (Tailwind utility 변종).
9
7
  * `aria-hidden`이 기본 적용되므로 스크린리더에 노출되지 않는다.
@@ -15,7 +13,7 @@ export const Skeleton = React.forwardRef<
15
13
  <div
16
14
  ref={ref}
17
15
  aria-hidden="true"
18
- className={cx(
16
+ className={cn(
19
17
  "block w-full h-4 bg-background-muted rounded-[calc(var(--radius)-2px)] animate-[sh-ui-skeleton-pulse_1.6s_ease-in-out_infinite] motion-reduce:animate-none",
20
18
  className,
21
19
  )}
@@ -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
  /**
9
7
  * 로딩 중 콘텐츠 자리를 채우는 placeholder 박스. 폭/높이는 인라인 스타일이나
10
8
  * 클래스로 직접 지정한다. `aria-hidden`이 기본 적용되므로 스크린리더에 노출되지
@@ -17,7 +15,7 @@ export const Skeleton = React.forwardRef<
17
15
  <div
18
16
  ref={ref}
19
17
  aria-hidden="true"
20
- className={cx("sh-ui-skeleton", className)}
18
+ className={cn("sh-ui-skeleton", className)}
21
19
  {...props}
22
20
  />
23
21
  ));
@@ -2,10 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- function cx(...args: (string | undefined | false | null)[]) {
6
- return args.filter(Boolean).join(" ");
7
- }
8
5
 
6
+ import { cn } from "@SH_UI_UTILS@";
9
7
  function clamp(n: number, min: number, max: number) {
10
8
  return Math.min(max, Math.max(min, n));
11
9
  }
@@ -109,7 +107,7 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
109
107
  <div
110
108
  ref={ref}
111
109
  {...rest}
112
- className={cx(
110
+ className={cn(
113
111
  "relative w-full py-[var(--space-2)] select-none",
114
112
  disabled && "opacity-[var(--opacity-disabled)] pointer-events-none",
115
113
  className,
@@ -171,7 +169,7 @@ export const SliderTrack = React.forwardRef<HTMLDivElement, React.HTMLAttributes
171
169
  return (
172
170
  <div
173
171
  ref={mergedRef}
174
- className={cx(
172
+ className={cn(
175
173
  "relative w-full h-1.5 bg-background-muted rounded-full cursor-pointer touch-none",
176
174
  className,
177
175
  )}
@@ -196,7 +194,7 @@ export const SliderRange = React.forwardRef<HTMLDivElement, React.HTMLAttributes
196
194
  return (
197
195
  <div
198
196
  ref={ref}
199
- className={cx(
197
+ className={cn(
200
198
  "absolute top-0 left-0 h-full bg-primary rounded-full pointer-events-none",
201
199
  className,
202
200
  )}
@@ -243,7 +241,7 @@ export const SliderThumb = React.forwardRef<
243
241
  aria-valuenow={value}
244
242
  aria-disabled={disabled || undefined}
245
243
  onKeyDown={onKeyDown}
246
- className={cx(
244
+ className={cn(
247
245
  "absolute top-1/2 w-4 h-4 -ml-2 -translate-y-1/2 bg-background border-2 border-primary rounded-full shadow-[0_1px_2px_rgba(0,0,0,0.1)] cursor-grab transition-transform duration-[80ms] active:cursor-grabbing active:scale-110 active:-translate-y-1/2 focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 [@media(hover:none)_and_(pointer:coarse)]:w-5 [@media(hover:none)_and_(pointer:coarse)]:h-5 [@media(hover:none)_and_(pointer:coarse)]:-ml-2.5",
248
246
  className,
249
247
  )}
@@ -3,10 +3,8 @@
3
3
  import * as React from "react";
4
4
  import "./styles.css";
5
5
 
6
- function cx(...args: (string | undefined | false | null)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
6
 
7
+ import { cn } from "@SH_UI_UTILS@";
10
8
  function clamp(n: number, min: number, max: number) {
11
9
  return Math.min(max, Math.max(min, n));
12
10
  }
@@ -128,7 +126,7 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
128
126
  <div
129
127
  ref={ref}
130
128
  {...rest}
131
- className={cx(
129
+ className={cn(
132
130
  "sh-ui-slider",
133
131
  disabled && "sh-ui-slider--disabled",
134
132
  className,
@@ -199,7 +197,7 @@ export const SliderTrack = React.forwardRef<
199
197
  return (
200
198
  <div
201
199
  ref={mergedRef}
202
- className={cx("sh-ui-slider__track", className)}
200
+ className={cn("sh-ui-slider__track", className)}
203
201
  onPointerDown={onPointerDown}
204
202
  {...props}
205
203
  >
@@ -225,7 +223,7 @@ export const SliderRange = React.forwardRef<
225
223
  return (
226
224
  <div
227
225
  ref={ref}
228
- className={cx("sh-ui-slider__range", className)}
226
+ className={cn("sh-ui-slider__range", className)}
229
227
  style={{ width: percent, ...style }}
230
228
  {...props}
231
229
  />
@@ -291,7 +289,7 @@ export const SliderThumb = React.forwardRef<
291
289
  aria-valuenow={value}
292
290
  aria-disabled={disabled || undefined}
293
291
  onKeyDown={onKeyDown}
294
- className={cx("sh-ui-slider__thumb", className)}
292
+ className={cn("sh-ui-slider__thumb", className)}
295
293
  style={{ left: percent, ...style }}
296
294
  {...props}
297
295
  />
@@ -1,10 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
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
  const spinnerVariants = cva(
9
7
  "inline-flex items-center justify-center align-middle text-current",
10
8
  {
@@ -39,12 +37,12 @@ export const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
39
37
  role="status"
40
38
  aria-live="polite"
41
39
  aria-label={ariaLabel}
42
- className={cx(spinnerVariants({ size }), className)}
40
+ className={cn(spinnerVariants({ size }), className)}
43
41
  {...props}
44
42
  >
45
43
  <span
46
44
  aria-hidden
47
- className={cx(
45
+ className={cn(
48
46
  "inline-block w-full h-full rounded-full border-current border-t-transparent opacity-80 animate-[sh-ui-spinner-rotate_0.8s_linear_infinite] motion-reduce:[animation-duration:3s]",
49
47
  ringBorder,
50
48
  )}
@@ -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 SpinnerSize = "sm" | "md" | "lg";
9
7
 
10
8
  export interface SpinnerProps
@@ -29,7 +27,7 @@ export const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
29
27
  role="status"
30
28
  aria-live="polite"
31
29
  aria-label={ariaLabel}
32
- className={cx("sh-ui-spinner", `sh-ui-spinner--${size}`, className)}
30
+ className={cn("sh-ui-spinner", `sh-ui-spinner--${size}`, className)}
33
31
  {...props}
34
32
  >
35
33
  <span className="sh-ui-spinner__ring" aria-hidden />