sh-ui-cli 0.45.3 → 0.47.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 (93) hide show
  1. package/data/changelog/versions.json +26 -0
  2. package/data/registry/react/components/accordion/index.module.tsx +97 -0
  3. package/data/registry/react/components/accordion/styles.module.css +111 -0
  4. package/data/registry/react/components/avatar/index.module.tsx +73 -0
  5. package/data/registry/react/components/avatar/styles.module.css +36 -0
  6. package/data/registry/react/components/badge/index.module.tsx +40 -0
  7. package/data/registry/react/components/badge/styles.module.css +57 -0
  8. package/data/registry/react/components/breadcrumb/index.module.tsx +152 -0
  9. package/data/registry/react/components/breadcrumb/styles.module.css +82 -0
  10. package/data/registry/react/components/button/index.module.tsx +45 -0
  11. package/data/registry/react/components/button/styles.module.css +92 -0
  12. package/data/registry/react/components/calendar/index.module.tsx +806 -0
  13. package/data/registry/react/components/calendar/styles.module.css +213 -0
  14. package/data/registry/react/components/card/index.module.tsx +63 -0
  15. package/data/registry/react/components/card/styles.module.css +73 -0
  16. package/data/registry/react/components/carousel/index.module.tsx +430 -0
  17. package/data/registry/react/components/carousel/styles.module.css +155 -0
  18. package/data/registry/react/components/checkbox/index.module.tsx +96 -0
  19. package/data/registry/react/components/checkbox/styles.module.css +75 -0
  20. package/data/registry/react/components/code-editor/index.module.tsx +230 -0
  21. package/data/registry/react/components/code-editor/styles.module.css +76 -0
  22. package/data/registry/react/components/code-panel/index.module.tsx +191 -0
  23. package/data/registry/react/components/code-panel/styles.module.css +124 -0
  24. package/data/registry/react/components/color-picker/index.module.tsx +467 -0
  25. package/data/registry/react/components/color-picker/styles.module.css +166 -0
  26. package/data/registry/react/components/combobox/index.module.tsx +165 -0
  27. package/data/registry/react/components/combobox/styles.module.css +151 -0
  28. package/data/registry/react/components/context-menu/index.module.tsx +251 -0
  29. package/data/registry/react/components/context-menu/styles.module.css +140 -0
  30. package/data/registry/react/components/date-picker/index.module.tsx +520 -0
  31. package/data/registry/react/components/date-picker/styles.module.css +103 -0
  32. package/data/registry/react/components/dialog/index.module.tsx +95 -0
  33. package/data/registry/react/components/dialog/styles.module.css +127 -0
  34. package/data/registry/react/components/dropdown-menu/index.module.tsx +255 -0
  35. package/data/registry/react/components/dropdown-menu/styles.module.css +150 -0
  36. package/data/registry/react/components/file-upload/index.module.tsx +487 -0
  37. package/data/registry/react/components/file-upload/styles.module.css +170 -0
  38. package/data/registry/react/components/form/index.module.tsx +61 -0
  39. package/data/registry/react/components/form/styles.module.css +47 -0
  40. package/data/registry/react/components/header/index.module.tsx +805 -0
  41. package/data/registry/react/components/header/styles.module.css +350 -0
  42. package/data/registry/react/components/input/index.module.tsx +486 -0
  43. package/data/registry/react/components/input/styles.module.css +200 -0
  44. package/data/registry/react/components/label/index.module.tsx +52 -0
  45. package/data/registry/react/components/label/styles.module.css +90 -0
  46. package/data/registry/react/components/markdown-editor/index.module.tsx +119 -0
  47. package/data/registry/react/components/markdown-editor/styles.module.css +160 -0
  48. package/data/registry/react/components/menubar/index.module.tsx +32 -0
  49. package/data/registry/react/components/menubar/styles.module.css +45 -0
  50. package/data/registry/react/components/numeric-input/index.module.tsx +148 -0
  51. package/data/registry/react/components/numeric-input/styles.module.css +56 -0
  52. package/data/registry/react/components/page-toc/index.module.tsx +174 -0
  53. package/data/registry/react/components/page-toc/styles.module.css +82 -0
  54. package/data/registry/react/components/pagination/index.module.tsx +269 -0
  55. package/data/registry/react/components/pagination/styles.module.css +105 -0
  56. package/data/registry/react/components/popover/index.module.tsx +113 -0
  57. package/data/registry/react/components/popover/styles.module.css +65 -0
  58. package/data/registry/react/components/progress/index.module.tsx +54 -0
  59. package/data/registry/react/components/progress/styles.module.css +41 -0
  60. package/data/registry/react/components/radio/index.module.tsx +65 -0
  61. package/data/registry/react/components/radio/styles.module.css +80 -0
  62. package/data/registry/react/components/rich-text-editor/index.module.tsx +348 -0
  63. package/data/registry/react/components/rich-text-editor/styles.module.css +196 -0
  64. package/data/registry/react/components/select/index.module.tsx +234 -0
  65. package/data/registry/react/components/select/styles.module.css +193 -0
  66. package/data/registry/react/components/separator/index.module.tsx +46 -0
  67. package/data/registry/react/components/separator/styles.module.css +15 -0
  68. package/data/registry/react/components/sidebar/index.module.tsx +1067 -0
  69. package/data/registry/react/components/sidebar/styles.module.css +502 -0
  70. package/data/registry/react/components/skeleton/index.module.tsx +22 -0
  71. package/data/registry/react/components/skeleton/styles.module.css +24 -0
  72. package/data/registry/react/components/slider/index.module.tsx +298 -0
  73. package/data/registry/react/components/slider/styles.module.css +64 -0
  74. package/data/registry/react/components/spinner/index.module.tsx +38 -0
  75. package/data/registry/react/components/spinner/styles.module.css +37 -0
  76. package/data/registry/react/components/switch/index.module.tsx +39 -0
  77. package/data/registry/react/components/switch/styles.module.css +83 -0
  78. package/data/registry/react/components/tabs/index.module.tsx +91 -0
  79. package/data/registry/react/components/tabs/styles.module.css +148 -0
  80. package/data/registry/react/components/textarea/index.module.tsx +23 -0
  81. package/data/registry/react/components/textarea/styles.module.css +54 -0
  82. package/data/registry/react/components/toast/index.module.tsx +258 -0
  83. package/data/registry/react/components/toast/styles.module.css +290 -0
  84. package/data/registry/react/components/toggle/index.module.tsx +131 -0
  85. package/data/registry/react/components/toggle/styles.module.css +85 -0
  86. package/data/registry/react/components/tooltip/index.module.tsx +83 -0
  87. package/data/registry/react/components/tooltip/styles.module.css +44 -0
  88. package/data/registry/react/registry.json +604 -1
  89. package/data/tokens/build.mjs +4 -0
  90. package/package.json +1 -1
  91. package/src/add.mjs +12 -12
  92. package/src/api.d.ts +4 -3
  93. package/src/constants.js +4 -3
