sh-ui-cli 0.52.1 → 0.52.2

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 (88) hide show
  1. package/data/changelog/versions.json +14 -0
  2. package/data/registry/react/components/_smoke/vanilla-extract.test.ts +33 -0
  3. package/data/registry/react/components/input/styles.css.ts +6 -6
  4. package/data/registry/react/registry.json +35 -852
  5. package/package.json +1 -1
  6. package/src/api.d.ts +3 -4
  7. package/src/constants.js +9 -5
  8. package/src/mcp.mjs +0 -1
  9. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
  10. package/data/registry/react/components/accordion/styles.css.ts +0 -131
  11. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
  12. package/data/registry/react/components/avatar/styles.css.ts +0 -68
  13. package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
  14. package/data/registry/react/components/badge/styles.css.ts +0 -71
  15. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
  16. package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
  17. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
  18. package/data/registry/react/components/calendar/styles.css.ts +0 -250
  19. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
  20. package/data/registry/react/components/carousel/styles.css.ts +0 -169
  21. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
  22. package/data/registry/react/components/checkbox/styles.css.ts +0 -74
  23. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
  24. package/data/registry/react/components/code-editor/styles.css.ts +0 -97
  25. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
  26. package/data/registry/react/components/code-panel/styles.css.ts +0 -151
  27. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
  28. package/data/registry/react/components/color-picker/styles.css.ts +0 -169
  29. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
  30. package/data/registry/react/components/combobox/styles.css.ts +0 -174
  31. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
  32. package/data/registry/react/components/context-menu/styles.css.ts +0 -167
  33. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
  34. package/data/registry/react/components/date-picker/styles.css.ts +0 -111
  35. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
  36. package/data/registry/react/components/dialog/styles.css.ts +0 -140
  37. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
  38. package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
  39. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
  40. package/data/registry/react/components/file-upload/styles.css.ts +0 -193
  41. package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
  42. package/data/registry/react/components/form/styles.css.ts +0 -56
  43. package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
  44. package/data/registry/react/components/header/styles.css.ts +0 -413
  45. package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
  46. package/data/registry/react/components/label/styles.css.ts +0 -141
  47. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
  48. package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
  49. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
  50. package/data/registry/react/components/menubar/styles.css.ts +0 -53
  51. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
  52. package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
  53. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
  54. package/data/registry/react/components/page-toc/styles.css.ts +0 -97
  55. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
  56. package/data/registry/react/components/pagination/styles.css.ts +0 -113
  57. package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
  58. package/data/registry/react/components/popover/styles.css.ts +0 -78
  59. package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
  60. package/data/registry/react/components/progress/styles.css.ts +0 -53
  61. package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
  62. package/data/registry/react/components/radio/styles.css.ts +0 -79
  63. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
  64. package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
  65. package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
  66. package/data/registry/react/components/select/styles.css.ts +0 -225
  67. package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
  68. package/data/registry/react/components/separator/styles.css.ts +0 -24
  69. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
  70. package/data/registry/react/components/sidebar/styles.css.ts +0 -578
  71. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
  72. package/data/registry/react/components/skeleton/styles.css.ts +0 -30
  73. package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
  74. package/data/registry/react/components/slider/styles.css.ts +0 -75
  75. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
  76. package/data/registry/react/components/spinner/styles.css.ts +0 -60
  77. package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
  78. package/data/registry/react/components/switch/styles.css.ts +0 -87
  79. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
  80. package/data/registry/react/components/tabs/styles.css.ts +0 -145
  81. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
  82. package/data/registry/react/components/textarea/styles.css.ts +0 -55
  83. package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
  84. package/data/registry/react/components/toast/styles.css.ts +0 -307
  85. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
  86. package/data/registry/react/components/toggle/styles.css.ts +0 -109
  87. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
  88. package/data/registry/react/components/tooltip/styles.css.ts +0 -59
