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 {
5
6
  Select,
6
7
  SelectContent,
@@ -11,9 +12,6 @@ import "./styles.css";
11
12
 
12
13
  /* ───────── Helpers ───────── */
13
14
 
14
- function cx(...args: (string | undefined | false)[]) {
15
- return args.filter(Boolean).join(" ");
16
- }
17
15
 
18
16
  const DEFAULT_WEEKDAYS_KO = ["일", "월", "화", "수", "목", "금", "토"] as const;
19
17
 
@@ -505,7 +503,7 @@ export function Calendar(props: CalendarProps) {
505
503
 
506
504
  return (
507
505
  <div
508
- className={cx("sh-ui-calendar", numberOfMonths > 1 && "sh-ui-calendar--multi", className)}
506
+ className={cn("sh-ui-calendar", numberOfMonths > 1 && "sh-ui-calendar--multi", className)}
509
507
  aria-label={ariaLabel}
510
508
  >
511
509
  {children
@@ -545,7 +543,7 @@ export interface CalendarHeaderProps extends React.HTMLAttributes<HTMLDivElement
545
543
  /** 헤더 컨테이너. 화살표/dropdown 등을 children 으로 자유롭게 배치. */
546
544
  export const CalendarHeader = React.forwardRef<HTMLDivElement, CalendarHeaderProps>(
547
545
  function CalendarHeader({ className, ...props }, ref) {
548
- return <div ref={ref} className={cx("sh-ui-calendar__header", className)} {...props} />;
546
+ return <div ref={ref} className={cn("sh-ui-calendar__header", className)} {...props} />;
549
547
  },
550
548
  );
551
549
 
@@ -574,7 +572,7 @@ function makeNavButton(
574
572
  <button
575
573
  ref={ref}
576
574
  type="button"
577
- className={cx("sh-ui-calendar__nav", className)}
575
+ className={cn("sh-ui-calendar__nav", className)}
578
576
  aria-label={ariaLabel ?? defaultLabel}
579
577
  onClick={(e) => {
580
578
  resolveHandler(ctx)();
@@ -649,7 +647,7 @@ export function CalendarYearSelect({
649
647
  onValueChange={(v) => ctx.setYearForVisible(Number(v))}
650
648
  >
651
649
  <SelectTrigger
652
- className={cx("sh-ui-calendar__select-trigger", className)}
650
+ className={cn("sh-ui-calendar__select-trigger", className)}
653
651
  aria-label="연도"
654
652
  >
655
653
  <span className="sh-ui-calendar__select-value">{formatYear(year)}</span>
@@ -684,7 +682,7 @@ export function CalendarMonthSelect({
684
682
  onValueChange={(v) => ctx.setMonthForVisible(Number(v))}
685
683
  >
686
684
  <SelectTrigger
687
- className={cx("sh-ui-calendar__select-trigger", className)}
685
+ className={cn("sh-ui-calendar__select-trigger", className)}
688
686
  aria-label="월"
689
687
  >
690
688
  <span className="sh-ui-calendar__select-value">{formatMonth(month)}</span>
@@ -716,7 +714,7 @@ export const CalendarGrid = React.forwardRef<HTMLDivElement, CalendarGridProps>(
716
714
  const ariaLabel = ctx.ariaLabel ?? monthLabel;
717
715
 
718
716
  return (
719
- <div ref={ref} className={cx("sh-ui-calendar__grid-wrap", className)} {...rest}>
717
+ <div ref={ref} className={cn("sh-ui-calendar__grid-wrap", className)} {...rest}>
720
718
  <div className="sh-ui-calendar__weekdays" role="row">
721
719
  {ctx.weekdayLabels.map((label) => (
722
720
  <span
@@ -751,7 +749,7 @@ export const CalendarGrid = React.forwardRef<HTMLDivElement, CalendarGridProps>(
751
749
  return (
752
750
  <div
753
751
  key={i}
754
- className={cx(
752
+ className={cn(
755
753
  "sh-ui-calendar__cell",
756
754
  inRange && "sh-ui-calendar__cell--in-range",
757
755
  isStart && "sh-ui-calendar__cell--range-start",
@@ -760,7 +758,7 @@ export const CalendarGrid = React.forwardRef<HTMLDivElement, CalendarGridProps>(
760
758
  >
761
759
  <button
762
760
  type="button"
763
- className={cx(
761
+ className={cn(
764
762
  "sh-ui-calendar__day",
765
763
  !current && "sh-ui-calendar__day--outside",
766
764
  selected && "sh-ui-calendar__day--selected",
@@ -0,0 +1,63 @@
1
+ import * as React from "react";
2
+ import { cn } from "@SH_UI_UTILS@";
3
+ import styles from "./styles.module.css";
4
+
5
+ type DivProps = React.HTMLAttributes<HTMLDivElement>;
6
+
7
+ export const Card = React.forwardRef<HTMLDivElement, DivProps>(
8
+ ({ className, ...props }, ref) => (
9
+ <div ref={ref} className={cn(styles.card, className)} {...props} />
10
+ ),
11
+ );
12
+ Card.displayName = "Card";
13
+
14
+ export const CardHeader = React.forwardRef<HTMLDivElement, DivProps>(
15
+ ({ className, ...props }, ref) => (
16
+ <div
17
+ ref={ref}
18
+ data-slot="card-header"
19
+ className={cn(styles.header, className)}
20
+ {...props}
21
+ />
22
+ ),
23
+ );
24
+ CardHeader.displayName = "CardHeader";
25
+
26
+ export const CardTitle = React.forwardRef<HTMLDivElement, DivProps>(
27
+ ({ className, ...props }, ref) => (
28
+ <div ref={ref} className={cn(styles.title, className)} {...props} />
29
+ ),
30
+ );
31
+ CardTitle.displayName = "CardTitle";
32
+
33
+ export const CardDescription = React.forwardRef<HTMLDivElement, DivProps>(
34
+ ({ className, ...props }, ref) => (
35
+ <div ref={ref} className={cn(styles.description, className)} {...props} />
36
+ ),
37
+ );
38
+ CardDescription.displayName = "CardDescription";
39
+
40
+ /**
41
+ * 헤더 우측에 배치되는 슬롯. CardHeader 내부에서 grid 2번째 컬럼을 차지.
42
+ * CardHeader가 `:has(.action)`으로 감지해 레이아웃을 전환한다.
43
+ */
44
+ export const CardAction = React.forwardRef<HTMLDivElement, DivProps>(
45
+ ({ className, ...props }, ref) => (
46
+ <div ref={ref} className={cn(styles.action, className)} {...props} />
47
+ ),
48
+ );
49
+ CardAction.displayName = "CardAction";
50
+
51
+ export const CardContent = React.forwardRef<HTMLDivElement, DivProps>(
52
+ ({ className, ...props }, ref) => (
53
+ <div ref={ref} className={cn(styles.content, className)} {...props} />
54
+ ),
55
+ );
56
+ CardContent.displayName = "CardContent";
57
+
58
+ export const CardFooter = React.forwardRef<HTMLDivElement, DivProps>(
59
+ ({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn(styles.footer, className)} {...props} />
61
+ ),
62
+ );
63
+ CardFooter.displayName = "CardFooter";
@@ -1,16 +1,14 @@
1
1
  import * as React from "react";
2
2
 
3
+ import { cn } from "@SH_UI_UTILS@";
3
4
  type DivProps = React.HTMLAttributes<HTMLDivElement>;
4
5
 
5
- function mergeClass(base: string, extra?: string) {
6
- return extra ? `${base} ${extra}` : base;
7
- }
8
6
 
9
7
  export const Card = React.forwardRef<HTMLDivElement, DivProps>(
10
8
  ({ className, ...props }, ref) => (
11
9
  <div
12
10
  ref={ref}
13
- className={mergeClass(
11
+ className={cn(
14
12
  "flex flex-col gap-[var(--space-6)] py-[var(--space-6)] bg-background text-foreground border border-border rounded-[var(--radius)] max-sm:gap-[var(--space-4)] max-sm:py-[var(--space-4)]",
15
13
  className,
16
14
  )}
@@ -25,7 +23,7 @@ export const CardHeader = React.forwardRef<HTMLDivElement, DivProps>(
25
23
  <div
26
24
  ref={ref}
27
25
  data-slot="card-header"
28
- className={mergeClass(
26
+ className={cn(
29
27
  "grid grid-cols-1 auto-rows-auto gap-y-1.5 px-[var(--space-6)] has-[[data-slot=card-action]]:grid-cols-[1fr_auto] max-sm:px-[var(--space-4)]",
30
28
  className,
31
29
  )}
@@ -39,7 +37,7 @@ export const CardTitle = React.forwardRef<HTMLDivElement, DivProps>(
39
37
  ({ className, ...props }, ref) => (
40
38
  <div
41
39
  ref={ref}
42
- className={mergeClass(
40
+ className={cn(
43
41
  "text-[length:var(--text-base)] font-semibold leading-tight tracking-tight",
44
42
  className,
45
43
  )}
@@ -53,7 +51,7 @@ export const CardDescription = React.forwardRef<HTMLDivElement, DivProps>(
53
51
  ({ className, ...props }, ref) => (
54
52
  <div
55
53
  ref={ref}
56
- className={mergeClass(
54
+ className={cn(
57
55
  "text-[length:var(--text-sm)] leading-normal text-foreground-muted",
58
56
  className,
59
57
  )}
@@ -72,7 +70,7 @@ export const CardAction = React.forwardRef<HTMLDivElement, DivProps>(
72
70
  <div
73
71
  ref={ref}
74
72
  data-slot="card-action"
75
- className={mergeClass(
73
+ className={cn(
76
74
  "col-start-2 row-span-2 self-start justify-self-end",
77
75
  className,
78
76
  )}
@@ -86,7 +84,7 @@ export const CardContent = React.forwardRef<HTMLDivElement, DivProps>(
86
84
  ({ className, ...props }, ref) => (
87
85
  <div
88
86
  ref={ref}
89
- className={mergeClass(
87
+ className={cn(
90
88
  "px-[var(--space-6)] text-[length:var(--text-sm)] leading-relaxed max-sm:px-[var(--space-4)]",
91
89
  className,
92
90
  )}
@@ -100,7 +98,7 @@ export const CardFooter = React.forwardRef<HTMLDivElement, DivProps>(
100
98
  ({ className, ...props }, ref) => (
101
99
  <div
102
100
  ref={ref}
103
- className={mergeClass(
101
+ className={cn(
104
102
  "px-[var(--space-6)] flex items-center gap-[var(--space-2)] max-sm:px-[var(--space-4)] max-sm:flex-wrap",
105
103
  className,
106
104
  )}
@@ -1,15 +1,13 @@
1
1
  import * as React from "react";
2
+ import { cn } from "@SH_UI_UTILS@";
2
3
  import "./styles.css";
3
4
 
4
5
  type DivProps = React.HTMLAttributes<HTMLDivElement>;
5
6
 
6
- function mergeClass(base: string, extra?: string) {
7
- return extra ? `${base} ${extra}` : base;
8
- }
9
7
 
10
8
  export const Card = React.forwardRef<HTMLDivElement, DivProps>(
11
9
  ({ className, ...props }, ref) => (
12
- <div ref={ref} className={mergeClass("sh-ui-card", className)} {...props} />
10
+ <div ref={ref} className={cn("sh-ui-card", className)} {...props} />
13
11
  ),
14
12
  );
15
13
  Card.displayName = "Card";
@@ -19,7 +17,7 @@ export const CardHeader = React.forwardRef<HTMLDivElement, DivProps>(
19
17
  <div
20
18
  ref={ref}
21
19
  data-slot="card-header"
22
- className={mergeClass("sh-ui-card__header", className)}
20
+ className={cn("sh-ui-card__header", className)}
23
21
  {...props}
24
22
  />
25
23
  ),
@@ -30,7 +28,7 @@ export const CardTitle = React.forwardRef<HTMLDivElement, DivProps>(
30
28
  ({ className, ...props }, ref) => (
31
29
  <div
32
30
  ref={ref}
33
- className={mergeClass("sh-ui-card__title", className)}
31
+ className={cn("sh-ui-card__title", className)}
34
32
  {...props}
35
33
  />
36
34
  ),
@@ -41,7 +39,7 @@ export const CardDescription = React.forwardRef<HTMLDivElement, DivProps>(
41
39
  ({ className, ...props }, ref) => (
42
40
  <div
43
41
  ref={ref}
44
- className={mergeClass("sh-ui-card__description", className)}
42
+ className={cn("sh-ui-card__description", className)}
45
43
  {...props}
46
44
  />
47
45
  ),
@@ -56,7 +54,7 @@ export const CardAction = React.forwardRef<HTMLDivElement, DivProps>(
56
54
  ({ className, ...props }, ref) => (
57
55
  <div
58
56
  ref={ref}
59
- className={mergeClass("sh-ui-card__action", className)}
57
+ className={cn("sh-ui-card__action", className)}
60
58
  {...props}
61
59
  />
62
60
  ),
@@ -67,7 +65,7 @@ export const CardContent = React.forwardRef<HTMLDivElement, DivProps>(
67
65
  ({ className, ...props }, ref) => (
68
66
  <div
69
67
  ref={ref}
70
- className={mergeClass("sh-ui-card__content", className)}
68
+ className={cn("sh-ui-card__content", className)}
71
69
  {...props}
72
70
  />
73
71
  ),
@@ -78,7 +76,7 @@ export const CardFooter = React.forwardRef<HTMLDivElement, DivProps>(
78
76
  ({ className, ...props }, ref) => (
79
77
  <div
80
78
  ref={ref}
81
- className={mergeClass("sh-ui-card__footer", className)}
79
+ className={cn("sh-ui-card__footer", className)}
82
80
  {...props}
83
81
  />
84
82
  ),
@@ -0,0 +1,73 @@
1
+ .card {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-6);
5
+ padding: var(--space-6) 0;
6
+ background: var(--background);
7
+ color: var(--foreground);
8
+ border: 1px solid var(--border);
9
+ border-radius: var(--radius);
10
+ }
11
+
12
+ /* 헤더: 기본은 타이틀/설명 세로 스택. action 있으면 2열 그리드로 전환. */
13
+ .header {
14
+ display: grid;
15
+ grid-template-columns: 1fr;
16
+ grid-auto-rows: auto;
17
+ row-gap: 0.375rem;
18
+ padding: 0 var(--space-6);
19
+ }
20
+
21
+ .header:has(.action) {
22
+ grid-template-columns: 1fr auto;
23
+ }
24
+
25
+ .title {
26
+ font-size: var(--text-base);
27
+ font-weight: var(--weight-semibold);
28
+ line-height: 1.25;
29
+ letter-spacing: -0.01em;
30
+ }
31
+
32
+ .description {
33
+ font-size: var(--text-sm);
34
+ line-height: 1.5;
35
+ color: var(--foreground-muted);
36
+ }
37
+
38
+ .action {
39
+ grid-column: 2;
40
+ grid-row: 1 / span 2;
41
+ align-self: start;
42
+ justify-self: end;
43
+ }
44
+
45
+ .content {
46
+ padding: 0 var(--space-6);
47
+ font-size: var(--text-sm);
48
+ line-height: 1.6;
49
+ }
50
+
51
+ .footer {
52
+ padding: 0 var(--space-6);
53
+ display: flex;
54
+ align-items: center;
55
+ gap: var(--space-2);
56
+ }
57
+
58
+ /* 모바일: 패딩 축소 */
59
+ @media (max-width: 640px) {
60
+ .card {
61
+ gap: var(--space-4);
62
+ padding: var(--space-4) 0;
63
+ }
64
+ .header,
65
+ .content,
66
+ .footer {
67
+ padding-left: var(--space-4);
68
+ padding-right: var(--space-4);
69
+ }
70
+ .footer {
71
+ flex-wrap: wrap;
72
+ }
73
+ }
@@ -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
  type Orientation = "horizontal" | "vertical";
10
8
 
11
9
  interface CarouselContextValue {
@@ -144,7 +142,7 @@ export const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
144
142
  <CarouselContext.Provider value={value}>
145
143
  <div
146
144
  ref={ref}
147
- className={cx("relative w-full", className)}
145
+ className={cn("relative w-full", className)}
148
146
  data-orientation={orientation}
149
147
  role="region"
150
148
  aria-roledescription="carousel"
@@ -171,7 +169,7 @@ export const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttrib
171
169
  return (
172
170
  <div
173
171
  ref={mergedRef}
174
- className={cx(
172
+ className={cn(
175
173
  "flex gap-[var(--space-4)] overflow-x-auto overflow-y-hidden snap-x snap-mandatory scroll-smooth [scrollbar-width:none] [-ms-overflow-style:none] [-webkit-overflow-scrolling:touch] overscroll-x-contain [&::-webkit-scrollbar]:hidden motion-reduce:scroll-auto",
176
174
  orientation === "vertical" && "flex-col overflow-x-hidden overflow-y-auto snap-y snap-mandatory h-80",
177
175
  className,
@@ -193,7 +191,7 @@ export const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttribute
193
191
  ref={ref}
194
192
  role="group"
195
193
  aria-roledescription="slide"
196
- className={cx(
194
+ className={cn(
197
195
  "flex-[0_0_100%] min-w-0 snap-start snap-always",
198
196
  orientation === "vertical" && "basis-auto",
199
197
  className,
@@ -218,7 +216,7 @@ export const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.Button
218
216
  ref={ref}
219
217
  type="button"
220
218
  aria-label="이전"
221
- className={cx(
219
+ className={cn(
222
220
  navClasses,
223
221
  orientation === "horizontal" ? "-left-4" : "-top-4 left-1/2",
224
222
  className,
@@ -248,7 +246,7 @@ export const CarouselNext = React.forwardRef<HTMLButtonElement, React.ButtonHTML
248
246
  ref={ref}
249
247
  type="button"
250
248
  aria-label="다음"
251
- className={cx(
249
+ className={cn(
252
250
  navClasses,
253
251
  orientation === "horizontal" ? "-right-4" : "-bottom-4 left-1/2 [top:auto]",
254
252
  className,
@@ -282,7 +280,7 @@ export const CarouselIndicators = React.forwardRef<HTMLDivElement, CarouselIndic
282
280
  ref={ref}
283
281
  role="tablist"
284
282
  aria-label="슬라이드 선택"
285
- className={cx(
283
+ className={cn(
286
284
  "flex justify-center items-center gap-[var(--space-2)] mt-[var(--space-3)]",
287
285
  orientation === "vertical" && "absolute top-1/2 right-2 mt-0 flex-col -translate-y-1/2",
288
286
  className,
@@ -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
  type Orientation = "horizontal" | "vertical";
11
9
 
12
10
  interface CarouselContextValue {
@@ -259,7 +257,7 @@ export const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
259
257
  <CarouselContext.Provider value={value}>
260
258
  <div
261
259
  ref={ref}
262
- className={cx("sh-ui-carousel", className)}
260
+ className={cn("sh-ui-carousel", className)}
263
261
  data-orientation={orientation}
264
262
  role="region"
265
263
  aria-roledescription="carousel"
@@ -292,7 +290,7 @@ export const CarouselContent = React.forwardRef<
292
290
  return (
293
291
  <div
294
292
  ref={mergedRef}
295
- className={cx("sh-ui-carousel__content", className)}
293
+ className={cn("sh-ui-carousel__content", className)}
296
294
  data-orientation={orientation}
297
295
  {...props}
298
296
  />
@@ -313,7 +311,7 @@ export const CarouselItem = React.forwardRef<
313
311
  ref={ref}
314
312
  role="group"
315
313
  aria-roledescription="slide"
316
- className={cx("sh-ui-carousel__item", className)}
314
+ className={cn("sh-ui-carousel__item", className)}
317
315
  data-orientation={orientation}
318
316
  {...props}
319
317
  />
@@ -332,7 +330,7 @@ export const CarouselPrevious = React.forwardRef<
332
330
  ref={ref}
333
331
  type="button"
334
332
  aria-label="이전"
335
- className={cx("sh-ui-carousel__nav", "sh-ui-carousel__nav--prev", className)}
333
+ className={cn("sh-ui-carousel__nav", "sh-ui-carousel__nav--prev", className)}
336
334
  data-orientation={orientation}
337
335
  disabled={disabled || atStart || count === 0}
338
336
  onClick={(e) => {
@@ -368,7 +366,7 @@ export const CarouselNext = React.forwardRef<
368
366
  ref={ref}
369
367
  type="button"
370
368
  aria-label="다음"
371
- className={cx("sh-ui-carousel__nav", "sh-ui-carousel__nav--next", className)}
369
+ className={cn("sh-ui-carousel__nav", "sh-ui-carousel__nav--next", className)}
372
370
  data-orientation={orientation}
373
371
  disabled={disabled || atEnd || count === 0}
374
372
  onClick={(e) => {
@@ -410,7 +408,7 @@ export const CarouselIndicators = React.forwardRef<
410
408
  ref={ref}
411
409
  role="tablist"
412
410
  aria-label="슬라이드 선택"
413
- className={cx("sh-ui-carousel__indicators", className)}
411
+ className={cn("sh-ui-carousel__indicators", className)}
414
412
  data-orientation={orientation}
415
413
  {...props}
416
414
  >
@@ -2,10 +2,8 @@ import * as React from "react";
2
2
  import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
3
3
  import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui/react/checkbox-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 CheckboxProps = Omit<
10
8
  React.ComponentPropsWithoutRef<typeof BaseCheckbox.Root>,
11
9
  "className"
@@ -17,7 +15,7 @@ export const Checkbox = React.forwardRef<HTMLElement, CheckboxProps>(
17
15
  ({ className, ...props }, ref) => (
18
16
  <BaseCheckbox.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-[calc(var(--radius)-2px)] bg-background text-primary-foreground cursor-pointer shrink-0 transition-[background-color,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]:bg-primary data-[checked]:border-primary data-[indeterminate]:bg-primary data-[indeterminate]: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
  )}
@@ -43,7 +41,7 @@ export const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps
43
41
  ({ className, orientation = "vertical", ...props }, ref) => (
44
42
  <BaseCheckboxGroup
45
43
  ref={ref}
46
- className={cx(
44
+ className={cn(
47
45
  "flex gap-2.5",
48
46
  orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
49
47
  className,
@@ -3,10 +3,8 @@ import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
3
3
  import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui/react/checkbox-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
  /* ───────────── Checkbox ───────────── */
11
9
 
12
10
  export type CheckboxProps = Omit<
@@ -24,7 +22,7 @@ export const Checkbox = React.forwardRef<HTMLElement, CheckboxProps>(
24
22
  ({ className, ...props }, ref) => (
25
23
  <BaseCheckbox.Root
26
24
  ref={ref}
27
- className={cx("sh-ui-checkbox", className)}
25
+ className={cn("sh-ui-checkbox", className)}
28
26
  {...props}
29
27
  >
30
28
  <BaseCheckbox.Indicator className="sh-ui-checkbox__indicator">
@@ -60,7 +58,7 @@ export const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps
60
58
  ({ className, orientation = "vertical", ...props }, ref) => (
61
59
  <BaseCheckboxGroup
62
60
  ref={ref}
63
- className={cx("sh-ui-checkbox-group", className)}
61
+ className={cn("sh-ui-checkbox-group", className)}
64
62
  data-orientation={orientation}
65
63
  {...props}
66
64
  />
@@ -10,6 +10,7 @@ import { css as cssLang } from "@codemirror/lang-css";
10
10
  import { html } from "@codemirror/lang-html";
11
11
  import { markdown } from "@codemirror/lang-markdown";
12
12
 
13
+ import { cn } from "@SH_UI_UTILS@";
13
14
  export type CodeEditorLanguage =
14
15
  | "text" | "javascript" | "typescript" | "jsx" | "tsx"
15
16
  | "json" | "css" | "html" | "markdown";
@@ -30,9 +31,6 @@ export interface CodeEditorProps {
30
31
  "aria-labelledby"?: string;
31
32
  }
32
33
 
33
- function cx(...args: (string | undefined | false | null)[]) {
34
- return args.filter(Boolean).join(" ");
35
- }
36
34
 
37
35
  function languageExtension(language: CodeEditorLanguage): Extension {
38
36
  switch (language) {
@@ -136,7 +134,7 @@ export function CodeEditor({
136
134
  return (
137
135
  <div
138
136
  ref={hostRef}
139
- className={cx(
137
+ className={cn(
140
138
  "sh-ui-code-editor relative border border-border rounded-[var(--radius)] bg-background text-[0.8125rem] leading-relaxed overflow-hidden transition-[border-color] duration-[var(--duration-fast)] focus-within:border-foreground focus-within:outline-[length:var(--border-width-strong)] focus-within:outline-foreground focus-within:outline-offset-2 data-[readonly]:bg-background-subtle motion-reduce:transition-none",
141
139
  className,
142
140
  )}
@@ -11,6 +11,7 @@ import { html } from "@codemirror/lang-html";
11
11
  import { markdown } from "@codemirror/lang-markdown";
12
12
  import "./styles.css";
13
13
 
14
+ import { cn } from "@SH_UI_UTILS@";
14
15
  export type CodeEditorLanguage =
15
16
  | "text"
16
17
  | "javascript"
@@ -59,9 +60,6 @@ export interface CodeEditorProps {
59
60
  "aria-labelledby"?: string;
60
61
  }
61
62
 
62
- function cx(...args: (string | undefined | false | null)[]) {
63
- return args.filter(Boolean).join(" ");
64
- }
65
63
 
66
64
  function languageExtension(language: CodeEditorLanguage): Extension {
67
65
  switch (language) {
@@ -219,7 +217,7 @@ export function CodeEditor({
219
217
  return (
220
218
  <div
221
219
  ref={hostRef}
222
- className={cx("sh-ui-code-editor", className)}
220
+ className={cn("sh-ui-code-editor", className)}
223
221
  data-readonly={readOnly || undefined}
224
222
  style={
225
223
  {
@@ -1,10 +1,8 @@
1
1
  import { codeToHtml } from "shiki";
2
2
  import { CodePanelCopyButton } from "./copy";
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 interface CodePanelProps
9
7
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
10
8
  code?: string;
@@ -21,7 +19,7 @@ const rootClasses =
21
19
  export async function CodePanel({
22
20
  code, language = "text", filename, showLineNumbers = true, hideCopy, className, children, ...rest
23
21
  }: CodePanelProps) {
24
- const classes = cx(rootClasses, className);
22
+ const classes = cn(rootClasses, className);
25
23
 
26
24
  if (children !== undefined) {
27
25
  return <div className={classes} {...rest}>{children}</div>;
@@ -52,7 +50,7 @@ export async function CodePanel({
52
50
  export function CodePanelHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
53
51
  return (
54
52
  <div
55
- className={cx(
53
+ className={cn(
56
54
  "flex items-center justify-between gap-[var(--space-2)] py-[var(--space-2)] pl-[var(--space-4)] pr-[var(--space-3)] border-b border-border bg-background-muted text-[length:var(--text-xs)] text-foreground-muted",
57
55
  className,
58
56
  )}
@@ -65,7 +63,7 @@ export function CodePanelHeader({ className, children, ...props }: React.HTMLAtt
65
63
 
66
64
  export function CodePanelFilename({ className, children, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
67
65
  return (
68
- <span className={cx("font-mono text-foreground", className)} {...props}>{children}</span>
66
+ <span className={cn("font-mono text-foreground", className)} {...props}>{children}</span>
69
67
  );
70
68
  }
71
69
 
@@ -93,7 +91,7 @@ export async function CodePanelBody({
93
91
 
94
92
  return (
95
93
  <div
96
- className={cx(
94
+ className={cn(
97
95
  "overflow-x-auto [&_pre]:m-0 [&_pre]:py-[var(--space-3)] [&_pre]:px-[var(--space-4)] [&_pre]:!bg-transparent [&_pre]:font-mono [&_pre]:text-[length:inherit] [&_pre]:leading-[inherit] [&_pre]:border-none [&_pre]:rounded-none [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-[length:inherit] [&_code]:block [&_.shiki]:!text-[var(--shiki-light)] [&_.shiki_span]:!text-[var(--shiki-light)] [&_.shiki]:!bg-transparent [&_.shiki_span]:!bg-transparent [.dark_&_.shiki]:!text-[var(--shiki-dark)] [.dark_&_.shiki_span]:!text-[var(--shiki-dark)] data-[line-numbers]:[&_pre_code]:[counter-reset:step] data-[line-numbers]:[&_pre_code_.line]:before:[content:counter(step)] data-[line-numbers]:[&_pre_code_.line]:before:[counter-increment:step] data-[line-numbers]:[&_pre_code_.line]:before:inline-block data-[line-numbers]:[&_pre_code_.line]:before:w-7 data-[line-numbers]:[&_pre_code_.line]:before:mr-[var(--space-4)] data-[line-numbers]:[&_pre_code_.line]:before:text-right data-[line-numbers]:[&_pre_code_.line]:before:text-foreground-muted data-[line-numbers]:[&_pre_code_.line]:before:opacity-70 data-[line-numbers]:[&_pre_code_.line]:before:select-none",
98
96
  className,
99
97
  )}