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
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
+ import { cn } from "@SH_UI_UTILS@";
3
4
  export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
4
5
  /**
5
6
  * 필수 필드 표시. `true`면 LabelTitle 뒤에 `*` 표시.
@@ -10,15 +11,12 @@ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement>
10
11
  isRequired?: boolean;
11
12
  }
12
13
 
13
- function cx(...args: (string | undefined | false)[]) {
14
- return args.filter(Boolean).join(" ");
15
- }
16
14
 
17
15
  export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
18
16
  ({ className, children, isRequired, ...props }, ref) => (
19
17
  <label
20
18
  ref={ref}
21
- className={cx(
19
+ className={cn(
22
20
  "flex flex-col gap-0.5 text-[length:var(--text-sm)] font-medium leading-snug text-foreground cursor-pointer select-none not-has-[[data-sh-ui-label-part]]:block",
23
21
  // 필수 표시 — title 이 있으면 title 뒤, 없으면 label 뒤에 * 부착
24
22
  isRequired &&
@@ -38,7 +36,7 @@ export function LabelTitle({ className, ...props }: React.HTMLAttributes<HTMLSpa
38
36
  return (
39
37
  <span
40
38
  data-sh-ui-label-part="title"
41
- className={cx("font-semibold text-[length:var(--text-sm)] text-foreground", className)}
39
+ className={cn("font-semibold text-[length:var(--text-sm)] text-foreground", className)}
42
40
  {...props}
43
41
  />
44
42
  );
@@ -48,7 +46,7 @@ export function LabelSubtitle({ className, ...props }: React.HTMLAttributes<HTML
48
46
  return (
49
47
  <span
50
48
  data-sh-ui-label-part="subtitle"
51
- className={cx("font-normal text-[0.8125rem] text-foreground", className)}
49
+ className={cn("font-normal text-[0.8125rem] text-foreground", className)}
52
50
  {...props}
53
51
  />
54
52
  );
@@ -58,7 +56,7 @@ export function LabelDescription({ className, ...props }: React.HTMLAttributes<H
58
56
  return (
59
57
  <p
60
58
  data-sh-ui-label-part="description"
61
- className={cx("m-0 font-normal text-[0.8125rem] leading-snug text-foreground-muted", className)}
59
+ className={cn("m-0 font-normal text-[0.8125rem] leading-snug text-foreground-muted", className)}
62
60
  {...props}
63
61
  />
64
62
  );
@@ -68,7 +66,7 @@ export function LabelCaption({ className, ...props }: React.HTMLAttributes<HTMLP
68
66
  return (
69
67
  <p
70
68
  data-sh-ui-label-part="caption"
71
- className={cx(
69
+ className={cn(
72
70
  "m-0 font-normal text-[length:var(--text-xs)] leading-tight text-[var(--foreground-subtle,var(--foreground-muted))] opacity-75",
73
71
  className,
74
72
  )}
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import "./styles.css";
3
3
 
4
+ import { cn } from "@SH_UI_UTILS@";
4
5
  export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
5
6
  /**
6
7
  * 필수 필드 표시. `true`면 `::after`로 `*` 표시가 붙는다.
@@ -11,9 +12,6 @@ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement>
11
12
  isRequired?: boolean;
12
13
  }
13
14
 
14
- function cx(...args: (string | undefined | false)[]) {
15
- return args.filter(Boolean).join(" ");
16
- }
17
15
 
18
16
  /**
19
17
  * 폼 컨트롤과 1:1로 연결되는 레이블. `htmlFor`로 컨트롤의 `id`와 매칭하거나
@@ -23,7 +21,7 @@ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
23
21
  ({ className, children, isRequired, ...props }, ref) => (
24
22
  <label
25
23
  ref={ref}
26
- className={cx("sh-ui-label", className)}
24
+ className={cn("sh-ui-label", className)}
27
25
  data-required={isRequired || undefined}
28
26
  {...props}
29
27
  >
@@ -35,20 +33,20 @@ Label.displayName = "Label";
35
33
 
36
34
  /** Label 안의 주 라벨 텍스트. 구조적 그룹핑이 필요할 때 Label과 함께 사용. */