@@ -1,165 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
5
- import { byKey, combobox__input, combobox__positioner, combobox__content, combobox__item, comboboxItemIndicator, comboboxItemText, combobox__empty, comboboxGroupLabel, combobox__chip, comboboxChipRemove } from "./styles.css";
6
-
7
- import { cn } from "@SH_UI_UTILS@";
8
- type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
9
-
10
-
11
- /**
12
- * Select + Input의 결합 — 타이핑으로 목록이 자동 필터링된다.
13
- * Base UI Combobox를 래핑해 `items` 배열을 받으면 기본 필터가 `input` 값 기준으로 동작.
14
- *
15
- * <Combobox items={fruits}>
16
- * <ComboboxInput placeholder="과일 검색" />
17
- * <ComboboxContent>
18
- * <ComboboxList>
19
- * {(item) => <ComboboxItem key={item} value={item}>{item}</ComboboxItem>}
20
- * </ComboboxList>
21
- * <ComboboxEmpty>일치하는 항목 없음</ComboboxEmpty>
22
- * </ComboboxContent>
23
- * </Combobox>
24
- */
25
- export const Combobox = BaseCombobox.Root;
26
-
27
- export const ComboboxIcon = BaseCombobox.Icon;
28
- export const ComboboxTrigger = BaseCombobox.Trigger;
29
- export const ComboboxClear = BaseCombobox.Clear;
30
- export const ComboboxValue = BaseCombobox.Value;
31
- export const ComboboxGroup = BaseCombobox.Group;
32
- export const ComboboxChips = BaseCombobox.Chips;
33
-
34
- export const ComboboxInput = React.forwardRef<
35
- HTMLInputElement,
36
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Input>>
37
- >(function ComboboxInput({ className, ...props }, ref) {
38
- return (
39
- <BaseCombobox.Input
40
- ref={ref}
41
- className={cn(combobox__input, className)}
42
- {...props}
43
- />
44
- );
45
- });
46
-
47
- /** Portal + Positioner + Popup 래퍼. */
48
- export const ComboboxContent = React.forwardRef<
49
- HTMLDivElement,
50
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup>> & {
51
- container?: React.ComponentPropsWithoutRef<typeof BaseCombobox.Portal>["container"];
52
- sideOffset?: number;
53
- }
54
- >(function ComboboxContent(
55
- { className, children, container, sideOffset = 4, ...props },
56
- ref,
57
- ) {
58
- return (
59
- <BaseCombobox.Portal container={container}>
60
- <BaseCombobox.Positioner
61
- className={combobox__positioner}
62
- sideOffset={sideOffset}
63
- align="start"
64
- >
65
- <BaseCombobox.Popup
66
- ref={ref}
67
- className={cn(combobox__content, className)}
68
- {...props}
69
- >
70
- {children}
71
- </BaseCombobox.Popup>
72
- </BaseCombobox.Positioner>
73
- </BaseCombobox.Portal>
74
- );
75
- });
76
-
77
- export const ComboboxList = BaseCombobox.List;
78
-
79
- export const ComboboxItem = React.forwardRef<
80
- HTMLDivElement,
81
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Item>>
82
- >(function ComboboxItem({ className, children, ...props }, ref) {
83
- return (
84
- <BaseCombobox.Item
85
- ref={ref}
86
- className={cn(combobox__item, className)}
87
- {...props}
88
- >
89
- <BaseCombobox.ItemIndicator className={comboboxItemIndicator}>
90
- <CheckIcon />
91
- </BaseCombobox.ItemIndicator>
92
- <span className={comboboxItemText}>{children}</span>
93
- </BaseCombobox.Item>
94
- );
95
- });
96
-
97
- export const ComboboxEmpty = React.forwardRef<
98
- HTMLDivElement,
99
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Empty>>
100
- >(function ComboboxEmpty({ className, ...props }, ref) {
101
- return (
102
- <BaseCombobox.Empty
103
- ref={ref}
104
- className={cn(combobox__empty, className)}
105
- {...props}
106
- />
107
- );
108
- });
109
-
110
- export const ComboboxGroupLabel = React.forwardRef<
111
- HTMLDivElement,
112
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.GroupLabel>>
113
- >(function ComboboxGroupLabel({ className, ...props }, ref) {
114
- return (
115
- <BaseCombobox.GroupLabel
116
- ref={ref}
117
- className={cn(comboboxGroupLabel, className)}
118
- {...props}
119
- />
120
- );
121
- });
122
-
123
- /** 다중 선택 칩. */
124
- export const ComboboxChip = React.forwardRef<
125
- HTMLDivElement,
126
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Chip>>
127
- >(function ComboboxChip({ className, ...props }, ref) {
128
- return (
129
- <BaseCombobox.Chip
130
- ref={ref}
131
- className={cn(combobox__chip, className)}
132
- {...props}
133
- />
134
- );
135
- });
136
-
137
- /** 칩 × 제거 버튼. */
138
- export const ComboboxChipRemove = React.forwardRef<
139
- HTMLButtonElement,
140
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.ChipRemove>>
141
- >(function ComboboxChipRemove({ className, children, ...props }, ref) {
142
- return (
143
- <BaseCombobox.ChipRemove
144
- ref={ref}
145
- className={cn(comboboxChipRemove, className)}
146
- {...props}
147
- >
148
- {children ?? "×"}
149
- </BaseCombobox.ChipRemove>
150
- );
151
- });
152
-
153
- function CheckIcon() {
154
- return (
155
- <svg viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden>
156
- <path
157
- d="M3 8.5l3 3 7-7"
158
- stroke="currentColor"
159
- strokeWidth="2"
160
- strokeLinecap="round"
161
- strokeLinejoin="round"
162
- />
163
- </svg>
164
- );
165
- }
@@ -1,174 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const combobox__input = style({
4
- display: "inline-flex",
5
- width: "100%",
6
- minWidth: "10rem",
7
- height: "var(--control-md)",
8
- padding: "0 var(--space-3)",
9
- background: "var(--background)",
10
- color: "var(--foreground)",
11
- border: "1px solid var(--border)",
12
- borderRadius: "var(--radius)",
13
- fontSize: "var(--text-sm)",
14
- lineHeight: 1,
15
- outline: "none",
16
- transition: "border-color var(--duration-fast)",
17
- selectors: {
18
- "&::placeholder": {
19
- color: "var(--foreground-subtle)",
20
- },
21
- "&:hover:not(:disabled)": {
22
- borderColor: "var(--border-strong)",
23
- },
24
- "&:focus-visible": {
25
- outline: "var(--border-width-strong) solid var(--foreground)",
26
- outlineOffset: "2px",
27
- },
28
- "&:disabled": {
29
- opacity: "var(--opacity-disabled)",
30
- pointerEvents: "none",
31
- },
32
- },
33
- });
34
-
35
- export const combobox__positioner = style({
36
- zIndex: "var(--z-dropdown)",
37
- outline: "none",
38
- width: "var(--anchor-width)",
39
- });
40
-
41
- export const combobox__content = style({
42
- maxHeight: "min(20rem, var(--available-height))",
43
- overflowY: "auto",
44
- padding: "var(--space-1)",
45
- background: "var(--background)",
46
- color: "var(--foreground)",
47
- border: "1px solid var(--border)",
48
- borderRadius: "var(--radius)",
49
- boxShadow: "0 8px 24px rgba(0, 0, 0, 0.08)",
50
- outline: "none",
51
- transformOrigin: "var(--transform-origin)",
52
- transition: "opacity 140ms ease, transform 140ms ease",
53
- selectors: {
54
- "&[data-starting-style]": {
55
- opacity: 0,
56
- transform: "scale(0.97)",
57
- },
58
- "&[data-ending-style]": {
59
- opacity: 0,
60
- transform: "scale(0.97)",
61
- },
62
- },
63
- });
64
-
65
- export const combobox__item = style({
66
- display: "flex",
67
- alignItems: "center",
68
- gap: "var(--space-2)",
69
- padding: "0.375rem 0.75rem",
70
- fontSize: "var(--text-sm)",
71
- lineHeight: 1.4,
72
- borderRadius: "calc(var(--radius) - 2px)",
73
- cursor: "pointer",
74
- userSelect: "none",
75
- outline: "none",
76
- selectors: {
77
- "&[data-highlighted]": {
78
- background: "var(--background-muted)",
79
- },
80
- "&:hover": {
81
- background: "var(--background-muted)",
82
- },
83
- "&[data-selected]": {
84
- color: "var(--foreground)",
85
- fontWeight: "var(--weight-medium)",
86
- },
87
- "&[data-disabled]": {
88
- opacity: "var(--opacity-disabled)",
89
- pointerEvents: "none",
90
- },
91
- },
92
- });
93
-
94
- export const comboboxItemIndicator = style({
95
- order: 1,
96
- marginLeft: "auto",
97
- display: "inline-flex",
98
- alignItems: "center",
99
- justifyContent: "center",
100
- color: "var(--foreground)",
101
- });
102
-
103
- export const comboboxItemText = style({
104
- flex: 1,
105
- overflow: "hidden",
106
- textOverflow: "ellipsis",
107
- whiteSpace: "nowrap",
108
- });
109
-
110
- export const combobox__empty = style({
111
- padding: "var(--space-3) var(--space-2)",
112
- textAlign: "center",
113
- fontSize: "0.8125rem",
114
- color: "var(--foreground-muted)",
115
- });
116
-
117
- export const comboboxGroupLabel = style({
118
- padding: "0.375rem var(--space-2) var(--space-1)",
119
- fontSize: "var(--text-xs)",
120
- fontWeight: "var(--weight-semibold)",
121
- color: "var(--foreground-muted)",
122
- textTransform: "uppercase",
123
- letterSpacing: "0.04em",
124
- });
125
-
126
- export const combobox__chip = style({
127
- display: "inline-flex",
128
- alignItems: "center",
129
- gap: "var(--space-1)",
130
- padding: "0.125rem 0.375rem 0.125rem var(--space-2)",
131
- marginRight: "var(--space-1)",
132
- fontSize: "var(--text-xs)",
133
- lineHeight: "1.25rem",
134
- background: "var(--background-muted)",
135
- borderRadius: "calc(var(--radius) - 2px)",
136
- whiteSpace: "nowrap",
137
- });
138
-
139
- export const comboboxChipRemove = style({
140
- display: "inline-flex",
141
- alignItems: "center",
142
- justifyContent: "center",
143
- width: "1rem",
144
- height: "1rem",
145
- padding: 0,
146
- border: 0,
147
- borderRadius: "999px",
148
- background: "transparent",
149
- color: "var(--foreground-muted)",
150
- fontSize: "var(--text-sm)",
151
- lineHeight: 1,
152
- cursor: "pointer",
153
- transition: "background-color var(--duration-fast), color var(--duration-fast)",
154
- selectors: {
155
- "&:hover": {
156
- background: "var(--background)",
157
- color: "var(--foreground)",
158
- },
159
- },
160
- });
161
-
162
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
163
- export const byKey: Record<string, string> = {
164
- "combobox__input": combobox__input,
165
- "combobox__positioner": combobox__positioner,
166
- "combobox__content": combobox__content,
167
- "combobox__item": combobox__item,
168
- "combobox__item-indicator": comboboxItemIndicator,
169
- "combobox__item-text": comboboxItemText,
170
- "combobox__empty": combobox__empty,
171
- "combobox__group-label": comboboxGroupLabel,
172
- "combobox__chip": combobox__chip,
173
- "combobox__chip-remove": comboboxChipRemove,
174
- };
@@ -1,251 +0,0 @@
1
- import * as React from "react";
2
- import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
3
- import { byKey, cm__trigger, cm__positioner, cm__content, cm__item, cmItemText, cmItemCheck, cmItemIndicator, cm__group, cm__label, cm__separator, cmSubArrow, cmSubTrigger } from "./styles.css";
4
-
5
- import { cn } from "@SH_UI_UTILS@";
6
- type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
7
-
8
-
9
- /* ───────── Root ───────── */
10
-
11
- /**
12
- * 우클릭(또는 long-press)으로 열리는 컨텍스트 메뉴 루트. 자식으로 Trigger와 Content를 둔다.
13
- * 사용 가능 액션이 명시적으로 보이는 게 유리한 경우엔 일반 버튼 + DropdownMenu를 권장.
14
- */
15
- export const ContextMenu = BaseContextMenu.Root;
16
-
17
- /* ───────── Trigger ─────────
18
- * 우클릭(또는 long-press)을 감지하는 wrapper. 기본은 투명, 사용자는
19
- * 자신의 영역(Card, 이미지 등)에 적용하여 감싼다.
20
- */
21
-
22
- /** 우클릭/long-press를 감지할 영역. Card나 이미지 등 임의 영역을 자식으로 감싼다. */
23
- export const ContextMenuTrigger = React.forwardRef<
24
- HTMLDivElement,
25
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Trigger>>
26
- >(function ContextMenuTrigger({ className, ...props }, ref) {
27
- return (
28
- <BaseContextMenu.Trigger
29
- ref={ref}
30
- className={cn(cm__trigger, className)}
31
- {...props}
32
- />
33
- );
34
- });
35
-
36
- /* ───────── Content ───────── */
37
-
38
- export interface ContextMenuContentProps
39
- extends WithStringClassName<
40
- React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup>
41
- > {
42
- /**
43
- * Portal이 마운트될 DOM 노드.
44
- * @default document.body
45
- */
46
- container?: React.ComponentPropsWithoutRef<
47
- typeof BaseContextMenu.Portal
48
- >["container"];
49
- }
50
-
51
- /** 메뉴의 실제 콘텐츠. portal로 마운트되며 클릭 위치 기준으로 자동 배치된다. */
52
- export const ContextMenuContent = React.forwardRef<
53
- HTMLDivElement,
54
- ContextMenuContentProps
55
- >(function ContextMenuContent({ className, children, container, ...props }, ref) {
56
- return (
57
- <BaseContextMenu.Portal container={container}>
58
- <BaseContextMenu.Positioner className={cm__positioner}>
59
- <BaseContextMenu.Popup
60
- ref={ref}
61
- className={cn(cm__content, className)}
62
- {...props}
63
- >
64
- {children}
65
- </BaseContextMenu.Popup>
66
- </BaseContextMenu.Positioner>
67
- </BaseContextMenu.Portal>
68
- );
69
- });
70
-
71
- /* ───────── Item ───────── */
72
-
73
- /** 일반 메뉴 항목. 클릭 시 메뉴가 닫히고 onClick이 발생한다. */
74
- export const ContextMenuItem = React.forwardRef<
75
- HTMLDivElement,
76
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Item>>
77
- >(function ContextMenuItem({ className, ...props }, ref) {
78
- return (
79
- <BaseContextMenu.Item
80
- ref={ref}
81
- className={cn(cm__item, className)}
82
- {...props}
83
- />
84
- );
85
- });
86
-
87
- /* ───────── CheckboxItem / RadioItem ───────── */
88
-
89
- /** 체크 표시를 토글하는 메뉴 항목. 메뉴는 닫지 않고 상태만 바꾸는 옵션 ON/OFF 용도. */
90
- export const ContextMenuCheckboxItem = React.forwardRef<
91
- HTMLDivElement,
92
- WithStringClassName<
93
- React.ComponentPropsWithoutRef<typeof BaseContextMenu.CheckboxItem>
94
- >
95
- >(function ContextMenuCheckboxItem({ className, children, ...props }, ref) {
96
- return (
97
- <BaseContextMenu.CheckboxItem
98
- ref={ref}
99
- className={cn(cm__item, cmItemCheck, className)}
100
- {...props}
101
- >
102
- <span className={cmItemIndicator} aria-hidden>
103
- <BaseContextMenu.CheckboxItemIndicator>
104
- <CheckIcon />
105
- </BaseContextMenu.CheckboxItemIndicator>
106
- </span>
107
- <span className={cmItemText}>{children}</span>
108
- </BaseContextMenu.CheckboxItem>
109
- );
110
- });
111
-
112
- /** RadioItem들을 단일 선택 그룹으로 묶는 컨테이너. `value`로 선택값을 제어. */
113
- export const ContextMenuRadioGroup = BaseContextMenu.RadioGroup;
114
-
115
- /** ContextMenuRadioGroup 내부의 단일 선택 항목. */
116
- export const ContextMenuRadioItem = React.forwardRef<
117
- HTMLDivElement,
118
- WithStringClassName<
119
- React.ComponentPropsWithoutRef<typeof BaseContextMenu.RadioItem>
120
- >
121
- >(function ContextMenuRadioItem({ className, children, ...props }, ref) {
122
- return (
123
- <BaseContextMenu.RadioItem
124
- ref={ref}
125
- className={cn(cm__item, cmItemCheck, className)}
126
- {...props}
127
- >
128
- <span className={cmItemIndicator} aria-hidden>
129
- <BaseContextMenu.RadioItemIndicator>
130
- <DotIcon />
131
- </BaseContextMenu.RadioItemIndicator>
132
- </span>
133
- <span className={cmItemText}>{children}</span>
134
- </BaseContextMenu.RadioItem>
135
- );
136
- });
137
-
138
- /* ───────── Group / Label / Separator ───────── */
139
-
140
- /** 의미적으로 묶이는 항목 그룹. ContextMenuLabel과 함께 사용해 카테고리 헤더를 붙인다. */
141
- export const ContextMenuGroup = React.forwardRef<
142
- HTMLDivElement,
143
- WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Group>>
144
- >(function ContextMenuGroup({ className, ...props }, ref) {
145
- return (
146
- <BaseContextMenu.Group
147
- ref={ref}
148
- className={cn(cm__group, className)}
149
- {...props}
150
- />
151
- );
152
- });
153
-
154
- /** 그룹의 카테고리 헤더 라벨. 클릭 불가, 시각·접근성 컨텍스트 제공용. */
155
- export const ContextMenuLabel = React.forwardRef<
156
- HTMLDivElement,
157
- WithStringClassName<
158
- React.ComponentPropsWithoutRef<typeof BaseContextMenu.GroupLabel>
159
- >
160
- >(function ContextMenuLabel({ className, ...props }, ref) {
161
- return (
162
- <BaseContextMenu.GroupLabel
163
- ref={ref}
164
- className={cn(cm__label, className)}
165
- {...props}
166
- />
167
- );
168
- });
169
-
170
- /** 항목 사이의 시각적 구분선. */
171
- export const ContextMenuSeparator = React.forwardRef<
172
- HTMLDivElement,
173
- React.HTMLAttributes<HTMLDivElement>
174
- >(function ContextMenuSeparator({ className, ...props }, ref) {
175
- return (
176
- <div
177
- ref={ref}
178
- role="separator"
179
- aria-orientation="horizontal"
180
- className={cn(cm__separator, className)}
181
- {...props}
182
- />
183
- );
184
- });
185
-
186
- /* ───────── Submenu ───────── */
187
-
188
- /** 서브메뉴 루트. 호버/포커스 시 우측으로 자식 메뉴를 펼친다. */
189
- export const ContextMenuSub = BaseContextMenu.SubmenuRoot;
190
-
191
- /** 서브메뉴를 여는 항목. 우측 화살표 아이콘이 자동 부착된다. */
192
- export const ContextMenuSubTrigger = React.forwardRef<
193
- HTMLDivElement,
194
- WithStringClassName<
195
- React.ComponentPropsWithoutRef<typeof BaseContextMenu.SubmenuTrigger>
196
- >
197
- >(function ContextMenuSubTrigger({ className, children, ...props }, ref) {
198
- return (
199
- <BaseContextMenu.SubmenuTrigger
200
- ref={ref}
201
- className={cn(cm__item, cmSubTrigger, className)}
202
- {...props}
203
- >
204
- <span className={cmItemText}>{children}</span>
205
- <span className={cmSubArrow} aria-hidden>
206
- <ChevronRightIcon />
207
- </span>
208
- </BaseContextMenu.SubmenuTrigger>
209
- );
210
- });
211
-
212
- /** 서브메뉴의 콘텐츠. ContextMenuContent의 별칭이다. */
213
- export const ContextMenuSubContent = ContextMenuContent;
214
-
215
- /* ───────── 기본 아이콘 ───────── */
216
-
217
- function CheckIcon() {
218
- return (
219
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
220
- <path
221
- d="M3.5 8.5l3 3 6-7"
222
- stroke="currentColor"
223
- strokeWidth="1.75"
224
- strokeLinecap="round"
225
- strokeLinejoin="round"
226
- />
227
- </svg>
228
- );
229
- }
230
-
231
- function DotIcon() {
232
- return (
233
- <svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor" aria-hidden>
234
- <circle cx="4" cy="4" r="3" />
235
- </svg>
236
- );
237
- }
238
-
239
- function ChevronRightIcon() {
240
- return (
241
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
242
- <path
243
- d="M6 4l4 4-4 4"
244
- stroke="currentColor"
245
- strokeWidth="1.5"
246
- strokeLinecap="round"
247
- strokeLinejoin="round"
248
- />
249
- </svg>
250
- );
251
- }