@@ -0,0 +1,113 @@
1
+ import * as React from "react";
2
+ import { Popover as BasePopover } from "@base-ui/react/popover";
3
+ import styles from "./styles.module.css";
4
+
5
+ import { cn } from "@SH_UI_UTILS@";
6
+ type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
7
+
8
+
9
+ /**
10
+ * 트리거 요소에 떠다니는 가벼운 패널을 띄우는 비모달 컨테이너. 포커스를 가두지 않으므로
11
+ * 짧은 폼·정보 표시에 적합하고, 강제 응답이 필요하면 Dialog를 사용할 것.
12
+ */
13
+ export const Popover = BasePopover.Root;
14
+
15
+ /** Popover를 여는 트리거. */
16
+ export const PopoverTrigger = BasePopover.Trigger;
17
+
18
+ /** Popover를 닫는 요소. */
19
+ export const PopoverClose = BasePopover.Close;
20
+
21
+ export interface PopoverContentProps
22
+ extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BasePopover.Popup>> {
23
+ /**
24
+ * Trigger 기준 배치 방향. 공간 부족 시 자동으로 반대편으로 뒤집힌다.
25
+ * @default "bottom"
26
+ */
27
+ side?: "top" | "right" | "bottom" | "left";
28
+ /**
29
+ * 트리거 축에서의 정렬.
30
+ * - `start` — 트리거 시작 가장자리 정렬
31
+ * - `center` — 가운데 (기본)
32
+ * - `end` — 트리거 끝 가장자리 정렬
33
+ *
34
+ * @default "center"
35
+ */
36
+ align?: "start" | "center" | "end";
37
+ /**
38
+ * Trigger와 Popup 사이 간격(px).
39
+ * @default 8
40
+ */
41
+ sideOffset?: number;
42
+ /**
43
+ * Portal이 마운트될 DOM 노드.
44
+ * @default document.body
45
+ */
46
+ container?: React.ComponentPropsWithoutRef<typeof BasePopover.Portal>["container"];
47
+ /**
48
+ * Trigger를 가리키는 화살표 표시 여부.
49
+ * @default false
50
+ */
51
+ showArrow?: boolean;
52
+ }
53
+
54
+ /**
55
+ * Popover의 콘텐츠. 트리거에 자동 위치 조정되어 portal로 마운트된다.
56
+ * `side`/`align`/`sideOffset`로 배치를 미세조정하고, `showArrow`로 화살표를 노출한다.
57
+ */
58
+ export const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
59
+ function PopoverContent(
60
+ { className, children, side, align, sideOffset = 8, container, showArrow, ...props },
61
+ ref,
62
+ ) {
63
+ return (
64
+ <BasePopover.Portal container={container}>
65
+ <BasePopover.Positioner
66
+ className={styles.popover__positioner}
67
+ side={side}
68
+ align={align}
69
+ sideOffset={sideOffset}
70
+ >
71
+ <BasePopover.Popup
72
+ ref={ref}
73
+ className={cn(styles.popover__content, className)}
74
+ {...props}
75
+ >
76
+ {showArrow && (
77
+ <BasePopover.Arrow className={styles.popover__arrow} />
78
+ )}
79
+ {children}
80
+ </BasePopover.Popup>
81
+ </BasePopover.Positioner>
82
+ </BasePopover.Portal>
83
+ );
84
+ },
85
+ );
86
+
87
+ /** Popover 콘텐츠의 제목. 접근성을 위해 짧은 제목이라도 함께 두는 것을 권장. */
88
+ export const PopoverTitle = React.forwardRef<
89
+ HTMLHeadingElement,
90
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BasePopover.Title>>
91
+ >(function PopoverTitle({ className, ...props }, ref) {
92
+ return (
93
+ <BasePopover.Title
94
+ ref={ref}
95
+ className={cn(styles.popover__title, className)}
96
+ {...props}
97
+ />
98
+ );
99
+ });
100
+
101
+ /** Popover 콘텐츠의 보조 설명. */
102
+ export const PopoverDescription = React.forwardRef<
103
+ HTMLParagraphElement,
104
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BasePopover.Description>>
105
+ >(function PopoverDescription({ className, ...props }, ref) {
106
+ return (
107
+ <BasePopover.Description
108
+ ref={ref}
109
+ className={cn(styles.popover__description, className)}
110
+ {...props}
111
+ />
112
+ );
113
+ });
@@ -0,0 +1,65 @@
1
+ .popover__positioner {
2
+ z-index: var(--z-popover);
3
+ outline: none;
4
+ }
5
+
6
+ .popover__content {
7
+ min-width: 12rem;
8
+ padding: var(--space-2);
9
+ background: var(--background);
10
+ color: var(--foreground);
11
+ border: 1px solid var(--border);
12
+ border-radius: var(--radius);
13
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
14
+ outline: none;
15
+ font-size: var(--text-sm);
16
+ line-height: 1.4;
17
+ transform-origin: var(--transform-origin);
18
+ }
19
+
20
+ .popover__content[data-starting-style],
21
+ .popover__content[data-ending-style] {
22
+ opacity: 0;
23
+ transform: scale(0.96);
24
+ }
25
+
26
+ .popover__content {
27
+ transition:
28
+ opacity 140ms ease,
29
+ transform 140ms ease;
30
+ }
31
+
32
+ .popover__content:focus-visible {
33
+ outline: var(--border-width-strong) solid var(--foreground);
34
+ outline-offset: 2px;
35
+ }
36
+
37
+ .popover__arrow {
38
+ color: var(--background);
39
+ }
40
+
41
+ .popover__arrow svg {
42
+ display: block;
43
+ }
44
+
45
+ .popover__title {
46
+ margin: 0 0 var(--space-1);
47
+ font-weight: var(--weight-semibold);
48
+ font-size: 0.9375rem;
49
+ }
50
+
51
+ .popover__description {
52
+ margin: 0;
53
+ color: var(--foreground-muted);
54
+ font-size: 0.8125rem;
55
+ }
56
+
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .popover__content {
59
+ transition: none;
60
+ }
61
+ .popover__content[data-starting-style],
62
+ .popover__content[data-ending-style] {
63
+ transform: none;
64
+ }
65
+ }
@@ -0,0 +1,54 @@
1
+ import * as React from "react";
2
+ import styles from "./styles.module.css";
3
+
4
+
5
+ import { cn } from "@SH_UI_UTILS@";
6
+ function clamp(n: number, min: number, max: number) {
7
+ return Math.min(max, Math.max(min, n));
8
+ }
9
+
10
+ export interface ProgressProps
11
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "role"> {
12
+ /** 0~100 사이의 현재 값. 생략 시 indeterminate 모드. */
13
+ value?: number;
14
+ /** 최댓값. 기본 100. */
15
+ max?: number;
16
+ /** 접근성: aria-label (시각적 라벨이 없으면 권장). */
17
+ "aria-label"?: string;
18
+ }
19
+
20
+ /**
21
+ * 작업 진행도를 가로 바로 표시. value가 없으면 무한 루프 indeterminate.
22
+ *
23
+ * - determinate: `<Progress value={40} />`
24
+ * - indeterminate: `<Progress aria-label="로딩 중" />`
25
+ */
26
+ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
27
+ function Progress(
28
+ { value, max = 100, className, "aria-label": ariaLabel, ...props },
29
+ ref,
30
+ ) {
31
+ const isDeterminate = value !== undefined;
32
+ const normalized = isDeterminate ? clamp((value / max) * 100, 0, 100) : 0;
33
+
34
+ return (
35
+ <div
36
+ ref={ref}
37
+ role="progressbar"
38
+ aria-label={ariaLabel}
39
+ aria-valuemin={isDeterminate ? 0 : undefined}
40
+ aria-valuemax={isDeterminate ? max : undefined}
41
+ aria-valuenow={isDeterminate ? value : undefined}
42
+ data-state={isDeterminate ? "determinate" : "indeterminate"}
43
+ className={cn(styles.progress, className)}
44
+ {...props}
45
+ >
46
+ <div
47
+ className={styles.progress__indicator}
48
+ style={isDeterminate ? { width: `${normalized}%` } : undefined}
49
+ />
50
+ </div>
51
+ );
52
+ },
53
+ );
54
+ Progress.displayName = "Progress";
@@ -0,0 +1,41 @@
1
+ .progress {
2
+ position: relative;
3
+ width: 100%;
4
+ height: 0.5rem;
5
+ overflow: hidden;
6
+ background: var(--background-muted);
7
+ border-radius: 999px;
8
+ }
9
+
10
+ .progress__indicator {
11
+ height: 100%;
12
+ background: var(--primary);
13
+ border-radius: 999px;
14
+ transition: width var(--duration-base) ease;
15
+ }
16
+
17
+ /* indeterminate 모드: 바가 좌우로 왕복 */
18
+ .progress[data-state="indeterminate"] .progress__indicator {
19
+ width: 40%;
20
+ animation: sh-ui-progress-slide 1.2s ease-in-out infinite;
21
+ }
22
+
23
+ @keyframes sh-ui-progress-slide {
24
+ 0% {
25
+ transform: translateX(-100%);
26
+ }
27
+ 100% {
28
+ transform: translateX(250%);
29
+ }
30
+ }
31
+
32
+ @media (prefers-reduced-motion: reduce) {
33
+ .progress__indicator {
34
+ transition: none;
35
+ }
36
+ .progress[data-state="indeterminate"] .progress__indicator {
37
+ /* 움직임 최소화 — 중앙에 정지된 바 */
38
+ animation: none;
39
+ transform: translateX(75%);
40
+ }
41
+ }
@@ -0,0 +1,65 @@
1
+ import * as React from "react";
2
+ import { Radio as BaseRadio } from "@base-ui/react/radio";
3
+ import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
4
+ import styles from "./styles.module.css";
5
+
6
+
7
+ import { cn } from "@SH_UI_UTILS@";
8
+ /* ───────────── Radio ───────────── */
9
+
10
+ export type RadioProps = Omit<
11
+ React.ComponentPropsWithoutRef<typeof BaseRadio.Root>,
12
+ "className"
13
+ > & {
14
+ className?: string;
15
+ };
16
+
17
+ /**
18
+ * 단일 선택지. 단독으로 쓰지 않고 반드시 `RadioGroup` 안에 두 개 이상을 묶어 사용한다.
19
+ * 단일 선택이지만 즉시 적용되는 설정에는 Switch를, 다중 선택에는 Checkbox를 권장.
20
+ */
21
+ export const Radio = React.forwardRef<HTMLElement, RadioProps>(
22
+ ({ className, ...props }, ref) => (
23
+ <BaseRadio.Root
24
+ ref={ref}
25
+ className={cn(styles.radio, className)}
26
+ {...props}
27
+ >
28
+ <BaseRadio.Indicator className={styles.radio__indicator} />
29
+ </BaseRadio.Root>
30
+ ),
31
+ );
32
+ Radio.displayName = "Radio";
33
+
34
+ /* ───────────── RadioGroup ───────────── */
35
+
36
+ export type RadioGroupProps = Omit<
37
+ React.ComponentPropsWithoutRef<typeof BaseRadioGroup>,
38
+ "className"
39
+ > & {
40
+ className?: string;
41
+ /**
42
+ * 그룹 내 항목 배치 방향.
43
+ * - `vertical` — 세로 나열 (기본)
44
+ * - `horizontal` — 가로 나열. 짧은 라벨 2~3개에만 권장
45
+ *
46
+ * @default "vertical"
47
+ */
48
+ orientation?: "horizontal" | "vertical";
49
+ };
50
+
51
+ /**
52
+ * 여러 Radio를 묶는 컨테이너. 같은 `name` 아래 단일 선택을 보장하고,
53
+ * 키보드 화살표로 항목 간 이동이 가능하다. 그룹 라벨은 외부 `<Label>`로 제공할 것.
54
+ */
55
+ export const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
56
+ ({ className, orientation = "vertical", ...props }, ref) => (
57
+ <BaseRadioGroup
58
+ ref={ref}
59
+ className={cn(styles["radio-group"], className)}
60
+ data-orientation={orientation}
61
+ {...props}
62
+ />
63
+ ),
64
+ );
65
+ RadioGroup.displayName = "RadioGroup";
@@ -0,0 +1,80 @@
1
+ /* ───────────── Radio ───────────── */
2
+
3
+ .radio {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ width: 1.125rem;
8
+ height: 1.125rem;
9
+ border: 1px solid var(--border-strong);
10
+ border-radius: 999px;
11
+ background: var(--background);
12
+ cursor: pointer;
13
+ flex-shrink: 0;
14
+ transition: border-color var(--duration-fast);
15
+ -webkit-tap-highlight-color: transparent;
16
+ }
17
+
18
+ .radio:hover:not([data-disabled]) {
19
+ border-color: var(--foreground);
20
+ }
21
+
22
+ .radio:focus-visible {
23
+ outline: var(--border-width-strong) solid var(--foreground);
24
+ outline-offset: 2px;
25
+ }
26
+
27
+ .radio[data-checked] {
28
+ border-color: var(--primary);
29
+ }
30
+
31
+ .radio[data-disabled] {
32
+ opacity: var(--opacity-disabled);
33
+ cursor: not-allowed;
34
+ }
35
+
36
+ /* 모바일/터치: 최소 탭 영역 */
37
+ @media (hover: none) and (pointer: coarse) {
38
+ .radio {
39
+ width: 1.25rem;
40
+ height: 1.25rem;
41
+ }
42
+ }
43
+
44
+ /* ───────────── Indicator (내부 원) ───────────── */
45
+
46
+ .radio__indicator {
47
+ width: 0.5rem;
48
+ height: 0.5rem;
49
+ border-radius: 999px;
50
+ background: var(--primary);
51
+ transform: scale(0);
52
+ transition: transform var(--duration-fast) ease-out;
53
+ }
54
+
55
+ .radio[data-checked] .radio__indicator {
56
+ transform: scale(1);
57
+ }
58
+
59
+ /* ───────────── RadioGroup ───────────── */
60
+
61
+ .radio-group {
62
+ display: flex;
63
+ gap: 0.625rem;
64
+ }
65
+
66
+ .radio-group[data-orientation="vertical"] {
67
+ flex-direction: column;
68
+ }
69
+
70
+ .radio-group[data-orientation="horizontal"] {
71
+ flex-direction: row;
72
+ flex-wrap: wrap;
73
+ }
74
+
75
+ @media (prefers-reduced-motion: reduce) {
76
+ .radio,
77
+ .radio__indicator {
78
+ transition: none;
79
+ }
80
+ }