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
@@ -4,11 +4,9 @@ import * as React from "react";
4
4
  import { Menu as BaseMenu } from "@base-ui/react/menu";
5
5
  import "./styles.css";
6
6
 
7
+ import { cn } from "@SH_UI_UTILS@";
7
8
  type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
8
9
 
9
- function cx(...args: (string | undefined | false | null)[]) {
10
- return args.filter(Boolean).join(" ");
11
- }
12
10
 
13
11
  /* ───────── Root ───────── */
14
12
 
@@ -23,7 +21,7 @@ export const DropdownMenuTrigger = React.forwardRef<
23
21
  return (
24
22
  <BaseMenu.Trigger
25
23
  ref={ref}
26
- className={cx("sh-ui-dm__trigger", className)}
24
+ className={cn("sh-ui-dm__trigger", className)}
27
25
  {...props}
28
26
  />
29
27
  );
@@ -63,7 +61,7 @@ export const DropdownMenuContent = React.forwardRef<
63
61
  >
64
62
  <BaseMenu.Popup
65
63
  ref={ref}
66
- className={cx("sh-ui-dm__content", className)}
64
+ className={cn("sh-ui-dm__content", className)}
67
65
  {...props}
68
66
  >
69
67
  {children}
@@ -82,7 +80,7 @@ export const DropdownMenuItem = React.forwardRef<
82
80
  return (
83
81
  <BaseMenu.Item
84
82
  ref={ref}
85
- className={cx("sh-ui-dm__item", className)}
83
+ className={cn("sh-ui-dm__item", className)}
86
84
  {...props}
87
85
  />
88
86
  );