37
35
  export function LabelTitle({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
38
- return <span className={cx("sh-ui-label__title", className)} {...props} />;
36
+ return <span className={cn("sh-ui-label__title", className)} {...props} />;
39
37
  }
40
38
 
41
39
  /** 라벨 옆에 약하게 표시되는 보조 텍스트(예: "선택 사항"). */
42
40
  export function LabelSubtitle({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
43
- return <span className={cx("sh-ui-label__subtitle", className)} {...props} />;
41
+ return <span className={cn("sh-ui-label__subtitle", className)} {...props} />;
44
42
  }
45
43
 
46
44
  /** 라벨 아래에 붙는 안내 문구. 컨트롤과 `aria-describedby`로 연결할 것. */
47
45
  export function LabelDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
48
- return <p className={cx("sh-ui-label__description", className)} {...props} />;
46
+ return <p className={cn("sh-ui-label__description", className)} {...props} />;
49
47
  }
50
48
 
51
49
  /** 라벨 아래의 보조 캡션(예: 입력 형식 예시, 글자 수 제한). */
52
50
  export function LabelCaption({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
53
- return <p className={cx("sh-ui-label__caption", className)} {...props} />;
51
+ return <p className={cn("sh-ui-label__caption", className)} {...props} />;
54
52
  }
@@ -5,6 +5,7 @@ import ReactMarkdown from "react-markdown";
5
5
  import remarkGfm from "remark-gfm";
6
6
  import { CodeEditor } from "../code-editor";
7
7
 
8
+ import { cn } from "@SH_UI_UTILS@";
8
9
  export interface MarkdownEditorProps {
9
10
  value?: string;
10
11
  defaultValue?: string;
@@ -19,9 +20,6 @@ export interface MarkdownEditorProps {
19
20
  "aria-label"?: string;
20
21
  }
21
22
 
22
- function cx(...args: (string | undefined | false | null)[]) {
23
- return args.filter(Boolean).join(" ");
24
- }
25
23
 
26
24
  /**
27
25
  * 마크다운 에디터 (Tailwind 변종) — react-markdown 의 출력 HTML 트리에 대한
@@ -50,7 +48,7 @@ export function MarkdownEditor({
50
48
 
51
49
  return (
52
50
  <div
53
- className={cx("grid gap-[var(--space-3)]", layoutClass, className)}
51
+ className={cn("grid gap-[var(--space-3)]", layoutClass, className)}
54
52
  data-readonly={readOnly || undefined}
55
53
  >
56
54
  <div className="min-w-0">
@@ -6,6 +6,7 @@ import remarkGfm from "remark-gfm";
6
6
  import { CodeEditor } from "../code-editor";
7
7
  import "./styles.css";
8
8
 
9
+ import { cn } from "@SH_UI_UTILS@";
9
10
  export interface MarkdownEditorProps {
10
11
  /**
11
12
  * Controlled — 현재 마크다운. 명시 시 외부 상태가 진실원천.
@@ -42,9 +43,6 @@ export interface MarkdownEditorProps {
42
43
  "aria-label"?: string;
43
44
  }
44
45
 
45
- function cx(...args: (string | undefined | false | null)[]) {
46
- return args.filter(Boolean).join(" ");
47
- }
48
46
 
49
47
  /**
50
48
  * 마크다운 에디터 — CodeEditor(소스) + react-markdown(라이브 프리뷰)의 합성.
@@ -79,7 +77,7 @@ export function MarkdownEditor({
79
77
 
80
78
  return (
81
79
  <div
82
- className={cx(
80
+ className={cn(
83
81
  "sh-ui-md-editor",
84
82
  preview && `sh-ui-md-editor--${previewPosition}`,
85
83
  !preview && "sh-ui-md-editor--no-preview",
@@ -1,11 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { Menubar as BaseMenubar } from "@base-ui/react/menubar";
3
3
 
4
+ import { cn } from "@SH_UI_UTILS@";
4
5
  type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
5
6
 
6
- function cx(...args: (string | undefined | false | null)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
7
 
10
8
  /**
11
9
  * 상단 앱 메뉴바 (Tailwind 변종). DropdownMenu 와 함께 사용 — DropdownMenu 의
@@ -22,7 +20,7 @@ export const Menubar = React.forwardRef<
22
20
  return (
23
21
  <BaseMenubar
24
22
  ref={ref}
25
- className={cx(
23
+ className={cn(
26
24
  "inline-flex items-center gap-[var(--space-1)] p-[var(--space-1)] bg-background border border-border rounded-[var(--radius)] shadow-[0_1px_2px_rgba(0,0,0,0.04)]",
27
25
  className,
28
26
  )}
@@ -2,11 +2,9 @@ import * as React from "react";
2
2
  import { Menubar as BaseMenubar } from "@base-ui/react/menubar";
3
3
  import "./styles.css";
4
4
 
5
+ import { cn } from "@SH_UI_UTILS@";
5
6
  type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
6
7
 
7
- function cx(...args: (string | undefined | false | null)[]) {
8
- return args.filter(Boolean).join(" ");
9
- }
10
8
 
11
9
  /**
12
10
  * 상단 앱 메뉴바(파일/편집/보기 등). 내부에 DropdownMenu를 나란히 배치하여
@@ -27,7 +25,7 @@ export const Menubar = React.forwardRef<
27
25
  return (
28
26
  <BaseMenubar
29
27
  ref={ref}
30
- className={cx("sh-ui-menubar", className)}
28
+ className={cn("sh-ui-menubar", className)}
31
29
  {...props}
32
30
  />
33
31
  );
@@ -2,10 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- function cx(...args: (string | undefined | null | false)[]) {
6
- return args.filter(Boolean).join(" ");
7
- }
8
5
 
6
+ import { cn } from "@SH_UI_UTILS@";
9
7
  export interface NumericInputProps
10
8
  extends Omit<
11
9
  React.InputHTMLAttributes<HTMLInputElement>,
@@ -59,7 +57,7 @@ export const NumericInput = React.forwardRef<HTMLInputElement, NumericInputProps
59
57
  ref={ref}
60
58
  type="text"
61
59
  inputMode="decimal"
62
- className={cx(inputClasses, className)}
60
+ className={cn(inputClasses, className)}
63
61
  value={buffer}
64
62
  onChange={(e) => {
65
63
  const raw = e.target.value;
@@ -3,10 +3,8 @@
3
3
  import * as React from "react";
4
4
  import "./styles.css";
5
5
 
6
- function cx(...args: (string | undefined | null | false)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
6
 
7
+ import { cn } from "@SH_UI_UTILS@";
10
8
  export interface NumericInputProps
11
9
  extends Omit<
12
10
  React.InputHTMLAttributes<HTMLInputElement>,
@@ -93,7 +91,7 @@ export const NumericInput = React.forwardRef<HTMLInputElement, NumericInputProps
93
91
  ref={ref}
94
92
  type="text"
95
93
  inputMode="decimal"
96
- className={cx("sh-ui-numeric-input__input", className)}
94
+ className={cn("sh-ui-numeric-input__input", className)}
97
95
  value={buffer}
98
96
  onChange={(e) => {
99
97
  const raw = e.target.value;
@@ -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 type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
6
7
 
7
8
  export interface PageTOCProps {
@@ -120,7 +121,7 @@ export function PageTOC({
120
121
 
121
122
  return (
122
123
  <nav
123
- className={cx(
124
+ className={cn(
124
125
  "fixed top-20 right-6 w-56 max-h-[calc(100vh-7rem)] overflow-y-auto pl-4 pr-2 py-3 border-l border-border text-[0.8125rem] z-[5] max-[80rem]:hidden",
125
126
  className,
126
127
  )}
@@ -135,7 +136,7 @@ export function PageTOC({
135
136
  <a
136
137
  href={`#${item.id}`}
137
138
  onClick={(e) => handleClick(e, item.id)}
138
- className={cx(linkBase, linkClassesForLevel(item.level))}
139
+ className={cn(linkBase, linkClassesForLevel(item.level))}
139
140
  data-active={activeId === item.id ? "true" : undefined}
140
141
  aria-current={activeId === item.id ? "true" : undefined}
141
142
  >
@@ -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 "./styles.css";
5
6
 
6
7
  export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
@@ -52,8 +53,6 @@ interface TocItem {
52
53
  level: HeadingLevel;
53
54
  }
54
55
 
55
- const cx = (...args: (string | undefined | false | null)[]) =>
56
- args.filter(Boolean).join(" ");
57
56
 
58
57
  /**
59
58
  * 페이지 내 자동 목차 (On this page).
@@ -151,7 +150,7 @@ export function PageTOC({
151
150
 
152
151
  return (
153
152
  <nav
154
- className={cx("sh-ui-page-toc", className)}
153
+ className={cn("sh-ui-page-toc", className)}
155
154
  aria-label={typeof label === "string" ? label : "목차"}
156
155
  >
157
156
  <div className="sh-ui-page-toc__label">{label}</div>
@@ -1,16 +1,14 @@
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 const Pagination = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLElement>>(
8
6
  function Pagination({ className, ...props }, ref) {
9
7
  return (
10
8
  <nav
11
9
  ref={ref}
12
10
  aria-label="Pagination"
13
- className={cx("flex justify-center text-[length:var(--text-sm)] text-foreground", className)}
11
+ className={cn("flex justify-center text-[length:var(--text-sm)] text-foreground", className)}
14
12
  {...props}
15
13
  />
16
14
  );
@@ -22,7 +20,7 @@ export const PaginationContent = React.forwardRef<HTMLUListElement, React.HTMLAt
22
20
  return (
23
21
  <ul
24
22
  ref={ref}
25
- className={cx("flex flex-wrap items-center gap-1 m-0 p-0 list-none", className)}
23
+ className={cn("flex flex-wrap items-center gap-1 m-0 p-0 list-none", className)}
26
24
  {...props}
27
25
  />
28
26
  );
@@ -31,7 +29,7 @@ export const PaginationContent = React.forwardRef<HTMLUListElement, React.HTMLAt
31
29
 
32
30
  export const PaginationItem = React.forwardRef<HTMLLIElement, React.LiHTMLAttributes<HTMLLIElement>>(
33
31
  function PaginationItem({ className, ...props }, ref) {
34
- return <li ref={ref} className={cx("inline-flex items-center", className)} {...props} />;
32
+ return <li ref={ref} className={cn("inline-flex items-center", className)} {...props} />;
35
33
  },
36
34
  );
37
35
 
@@ -51,7 +49,7 @@ export const PaginationLink = React.forwardRef<HTMLAnchorElement, PaginationLink
51
49
  aria-current={isActive ? "page" : undefined}
52
50
  data-active={isActive ? "" : undefined}
53
51
  data-size={size}
54
- className={cx(linkBase, className)}
52
+ className={cn(linkBase, className)}
55
53
  {...props}
56
54
  />
57
55
  );
@@ -61,7 +59,7 @@ export const PaginationLink = React.forwardRef<HTMLAnchorElement, PaginationLink
61
59
  export const PaginationPrevious = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(
62
60
  function PaginationPrevious({ className, children, ...props }, ref) {
63
61
  return (
64
- <PaginationLink ref={ref} aria-label="이전 페이지" className={cx("px-2.5", className)} {...props}>
62
+ <PaginationLink ref={ref} aria-label="이전 페이지" className={cn("px-2.5", className)} {...props}>
65
63
  <ChevronLeftIcon />
66
64
  {children ?? <span>이전</span>}
67
65
  </PaginationLink>
@@ -72,7 +70,7 @@ export const PaginationPrevious = React.forwardRef<HTMLAnchorElement, Pagination
72
70
  export const PaginationNext = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(
73
71
  function PaginationNext({ className, children, ...props }, ref) {
74
72
  return (
75
- <PaginationLink ref={ref} aria-label="다음 페이지" className={cx("px-2.5", className)} {...props}>
73
+ <PaginationLink ref={ref} aria-label="다음 페이지" className={cn("px-2.5", className)} {...props}>
76
74
  {children ?? <span>다음</span>}
77
75
  <ChevronRightIcon />
78
76
  </PaginationLink>
@@ -87,7 +85,7 @@ export const PaginationEllipsis = React.forwardRef<HTMLSpanElement, React.HTMLAt
87
85
  ref={ref}
88
86
  role="presentation"
89
87
  aria-hidden="true"
90
- className={cx("inline-flex items-center justify-center w-9 h-9 text-foreground-muted", className)}
88
+ className={cn("inline-flex items-center justify-center w-9 h-9 text-foreground-muted", className)}
91
89
  {...props}
92
90
  >
93
91
  <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden>
@@ -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
  /* ───────── Pagination (nav) ─────────
9
7
  * 시맨틱: <nav aria-label="Pagination"><ul>...</ul></nav>.
10
8
  * 현재 페이지 링크에 aria-current="page"를 부여해 스크린리더가 위치를 읽게 한다.
@@ -22,7 +20,7 @@ export const Pagination = React.forwardRef<
22
20
  <nav
23
21
  ref={ref}
24
22
  aria-label="Pagination"
25
- className={cx("sh-ui-pagination", className)}
23
+ className={cn("sh-ui-pagination", className)}
26
24
  {...props}
27
25
  />
28
26
  );
@@ -38,7 +36,7 @@ export const PaginationContent = React.forwardRef<
38
36
  return (
39
37
  <ul
40
38
  ref={ref}
41
- className={cx("sh-ui-pagination__content", className)}
39
+ className={cn("sh-ui-pagination__content", className)}
42
40
  {...props}
43
41
  />
44
42
  );
@@ -54,7 +52,7 @@ export const PaginationItem = React.forwardRef<
54
52
  return (
55
53
  <li
56
54
  ref={ref}
57
- className={cx("sh-ui-pagination__item", className)}
55
+ className={cn("sh-ui-pagination__item", className)}
58
56
  {...props}
59
57
  />
60
58
  );
@@ -98,7 +96,7 @@ export const PaginationLink = React.forwardRef<
98
96
  aria-current={isActive ? "page" : undefined}
99
97
  data-active={isActive ? "" : undefined}
100
98
  data-size={size}
101
- className={cx("sh-ui-pagination__link", className)}
99
+ className={cn("sh-ui-pagination__link", className)}
102
100
  {...props}
103
101
  />
104
102
  );
@@ -117,7 +115,7 @@ export const PaginationPrevious = React.forwardRef<
117
115
  <PaginationLink
118
116
  ref={ref}
119
117
  aria-label="이전 페이지"
120
- className={cx("sh-ui-pagination__nav", className)}
118
+ className={cn("sh-ui-pagination__nav", className)}
121
119
  {...props}
122
120
  >
123
121
  <ChevronLeftIcon />
@@ -135,7 +133,7 @@ export const PaginationNext = React.forwardRef<
135
133
  <PaginationLink
136
134
  ref={ref}
137
135
  aria-label="다음 페이지"
138
- className={cx("sh-ui-pagination__nav", className)}
136
+ className={cn("sh-ui-pagination__nav", className)}
139
137
  {...props}
140
138
  >
141
139
  {children ?? <span>다음</span>}
@@ -156,7 +154,7 @@ export const PaginationEllipsis = React.forwardRef<
156
154
  ref={ref}
157
155
  role="presentation"
158
156
  aria-hidden="true"
159
- className={cx("sh-ui-pagination__ellipsis", className)}
157
+ className={cn("sh-ui-pagination__ellipsis", className)}
160
158
  {...props}
161
159
  >
162
160
  <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden>
@@ -1,11 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { Popover as BasePopover } from "@base-ui/react/popover";
3
3
 
4
+ import { cn } from "@SH_UI_UTILS@";
4
5
  type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
5
6
 
6
- function cx(...args: (string | undefined | false)[]) {
7
- return args.filter(Boolean).join(" ");
8
- }
9
7
 
10
8
  export const Popover = BasePopover.Root;
11
9
  export const PopoverTrigger = BasePopover.Trigger;
@@ -35,7 +33,7 @@ export const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentPro
35
33
  >
36
34
  <BasePopover.Popup
37
35
  ref={ref}
38
- className={cx(
36
+ className={cn(
39
37
  "min-w-48 p-[var(--space-2)] bg-background text-foreground border border-border rounded-[var(--radius)] shadow-[0_8px_24px_rgba(0,0,0,0.12)] outline-none text-[length:var(--text-sm)] leading-snug origin-[var(--transform-origin)] transition-[opacity,transform] duration-[140ms] ease-out motion-reduce:transition-none data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 motion-reduce:data-[starting-style]:scale-100 motion-reduce:data-[ending-style]:scale-100 focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2",
40
38
  className,
41
39
  )}
@@ -57,7 +55,7 @@ export const PopoverTitle = React.forwardRef<
57
55
  return (
58
56
  <BasePopover.Title
59
57
  ref={ref}
60
- className={cx("m-0 mb-[var(--space-1)] font-semibold text-[0.9375rem]", className)}
58
+ className={cn("m-0 mb-[var(--space-1)] font-semibold text-[0.9375rem]", className)}
61
59
  {...props}
62
60
  />
63
61
  );
@@ -70,7 +68,7 @@ export const PopoverDescription = React.forwardRef<
70
68
  return (
71
69
  <BasePopover.Description
72
70
  ref={ref}
73
- className={cx("m-0 text-foreground-muted text-[0.8125rem]", className)}
71
+ className={cn("m-0 text-foreground-muted text-[0.8125rem]", className)}
74
72
  {...props}
75
73
  />
76
74
  );
@@ -2,11 +2,9 @@ import * as React from "react";
2
2
  import { Popover as BasePopover } from "@base-ui/react/popover";
3
3
  import "./styles.css";
4
4
 
5
+ import { cn } from "@SH_UI_UTILS@";
5
6
  type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
6
7
 
7
- function cx(...args: (string | undefined | false)[]) {
8
- return args.filter(Boolean).join(" ");
9
- }
10
8
 
11
9
  /**
12
10
  * 트리거 요소에 떠다니는 가벼운 패널을 띄우는 비모달 컨테이너. 포커스를 가두지 않으므로
@@ -72,7 +70,7 @@ export const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentPro
72
70
  >
73
71
  <BasePopover.Popup
74
72
  ref={ref}
75
- className={cx("sh-ui-popover__content", className)}
73
+ className={cn("sh-ui-popover__content", className)}
76
74
  {...props}
77
75
  >
78
76
  {showArrow && (
@@ -94,7 +92,7 @@ export const PopoverTitle = React.forwardRef<
94
92
  return (
95
93
  <BasePopover.Title
96
94
  ref={ref}
97
- className={cx("sh-ui-popover__title", className)}
95
+ className={cn("sh-ui-popover__title", className)}
98
96
  {...props}
99
97
  />
100
98
  );
@@ -108,7 +106,7 @@ export const PopoverDescription = React.forwardRef<
108
106
  return (
109
107
  <BasePopover.Description
110
108
  ref={ref}
111
- className={cx("sh-ui-popover__description", className)}
109
+ className={cn("sh-ui-popover__description", className)}
112
110
  {...props}
113
111
  />
114
112
  );
@@ -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
  function clamp(n: number, min: number, max: number) {
8
6
  return Math.min(max, Math.max(min, n));
9
7
  }
@@ -32,14 +30,14 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
32
30
  aria-valuemax={isDeterminate ? max : undefined}
33
31
  aria-valuenow={isDeterminate ? value : undefined}
34
32
  data-state={isDeterminate ? "determinate" : "indeterminate"}
35
- className={cx(
33
+ className={cn(
36
34
  "relative w-full h-2 overflow-hidden bg-background-muted rounded-full",
37
35
  className,
38
36
  )}
39
37
  {...props}
40
38
  >
41
39
  <div
42
- className={cx(
40
+ className={cn(
43
41
  "h-full bg-primary rounded-full transition-[width] duration-[var(--duration-base)] ease-out motion-reduce:transition-none",
44
42
  !isDeterminate &&
45
43
  "w-2/5 animate-[sh-ui-progress-slide_1.2s_ease-in-out_infinite] motion-reduce:animate-none motion-reduce:translate-x-3/4",
@@ -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
  function clamp(n: number, min: number, max: number) {
9
7
  return Math.min(max, Math.max(min, n));
10
8
  }
@@ -42,7 +40,7 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
42
40
  aria-valuemax={isDeterminate ? max : undefined}
43
41
  aria-valuenow={isDeterminate ? value : undefined}
44
42
  data-state={isDeterminate ? "determinate" : "indeterminate"}
45
- className={cx("sh-ui-progress", className)}
43
+ className={cn("sh-ui-progress", className)}
46
44
  {...props}
47
45
  >
48
46
  <div
@@ -2,10 +2,8 @@ import * as React from "react";
2
2
  import { Radio as BaseRadio } from "@base-ui/react/radio";
3
3
  import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
4
4
 
5
- function cx(...args: (string | undefined | false)[]) {
6
- return args.filter(Boolean).join(" ");
7
- }
8
5
 
6
+ import { cn } from "@SH_UI_UTILS@";
9
7
  export type RadioProps = Omit<
10
8
  React.ComponentPropsWithoutRef<typeof BaseRadio.Root>,
11
9
  "className"
@@ -17,7 +15,7 @@ export const Radio = React.forwardRef<HTMLElement, RadioProps>(
17
15
  ({ className, ...props }, ref) => (
18
16
  <BaseRadio.Root
19
17
  ref={ref}
20
- className={cx(
18
+ className={cn(
21
19
  "inline-flex items-center justify-center w-[1.125rem] h-[1.125rem] border border-border-strong rounded-full bg-background cursor-pointer shrink-0 transition-[border-color] duration-[var(--duration-fast)] hover:not-data-[disabled]:border-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 data-[checked]:border-primary data-[disabled]:opacity-[var(--opacity-disabled)] data-[disabled]:cursor-not-allowed motion-reduce:transition-none [@media(hover:none)_and_(pointer:coarse)]:w-5 [@media(hover:none)_and_(pointer:coarse)]:h-5",
22
20
  className,
23
21
  )}
@@ -41,7 +39,7 @@ export const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
41
39
  ({ className, orientation = "vertical", ...props }, ref) => (
42
40
  <BaseRadioGroup
43
41
  ref={ref}
44
- className={cx(
42
+ className={cn(
45
43
  "flex gap-2.5",
46
44
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
47
45
  className,
@@ -3,10 +3,8 @@ import { Radio as BaseRadio } from "@base-ui/react/radio";
3
3
  import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
4
4
  import "./styles.css";
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
  /* ───────────── Radio ───────────── */
11
9
 
12
10
  export type RadioProps = Omit<
@@ -24,7 +22,7 @@ export const Radio = React.forwardRef<HTMLElement, RadioProps>(
24
22
  ({ className, ...props }, ref) => (
25
23
  <BaseRadio.Root
26
24
  ref={ref}
27
- className={cx("sh-ui-radio", className)}
25
+ className={cn("sh-ui-radio", className)}
28
26
  {...props}
29
27
  >
30
28
  <BaseRadio.Indicator className="sh-ui-radio__indicator" />
@@ -58,7 +56,7 @@ export const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
58
56
  ({ className, orientation = "vertical", ...props }, ref) => (
59
57
  <BaseRadioGroup
60
58
  ref={ref}
61
- className={cx("sh-ui-radio-group", className)}
59
+ className={cn("sh-ui-radio-group", className)}
62
60
  data-orientation={orientation}
63
61
  {...props}
64
62
  />