@@ -102,7 +100,7 @@ export const DropdownMenuCheckboxItem = React.forwardRef<
102
100
  return (
103
101
  <BaseMenu.CheckboxItem
104
102
  ref={ref}
105
- className={cx("sh-ui-dm__item", "sh-ui-dm__item--check", className)}
103
+ className={cn("sh-ui-dm__item", "sh-ui-dm__item--check", className)}
106
104
  {...props}
107
105
  >
108
106
  <span className="sh-ui-dm__item-indicator" aria-hidden>
@@ -131,7 +129,7 @@ export const DropdownMenuRadioItem = React.forwardRef<
131
129
  return (
132
130
  <BaseMenu.RadioItem
133
131
  ref={ref}
134
- className={cx("sh-ui-dm__item", "sh-ui-dm__item--check", className)}
132
+ className={cn("sh-ui-dm__item", "sh-ui-dm__item--check", className)}
135
133
  {...props}
136
134
  >
137
135
  <span className="sh-ui-dm__item-indicator" aria-hidden>
@@ -153,7 +151,7 @@ export const DropdownMenuGroup = React.forwardRef<
153
151
  return (
154
152
  <BaseMenu.Group
155
153
  ref={ref}
156
- className={cx("sh-ui-dm__group", className)}
154
+ className={cn("sh-ui-dm__group", className)}
157
155
  {...props}
158
156
  />
159
157
  );
@@ -166,7 +164,7 @@ export const DropdownMenuLabel = React.forwardRef<
166
164
  return (
167
165
  <BaseMenu.GroupLabel
168
166
  ref={ref}
169
- className={cx("sh-ui-dm__label", className)}
167
+ className={cn("sh-ui-dm__label", className)}
170
168
  {...props}
171
169
  />
172
170
  );
@@ -185,7 +183,7 @@ export const DropdownMenuSeparator = React.forwardRef<
185
183
  ref={ref}
186
184
  role="separator"
187
185
  aria-orientation="horizontal"
188
- className={cx("sh-ui-dm__separator", className)}
186
+ className={cn("sh-ui-dm__separator", className)}
189
187
  {...props}
190
188
  />
191
189
  );
@@ -204,7 +202,7 @@ export const DropdownMenuSubTrigger = React.forwardRef<
204
202
  return (
205
203
  <BaseMenu.SubmenuTrigger
206
204
  ref={ref}
207
- className={cx("sh-ui-dm__item", "sh-ui-dm__sub-trigger", className)}
205
+ className={cn("sh-ui-dm__item", "sh-ui-dm__sub-trigger", className)}
208
206
  {...props}
209
207
  >
210
208
  <span className="sh-ui-dm__item-text">{children}</span>
@@ -2,10 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- function cx(...args: (string | false | undefined)[]) {
6
- return args.filter(Boolean).join(" ");
7
- }
8
5
 
6
+ import { cn } from "@SH_UI_UTILS@";
9
7
  function formatBytes(bytes: number): string {
10
8
  if (bytes < 1024) return `${bytes} B`;
11
9
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -100,7 +98,7 @@ export const FileUpload = React.forwardRef<HTMLInputElement, FileUploadProps>(
100
98
 
101
99
  return (
102
100
  <FileUploadContext.Provider value={ctx}>
103
- <div className={cx("flex flex-col gap-[var(--space-3)]", className)} style={style}>
101
+ <div className={cn("flex flex-col gap-[var(--space-3)]", className)} style={style}>
104
102
  <input
105
103
  ref={inputRef}
106
104
  id={id}
@@ -154,7 +152,7 @@ export const FileUploadDropzone = React.forwardRef<HTMLDivElement, FileUploadDro
154
152
  tabIndex={disabled ? -1 : 0}
155
153
  aria-disabled={disabled || undefined}
156
154
  data-dragging={dragging || undefined}
157
- className={cx(
155
+ className={cn(
158
156
  "relative flex flex-col items-center justify-center gap-[var(--space-2)] py-[var(--space-8)] px-[var(--space-6)] min-h-40 bg-background-subtle text-foreground-muted border-[1.5px] border-dashed border-border rounded-[var(--radius)] cursor-pointer text-center transition-[border-color,background-color,color] duration-[var(--duration-fast)] hover:border-border-strong hover:text-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 focus-visible:border-foreground motion-reduce:transition-none",
159
157
  dragging && "border-foreground bg-background-muted text-foreground",
160
158
  disabled && "opacity-[var(--opacity-disabled)] cursor-not-allowed pointer-events-none",
@@ -190,7 +188,7 @@ export const FileUploadTrigger = React.forwardRef<HTMLButtonElement, FileUploadT
190
188
  ref={ref}
191
189
  type={type ?? "button"}
192
190
  disabled={disabled || rest.disabled}
193
- className={cx(
191
+ className={cn(
194
192
  "inline-flex items-center justify-center gap-[var(--space-2)] py-[var(--space-2)] px-[var(--space-3)] text-[length:var(--text-sm)] font-medium text-foreground bg-background border border-border rounded-[calc(var(--radius)-2px)] cursor-pointer transition-[background-color,border-color] duration-[var(--duration-fast)] hover:not-disabled:bg-background-muted hover:not-disabled:border-border-strong focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed",
195
193
  className,
196
194
  )}
@@ -220,7 +218,7 @@ export const FileUploadList = React.forwardRef<HTMLUListElement, FileUploadListP
220
218
  : (children ?? files.map((f, i) => <FileUploadItem key={`${f.name}-${i}`} file={f} index={i} />));
221
219
 
222
220
  return (
223
- <ul ref={ref} className={cx("list-none m-0 p-0 flex flex-col gap-1.5", className)} {...rest}>
221
+ <ul ref={ref} className={cn("list-none m-0 p-0 flex flex-col gap-1.5", className)} {...rest}>
224
222
  {content}
225
223
  </ul>
226
224
  );
@@ -239,7 +237,7 @@ export const FileUploadItem = React.forwardRef<HTMLLIElement, FileUploadItemProp
239
237
  return (
240
238
  <li
241
239
  ref={ref}
242
- className={cx(
240
+ className={cn(
243
241
  "flex items-center gap-2.5 py-[var(--space-2)] px-[var(--space-3)] bg-background border border-border rounded-[calc(var(--radius)-2px)] text-[length:var(--text-sm)] text-foreground [&>svg]:text-foreground-muted [&>svg]:shrink-0",
244
242
  className,
245
243
  )}
@@ -3,10 +3,8 @@
3
3
  import * as React from "react";
4
4
  import "./styles.css";
5
5
 
6
- function cx(...args: (string | false | undefined)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
6
 
7
+ import { cn } from "@SH_UI_UTILS@";
10
8
  function formatBytes(bytes: number): string {
11
9
  if (bytes < 1024) return `${bytes} B`;
12
10
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -200,7 +198,7 @@ export const FileUpload = React.forwardRef<HTMLInputElement, FileUploadProps>(
200
198
 
201
199
  return (
202
200
  <FileUploadContext.Provider value={ctx}>
203
- <div className={cx("sh-ui-file-upload", className)} style={style}>
201
+ <div className={cn("sh-ui-file-upload", className)} style={style}>
204
202
  {/* 공유 네이티브 input. Trigger/Dropzone 모두 이를 통해 파일 선택을 연다. */}
205
203
  <input
206
204
  ref={inputRef}
@@ -283,7 +281,7 @@ export const FileUploadDropzone = React.forwardRef<
283
281
  tabIndex={disabled ? -1 : 0}
284
282
  aria-disabled={disabled || undefined}
285
283
  data-dragging={dragging || undefined}
286
- className={cx(
284
+ className={cn(
287
285
  "sh-ui-file-upload__dropzone",
288
286
  dragging && "sh-ui-file-upload__dropzone--drag",
289
287
  disabled && "sh-ui-file-upload__dropzone--disabled",
@@ -332,7 +330,7 @@ export const FileUploadTrigger = React.forwardRef<
332
330
  ref={ref}
333
331
  type={type ?? "button"}
334
332
  disabled={disabled || rest.disabled}
335
- className={cx("sh-ui-file-upload__trigger", className)}
333
+ className={cn("sh-ui-file-upload__trigger", className)}
336
334
  onClick={(e) => {
337
335
  // Dropzone 내부에 있을 때 상위 onClick이 중복 트리거되지 않도록 버블 차단
338
336
  e.stopPropagation();
@@ -391,7 +389,7 @@ export const FileUploadList = React.forwardRef<
391
389
  return (
392
390
  <ul
393
391
  ref={ref}
394
- className={cx("sh-ui-file-upload__list", className)}
392
+ className={cn("sh-ui-file-upload__list", className)}
395
393
  {...rest}
396
394
  >
397
395
  {content}
@@ -418,7 +416,7 @@ export const FileUploadItem = React.forwardRef<
418
416
  return (
419
417
  <li
420
418
  ref={ref}
421
- className={cx("sh-ui-file-upload__item", className)}
419
+ className={cn("sh-ui-file-upload__item", className)}
422
420
  {...rest}
423
421
  >
424
422
  {children ?? (
@@ -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 { FormContext, FieldContext, SectionContext, StepContext, DisabledContext, useFormField } from "./context";
5
6
  import type { FieldValidate, ValidateOn } from "./types";
6
7
  import { scopedPath } from "./utils";
@@ -127,7 +128,7 @@ export function FormControl({ children, valueAs = "value", render }: FormControl
127
128
  const store = React.useContext(FormContext)!;
128
129
  const field = useFormField(ctx.path);
129
130
 
130
- const describedBy = [ctx.descId, field.hasError ? ctx.errorId : null].filter(Boolean).join(" ") || undefined;
131
+ const describedBy = cn(ctx.descId, field.hasError ? ctx.errorId : null) || undefined;
131
132
 
132
133
  const ctrl: ControlProps = {
133
134
  id: ctx.id, name: ctx.path,
@@ -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 {
5
6
  FormContext,
6
7
  FieldContext,
@@ -195,9 +196,7 @@ export function FormControl({
195
196
  const field = useFormField(ctx.path);
196
197
 
197
198
  const describedBy =
198
- [ctx.descId, field.hasError ? ctx.errorId : null]
199
- .filter(Boolean)
200
- .join(" ") || undefined;
199
+ cn(ctx.descId, field.hasError ? ctx.errorId : null) || undefined;
201
200
 
202
201
  const ctrl: ControlProps = {
203
202
  id: ctx.id,
@@ -3,10 +3,8 @@
3
3
  import * as React from "react";
4
4
  import { createPortal } from "react-dom";
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
  const FOCUSABLE_SELECTOR =
11
9
  'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
12
10
 
@@ -151,7 +149,7 @@ export const Header = React.forwardRef<HTMLElement, HeaderProps>(function Header
151
149
  <HeaderContext.Provider value={ctx}>
152
150
  <header
153
151
  ref={setRefs}
154
- className={cx(
152
+ className={cn(
155
153
  "relative flex items-center gap-[var(--space-4)] h-[var(--control-md)] px-[var(--space-3)] border-b border-border transition-[transform,background-color] duration-[var(--duration-base)] [--sh-ui-header-hover-bg:var(--background-muted)] [--sh-ui-header-blur-opacity:85%] [--sh-ui-header-blur-radius:16px] motion-reduce:transition-none max-md:gap-[var(--space-2)] data-[sticky-hide][data-hidden]:-translate-y-full",
156
154
  variantClasses[variant],
157
155
  className,
@@ -169,19 +167,19 @@ export const Header = React.forwardRef<HTMLElement, HeaderProps>(function Header
169
167
 
170
168
  export const HeaderBrand = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
171
169
  function HeaderBrand({ className, ...props }, ref) {
172
- return <div ref={ref} className={cx("inline-flex items-center gap-[var(--space-2)] shrink-0", className)} {...props} />;
170
+ return <div ref={ref} className={cn("inline-flex items-center gap-[var(--space-2)] shrink-0", className)} {...props} />;
173
171
  },
174
172
  );
175
173
 
176
174
  export const HeaderLogo = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
177
175
  function HeaderLogo({ className, ...props }, ref) {
178
- return <span ref={ref} className={cx("inline-flex items-center text-foreground", className)} {...props} />;
176
+ return <span ref={ref} className={cn("inline-flex items-center text-foreground", className)} {...props} />;
179
177
  },
180
178
  );
181
179
 
182
180
  export const HeaderTitle = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
183
181
  function HeaderTitle({ className, ...props }, ref) {
184
- return <span ref={ref} className={cx("text-[length:var(--text-base)] font-bold text-foreground tracking-[-0.3px]", className)} {...props} />;
182
+ return <span ref={ref} className={cn("text-[length:var(--text-base)] font-bold text-foreground tracking-[-0.3px]", className)} {...props} />;
185
183
  },
186
184
  );
187
185
 
@@ -198,7 +196,7 @@ export const HeaderTrigger = React.forwardRef<HTMLButtonElement, React.ButtonHTM
198
196
  <button
199
197
  ref={setRefs}
200
198
  type="button"
201
- className={cx(
199
+ className={cn(
202
200
  "hidden items-center justify-center w-9 h-9 p-0 bg-transparent border-0 text-foreground rounded-[calc(var(--radius)-2px)] cursor-pointer transition-[background-color] duration-[var(--duration-fast)] hover:bg-[var(--sh-ui-header-hover-bg)] focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 max-md:inline-flex max-md:order-[-1]",
203
201
  className,
204
202
  )}
@@ -244,7 +242,7 @@ export const HeaderNav = React.forwardRef<HTMLElement, HeaderNavProps>(
244
242
  <NavLocationContext.Provider value="inline">
245
243
  <nav
246
244
  ref={ref}
247
- className={cx(
245
+ className={cn(
248
246
  "flex items-center gap-[var(--space-1)] flex-1 min-w-0 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden max-md:hidden",
249
247
  className,
250
248
  )}
@@ -293,7 +291,7 @@ export const HeaderItem = React.forwardRef<
293
291
  <a
294
292
  ref={ref}
295
293
  href={href}
296
- className={cx(
294
+ className={cn(
297
295
  "inline-flex items-center gap-[var(--space-1)] py-[var(--space-2)] px-[var(--space-3)] text-[length:var(--text-sm)] font-medium text-foreground-muted no-underline bg-transparent border-0 rounded-[calc(var(--radius)-2px)] cursor-pointer whitespace-nowrap transition-[color,background-color] duration-[var(--duration-fast)] hover:text-foreground hover:bg-[var(--sh-ui-header-hover-bg)] data-[active]:text-foreground data-[active]:font-semibold focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 motion-reduce:transition-none max-md:py-[var(--space-3)] max-md:px-[var(--space-3)]",
298
296
  className,
299
297
  )}
@@ -311,19 +309,19 @@ export const HeaderItem = React.forwardRef<
311
309
 
312
310
  export const HeaderActions = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
313
311
  function HeaderActions({ className, ...props }, ref) {
314
- return <div ref={ref} className={cx("inline-flex items-center gap-[var(--space-2)] ml-auto shrink-0", className)} {...props} />;
312
+ return <div ref={ref} className={cn("inline-flex items-center gap-[var(--space-2)] ml-auto shrink-0", className)} {...props} />;
315
313
  },
316
314
  );
317
315
 
318
316
  export const HeaderDesktopOnly = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
319
317
  function HeaderDesktopOnly({ className, ...props }, ref) {
320
- return <div ref={ref} className={cx("contents max-md:hidden", className)} {...props} />;
318
+ return <div ref={ref} className={cn("contents max-md:hidden", className)} {...props} />;
321
319
  },
322
320
  );
323
321
 
324
322
  export const HeaderMobileOnly = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
325
323
  function HeaderMobileOnly({ className, ...props }, ref) {
326
- return <div ref={ref} className={cx("hidden max-md:contents", className)} {...props} />;
324
+ return <div ref={ref} className={cn("hidden max-md:contents", className)} {...props} />;
327
325
  },
328
326
  );
329
327
 
@@ -335,12 +333,12 @@ export const HeaderNavGroup = React.forwardRef<HTMLDivElement, HeaderNavGroupPro
335
333
  function HeaderNavGroup({ className, label, children, ...props }, ref) {
336
334
  const location = React.useContext(NavLocationContext);
337
335
  if (location === "inline") {
338
- return <div ref={ref} className={cx("contents", className)} {...props}>{children}</div>;
336
+ return <div ref={ref} className={cn("contents", className)} {...props}>{children}</div>;
339
337
  }
340
338
  return (
341
339
  <div
342
340
  ref={ref}
343
- className={cx("flex flex-col mt-[var(--space-3)] first:mt-0", className)}
341
+ className={cn("flex flex-col mt-[var(--space-3)] first:mt-0", className)}
344
342
  role="group"
345
343
  aria-label={typeof label === "string" ? label : undefined}
346
344
  {...props}
@@ -407,7 +405,7 @@ export function HeaderMenu({ children, className, defaultOpen = false }: { child
407
405
  <MenuContext.Provider value={ctx}>
408
406
  <div
409
407
  ref={containerRef}
410
- className={cx(
408
+ className={cn(
411
409
  "relative",
412
410
  location === "inline" ? "inline-block" : "flex flex-col",
413
411
  className,
@@ -438,7 +436,7 @@ export const HeaderMenuTrigger = React.forwardRef<HTMLButtonElement, React.Butto
438
436
  aria-expanded={open}
439
437
  aria-controls={contentId}
440
438
  data-open={open ? "" : undefined}
441
- className={cx(
439
+ className={cn(
442
440
  "inline-flex items-center gap-[var(--space-1)] py-[var(--space-2)] px-[var(--space-3)] text-[length:var(--text-sm)] font-medium text-foreground-muted bg-transparent border-0 rounded-[calc(var(--radius)-2px)] cursor-pointer whitespace-nowrap transition-[color,background-color] duration-[var(--duration-fast)] hover:text-foreground hover:bg-[var(--sh-ui-header-hover-bg)] data-[open]:text-foreground data-[open]:bg-[var(--sh-ui-header-hover-bg)] focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 motion-reduce:transition-none",
443
441
  location === "drawer" && "max-md:justify-between max-md:w-full max-md:py-[var(--space-3)] max-md:px-[var(--space-3)]",
444
442
  className,
@@ -471,7 +469,7 @@ export const HeaderMenuContent = React.forwardRef<HTMLDivElement, React.HTMLAttr
471
469
  aria-labelledby={triggerId}
472
470
  data-open={open ? "" : undefined}
473
471
  hidden={!open}
474
- className={cx(
472
+ className={cn(
475
473
  "max-md:flex max-md:flex-col max-md:py-[var(--space-1)] max-md:pl-[var(--space-4)] max-md:gap-px max-md:[&[hidden]]:hidden",
476
474
  className,
477
475
  )}
@@ -513,7 +511,7 @@ export const HeaderMenuContent = React.forwardRef<HTMLDivElement, React.HTMLAttr
513
511
  role="menu"
514
512
  aria-labelledby={triggerId}
515
513
  data-open=""
516
- className={cx(
514
+ className={cn(
517
515
  "z-[var(--z-dropdown,50)] p-[var(--space-1)] bg-background border border-border rounded-[var(--radius)] shadow-[0_8px_24px_-8px_rgba(0,0,0,0.18)] flex flex-col gap-px text-foreground",
518
516
  className,
519
517
  )}
@@ -4,10 +4,8 @@ import * as React from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import "./styles.css";
6
6
 
7
- function cx(...args: (string | undefined | false | null)[]) {
8
- return args.filter(Boolean).join(" ");
9
- }
10
7
 
8
+ import { cn } from "@SH_UI_UTILS@";
11
9
  const FOCUSABLE_SELECTOR =
12
10
  'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
13
11
 
@@ -264,7 +262,7 @@ export const Header = React.forwardRef<HTMLElement, HeaderProps>(function Header
264
262
  <HeaderContext.Provider value={ctx}>
265
263
  <header
266
264
  ref={setRefs}
267
- className={cx("sh-ui-header", `sh-ui-header--${variant}`, className)}
265
+ className={cn("sh-ui-header", `sh-ui-header--${variant}`, className)}
268
266
  data-drawer-open={open ? "" : undefined}
269
267
  data-sticky-hide={stickyHide ? "" : undefined}
270
268
  data-hidden={hidden ? "" : undefined}
@@ -282,21 +280,21 @@ export const HeaderBrand = React.forwardRef<
282
280
  HTMLDivElement,
283
281
  React.HTMLAttributes<HTMLDivElement>
284
282
  >(function HeaderBrand({ className, ...props }, ref) {
285
- return <div ref={ref} className={cx("sh-ui-header__brand", className)} {...props} />;
283
+ return <div ref={ref} className={cn("sh-ui-header__brand", className)} {...props} />;
286
284
  });
287
285
 
288
286
  export const HeaderLogo = React.forwardRef<
289
287
  HTMLSpanElement,
290
288
  React.HTMLAttributes<HTMLSpanElement>
291
289
  >(function HeaderLogo({ className, ...props }, ref) {
292
- return <span ref={ref} className={cx("sh-ui-header__logo", className)} {...props} />;
290
+ return <span ref={ref} className={cn("sh-ui-header__logo", className)} {...props} />;
293
291
  });
294
292
 
295
293
  export const HeaderTitle = React.forwardRef<
296
294
  HTMLSpanElement,
297
295
  React.HTMLAttributes<HTMLSpanElement>
298
296
  >(function HeaderTitle({ className, ...props }, ref) {
299
- return <span ref={ref} className={cx("sh-ui-header__title", className)} {...props} />;
297
+ return <span ref={ref} className={cn("sh-ui-header__title", className)} {...props} />;
300
298
  });
301
299
 
302
300
  /* ───────── Trigger ─────────
@@ -322,7 +320,7 @@ export const HeaderTrigger = React.forwardRef<
322
320
  <button
323
321
  ref={setRefs}
324
322
  type="button"
325
- className={cx("sh-ui-header__trigger", className)}
323
+ className={cn("sh-ui-header__trigger", className)}
326
324
  aria-label={open ? "메뉴 닫기" : "메뉴 열기"}
327
325
  aria-expanded={open}
328
326
  data-open={open ? "" : undefined}
@@ -399,7 +397,7 @@ export const HeaderNav = React.forwardRef<HTMLElement, HeaderNavProps>(
399
397
  return (
400
398
  <NavMatchContext.Provider value={navMatch}>
401
399
  <NavLocationContext.Provider value="inline">
402
- <nav ref={ref} className={cx("sh-ui-header__nav", className)} {...props}>
400
+ <nav ref={ref} className={cn("sh-ui-header__nav", className)} {...props}>
403
401
  {children}
404
402
  </nav>
405
403
  </NavLocationContext.Provider>
@@ -457,7 +455,7 @@ export const HeaderItem = React.forwardRef<
457
455
  <a
458
456
  ref={ref}
459
457
  href={href}
460
- className={cx("sh-ui-header__item", className)}
458
+ className={cn("sh-ui-header__item", className)}
461
459
  data-active={computedActive ? "" : undefined}
462
460
  aria-current={computedActive ? "page" : undefined}
463
461
  onClick={(e) => {
@@ -478,7 +476,7 @@ export const HeaderActions = React.forwardRef<
478
476
  React.HTMLAttributes<HTMLDivElement>
479
477
  >(function HeaderActions({ className, ...props }, ref) {
480
478
  return (
481
- <div ref={ref} className={cx("sh-ui-header__actions", className)} {...props} />
479
+ <div ref={ref} className={cn("sh-ui-header__actions", className)} {...props} />
482
480
  );
483
481
  });
484
482
 
@@ -493,7 +491,7 @@ export const HeaderDesktopOnly = React.forwardRef<
493
491
  React.HTMLAttributes<HTMLDivElement>
494
492
  >(function HeaderDesktopOnly({ className, ...props }, ref) {
495
493
  return (
496
- <div ref={ref} className={cx("sh-ui-header__desktop-only", className)} {...props} />
494
+ <div ref={ref} className={cn("sh-ui-header__desktop-only", className)} {...props} />
497
495
  );
498
496
  });
499
497
 
@@ -503,7 +501,7 @@ export const HeaderMobileOnly = React.forwardRef<
503
501
  React.HTMLAttributes<HTMLDivElement>
504
502
  >(function HeaderMobileOnly({ className, ...props }, ref) {
505
503
  return (
506
- <div ref={ref} className={cx("sh-ui-header__mobile-only", className)} {...props} />
504
+ <div ref={ref} className={cn("sh-ui-header__mobile-only", className)} {...props} />
507
505
  );
508
506
  });
509
507
 
@@ -524,7 +522,7 @@ export const HeaderNavGroup = React.forwardRef<HTMLDivElement, HeaderNavGroupPro
524
522
  return (
525
523
  <div
526
524
  ref={ref}
527
- className={cx("sh-ui-header__group sh-ui-header__group--inline", className)}
525
+ className={cn("sh-ui-header__group sh-ui-header__group--inline", className)}
528
526
  {...props}
529
527
  >
530
528
  {children}
@@ -534,7 +532,7 @@ export const HeaderNavGroup = React.forwardRef<HTMLDivElement, HeaderNavGroupPro
534
532
  return (
535
533
  <div
536
534
  ref={ref}
537
- className={cx("sh-ui-header__group sh-ui-header__group--drawer", className)}
535
+ className={cn("sh-ui-header__group sh-ui-header__group--drawer", className)}
538
536
  role="group"
539
537
  aria-label={typeof label === "string" ? label : undefined}
540
538
  {...props}
@@ -626,7 +624,7 @@ export function HeaderMenu({
626
624
  <MenuContext.Provider value={ctx}>
627
625
  <div
628
626
  ref={containerRef}
629
- className={cx(
627
+ className={cn(
630
628
  "sh-ui-header__menu",
631
629
  `sh-ui-header__menu--${location}`,
632
630
  open && "is-open",
@@ -665,7 +663,7 @@ export const HeaderMenuTrigger = React.forwardRef<
665
663
  aria-expanded={open}
666
664
  aria-controls={contentId}
667
665
  data-open={open ? "" : undefined}
668
- className={cx("sh-ui-header__menu-trigger", className)}
666
+ className={cn("sh-ui-header__menu-trigger", className)}
669
667
  onClick={(e) => {
670
668
  setOpen(!open);
671
669
  onClick?.(e);
@@ -704,7 +702,7 @@ export const HeaderMenuContent = React.forwardRef<
704
702
  aria-labelledby={triggerId}
705
703
  data-open={open ? "" : undefined}
706
704
  hidden={!open}
707
- className={cx("sh-ui-header__menu-content", className)}
705
+ className={cn("sh-ui-header__menu-content", className)}
708
706
  style={style}
709
707
  {...props}
710
708
  >
@@ -754,7 +752,7 @@ export const HeaderMenuContent = React.forwardRef<
754
752
  role="menu"
755
753
  aria-labelledby={triggerId}
756
754
  data-open=""
757
- className={cx("sh-ui-header__menu-content sh-ui-header__menu-content--portal", className)}
755
+ className={cn("sh-ui-header__menu-content sh-ui-header__menu-content--portal", className)}
758
756
  style={{
759
757
  position: "absolute",
760
758
  top: pos.top,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
+ import { cn } from "@SH_UI_UTILS@";
5
6
  export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix"> {
6
7
  /** input 우측에 부착할 보조 노드(아이콘·단위·버튼 등). 더 많은 슬롯이 필요하면 InputGroup 사용. */
7
8
  suffix?: React.ReactNode;
@@ -9,9 +10,6 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
9
10
  prefix?: React.ReactNode;
10
11
  }
11
12
 
12
- function cx(...args: (string | undefined | null | false)[]) {
13
- return args.filter(Boolean).join(" ");
14
- }
15
13
 
16
14
  /* ───────── Base utility 묶음 (반복 줄이기) ───────── */
17
15
 
@@ -69,7 +67,7 @@ export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
69
67
  <InputGroupContext.Provider value={{ inGroup: true }}>
70
68
  <div
71
69
  ref={mergedRef}
72
- className={cx(baseGroupClasses, className)}
70
+ className={cn(baseGroupClasses, className)}
73
71
  data-disabled={disabled || undefined}
74
72
  aria-invalid={ariaInvalid}
75
73
  onClick={handleClick}
@@ -91,7 +89,7 @@ export const InputAdornment = React.forwardRef<HTMLSpanElement, InputAdornmentPr
91
89
  ({ className, interactive, ...props }, ref) => (
92
90
  <span
93
91
  ref={ref}
94
- className={cx(
92
+ className={cn(
95
93
  "inline-flex items-center justify-center flex-none text-foreground-muted px-[var(--space-1)] data-[interactive]:p-0",
96
94
  className,
97
95
  )}
@@ -112,7 +110,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
112
110
  <input
113
111
  ref={ref}
114
112
  type={type}
115
- className={cx(
113
+ className={cn(
116
114
  baseInputClasses,
117
115
  inGroupOverrides,
118
116
  !!prefix && "pl-[var(--space-10)]",
@@ -3,6 +3,7 @@
3
3
  import * as React from "react";
4
4
  import "./styles.css";
5
5
 
6
+ import { cn } from "@SH_UI_UTILS@";
6
7
  export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix"> {
7
8
  /** input 우측에 부착할 보조 노드(아이콘·단위·버튼 등). 더 많은 슬롯이 필요하면 InputGroup 사용. */
8
9
  suffix?: React.ReactNode;
@@ -10,9 +11,6 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
10
11
  prefix?: React.ReactNode;
11
12
  }
12
13
 
13
- function cx(...args: (string | undefined | null | false)[]) {
14
- return args.filter(Boolean).join(" ");
15
- }
16
14
 
17
15
  /* ───────── InputGroup + InputAdornment (compound) ─────────
18
16
  * <InputGroup>
@@ -87,7 +85,7 @@ export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
87
85
  <InputGroupContext.Provider value={{ inGroup: true }}>
88
86
  <div
89
87
  ref={mergedRef}
90
- className={cx("sh-ui-input-group", className)}
88
+ className={cn("sh-ui-input-group", className)}
91
89
  data-disabled={disabled || undefined}
92
90
  aria-invalid={ariaInvalid}
93
91
  onClick={handleClick}
@@ -123,7 +121,7 @@ export const InputAdornment = React.forwardRef<
123
121
  return (
124
122
  <span
125
123
  ref={ref}
126
- className={cx("sh-ui-input-group__adornment", className)}
124
+ className={cn("sh-ui-input-group__adornment", className)}
127
125
  data-interactive={interactive || undefined}
128
126
  {...props}
129
127
  />
@@ -143,7 +141,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
143
141
  <input
144
142
  ref={ref}
145
143
  type={type}
146
- className={cx(
144
+ className={cn(
147
145
  "sh-ui-input",
148
146
  !!prefix && "sh-ui-input--with-prefix",
149
147
  !!suffix && "sh-ui-input--with-suffix",