sh-ui-cli 0.42.1 → 0.44.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 (53) hide show
  1. package/README.md +6 -1
  2. package/data/changelog/versions.json +25 -0
  3. package/data/registry/flutter/registry.json +1 -1
  4. package/data/registry/react/components/accordion/index.tailwind.tsx +88 -0
  5. package/data/registry/react/components/avatar/index.tailwind.tsx +74 -0
  6. package/data/registry/react/components/badge/index.tailwind.tsx +47 -0
  7. package/data/registry/react/components/breadcrumb/index.tailwind.tsx +138 -0
  8. package/data/registry/react/components/button/index.tailwind.tsx +70 -0
  9. package/data/registry/react/components/card/index.tailwind.tsx +111 -0
  10. package/data/registry/react/components/checkbox/index.tailwind.tsx +72 -0
  11. package/data/registry/react/components/code-panel/index.tailwind.tsx +107 -0
  12. package/data/registry/react/components/combobox/index.tailwind.tsx +160 -0
  13. package/data/registry/react/components/context-menu/index.tailwind.tsx +170 -0
  14. package/data/registry/react/components/date-picker/index.tailwind.tsx +294 -0
  15. package/data/registry/react/components/dialog/index.tailwind.tsx +96 -0
  16. package/data/registry/react/components/dropdown-menu/index.tailwind.tsx +205 -0
  17. package/data/registry/react/components/input/index.tailwind.tsx +405 -0
  18. package/data/registry/react/components/label/index.tailwind.tsx +78 -0
  19. package/data/registry/react/components/menubar/index.tailwind.tsx +32 -0
  20. package/data/registry/react/components/numeric-input/index.tailwind.tsx +113 -0
  21. package/data/registry/react/components/page-toc/index.tailwind.tsx +149 -0
  22. package/data/registry/react/components/pagination/index.tailwind.tsx +148 -0
  23. package/data/registry/react/components/popover/index.tailwind.tsx +77 -0
  24. package/data/registry/react/components/progress/index.tailwind.tsx +60 -0
  25. package/data/registry/react/components/radio/index.tailwind.tsx +54 -0
  26. package/data/registry/react/components/select/index.tailwind.tsx +199 -0
  27. package/data/registry/react/components/separator/index.tailwind.tsx +42 -0
  28. package/data/registry/react/components/skeleton/index.tailwind.tsx +39 -0
  29. package/data/registry/react/components/slider/index.tailwind.tsx +255 -0
  30. package/data/registry/react/components/spinner/index.tailwind.tsx +63 -0
  31. package/data/registry/react/components/switch/index.tailwind.tsx +62 -0
  32. package/data/registry/react/components/tabs/index.tailwind.tsx +113 -0
  33. package/data/registry/react/components/textarea/index.tailwind.tsx +21 -0
  34. package/data/registry/react/components/toggle/index.tailwind.tsx +111 -0
  35. package/data/registry/react/components/tooltip/index.tailwind.tsx +55 -0
  36. package/data/registry/react/peer-versions.json +1 -0
  37. package/data/registry/react/registry.json +530 -72
  38. package/data/tokens/build.mjs +66 -0
  39. package/package.json +1 -1
  40. package/src/add.mjs +54 -6
  41. package/src/api.d.ts +14 -0
  42. package/src/api.js +4 -0
  43. package/src/constants.js +19 -0
  44. package/src/create/cli-args.js +18 -2
  45. package/src/create/generator.js +55 -6
  46. package/src/create/index.mjs +3 -1
  47. package/src/init.mjs +25 -7
  48. package/src/mcp.mjs +13 -2
  49. package/templates/flutter-standalone/sh-ui.config.json +1 -1
  50. package/templates/nextjs-standalone/app/globals.css +1 -21
  51. package/templates/nextjs-standalone/sh-ui.config.json +1 -1
  52. package/templates/ui-app-template/sh-ui.config.json +1 -1
  53. package/templates/ui-app-template/src/styles/globals.css +1 -21
@@ -0,0 +1,107 @@
1
+ import { codeToHtml } from "shiki";
2
+ import { CodePanelCopyButton } from "./copy";
3
+
4
+ function cx(...args: (string | undefined | false | null)[]) {
5
+ return args.filter(Boolean).join(" ");
6
+ }
7
+
8
+ export interface CodePanelProps
9
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
10
+ code?: string;
11
+ language?: string;
12
+ filename?: string;
13
+ showLineNumbers?: boolean;
14
+ hideCopy?: boolean;
15
+ children?: React.ReactNode;
16
+ }
17
+
18
+ const rootClasses =
19
+ "group relative border border-border rounded-[var(--radius)] bg-background-subtle overflow-hidden text-[0.8125rem] leading-relaxed my-[var(--space-4)] max-sm:text-[length:var(--text-xs)]";
20
+
21
+ export async function CodePanel({
22
+ code, language = "text", filename, showLineNumbers = true, hideCopy, className, children, ...rest
23
+ }: CodePanelProps) {
24
+ const classes = cx(rootClasses, className);
25
+
26
+ if (children !== undefined) {
27
+ return <div className={classes} {...rest}>{children}</div>;
28
+ }
29
+ if (code === undefined) throw new Error("CodePanel: `code` prop 또는 children 중 하나가 필요합니다.");
30
+
31
+ const trimmed = code.replace(/\n$/, "");
32
+
33
+ return (
34
+ <div className={classes} {...rest}>
35
+ {filename ? (
36
+ <CodePanelHeader>
37
+ <CodePanelFilename>{filename}</CodePanelFilename>
38
+ {!hideCopy && <CodePanelCopy code={trimmed} />}
39
+ </CodePanelHeader>
40
+ ) : (
41
+ !hideCopy && (
42
+ <div className="absolute top-[var(--space-2)] right-[var(--space-2)] z-[1] opacity-0 transition-opacity duration-[var(--duration-fast)] group-hover:opacity-100 group-focus-within:opacity-100">
43
+ <CodePanelCopy code={trimmed} />
44
+ </div>
45
+ )
46
+ )}
47
+ <CodePanelBody code={trimmed} language={language} showLineNumbers={showLineNumbers} />
48
+ </div>
49
+ );
50
+ }
51
+
52
+ export function CodePanelHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
53
+ return (
54
+ <div
55
+ className={cx(
56
+ "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
+ className,
58
+ )}
59
+ {...props}
60
+ >
61
+ {children}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export function CodePanelFilename({ className, children, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
67
+ return (
68
+ <span className={cx("font-mono text-foreground", className)} {...props}>{children}</span>
69
+ );
70
+ }
71
+
72
+ export interface CodePanelCopyProps { code: string; }
73
+ export function CodePanelCopy({ code }: CodePanelCopyProps) {
74
+ return <CodePanelCopyButton code={code} />;
75
+ }
76
+
77
+ export interface CodePanelBodyProps
78
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "dangerouslySetInnerHTML"> {
79
+ code: string;
80
+ language?: string;
81
+ showLineNumbers?: boolean;
82
+ }
83
+
84
+ export async function CodePanelBody({
85
+ code, language = "text", showLineNumbers = true, className, ...rest
86
+ }: CodePanelBodyProps) {
87
+ const trimmed = code.replace(/\n$/, "");
88
+ const html = await codeToHtml(trimmed, {
89
+ lang: language,
90
+ themes: { light: "github-light", dark: "github-dark" },
91
+ defaultColor: false,
92
+ });
93
+
94
+ return (
95
+ <div
96
+ className={cx(
97
+ "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
+ className,
99
+ )}
100
+ data-line-numbers={showLineNumbers || undefined}
101
+ dangerouslySetInnerHTML={{ __html: html }}
102
+ {...rest}
103
+ />
104
+ );
105
+ }
106
+
107
+ export { CodePanelCopyButton };
@@ -0,0 +1,160 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
5
+
6
+ type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
7
+
8
+ function cx(...args: (string | undefined | false)[]) {
9
+ return args.filter(Boolean).join(" ");
10
+ }
11
+
12
+ export const Combobox = BaseCombobox.Root;
13
+ export const ComboboxIcon = BaseCombobox.Icon;
14
+ export const ComboboxTrigger = BaseCombobox.Trigger;
15
+ export const ComboboxClear = BaseCombobox.Clear;
16
+ export const ComboboxValue = BaseCombobox.Value;
17
+ export const ComboboxGroup = BaseCombobox.Group;
18
+ export const ComboboxChips = BaseCombobox.Chips;
19
+
20
+ export const ComboboxInput = React.forwardRef<
21
+ HTMLInputElement,
22
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Input>>
23
+ >(function ComboboxInput({ className, ...props }, ref) {
24
+ return (
25
+ <BaseCombobox.Input
26
+ ref={ref}
27
+ className={cx(
28
+ "inline-flex w-full min-w-40 h-[var(--control-md)] px-[var(--space-3)] bg-background text-foreground border border-border rounded-[var(--radius)] text-[length:var(--text-sm)] leading-none outline-none transition-[border-color] duration-[var(--duration-fast)] placeholder:text-foreground-subtle hover:not-disabled:border-border-strong focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 disabled:opacity-[var(--opacity-disabled)] disabled:pointer-events-none",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ });
35
+
36
+ export const ComboboxContent = React.forwardRef<
37
+ HTMLDivElement,
38
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Popup>> & {
39
+ container?: React.ComponentPropsWithoutRef<typeof BaseCombobox.Portal>["container"];
40
+ sideOffset?: number;
41
+ }
42
+ >(function ComboboxContent({ className, children, container, sideOffset = 4, ...props }, ref) {
43
+ return (
44
+ <BaseCombobox.Portal container={container}>
45
+ <BaseCombobox.Positioner
46
+ className="z-[var(--z-dropdown)] outline-none w-[var(--anchor-width)]"
47
+ sideOffset={sideOffset}
48
+ align="start"
49
+ >
50
+ <BaseCombobox.Popup
51
+ ref={ref}
52
+ className={cx(
53
+ "max-h-[min(20rem,var(--available-height))] overflow-y-auto p-[var(--space-1)] bg-background text-foreground border border-border rounded-[var(--radius)] shadow-[0_8px_24px_rgba(0,0,0,0.08)] outline-none origin-[var(--transform-origin)] transition-[opacity,transform] duration-[140ms] ease-out motion-reduce:transition-none data-[starting-style]:opacity-0 data-[starting-style]:scale-[0.97] data-[ending-style]:opacity-0 data-[ending-style]:scale-[0.97]",
54
+ className,
55
+ )}
56
+ {...props}
57
+ >
58
+ {children}
59
+ </BaseCombobox.Popup>
60
+ </BaseCombobox.Positioner>
61
+ </BaseCombobox.Portal>
62
+ );
63
+ });
64
+
65
+ export const ComboboxList = BaseCombobox.List;
66
+
67
+ export const ComboboxItem = React.forwardRef<
68
+ HTMLDivElement,
69
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Item>>
70
+ >(function ComboboxItem({ className, children, ...props }, ref) {
71
+ return (
72
+ <BaseCombobox.Item
73
+ ref={ref}
74
+ className={cx(
75
+ "flex items-center gap-[var(--space-2)] py-1.5 px-3 text-[length:var(--text-sm)] leading-snug rounded-[calc(var(--radius)-2px)] cursor-pointer select-none outline-none data-[highlighted]:bg-background-muted hover:bg-background-muted data-[selected]:text-foreground data-[selected]:font-medium data-[disabled]:opacity-[var(--opacity-disabled)] data-[disabled]:pointer-events-none",
76
+ className,
77
+ )}
78
+ {...props}
79
+ >
80
+ <BaseCombobox.ItemIndicator className="order-1 ml-auto inline-flex items-center justify-center text-foreground">
81
+ <CheckIcon />
82
+ </BaseCombobox.ItemIndicator>
83
+ <span className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">{children}</span>
84
+ </BaseCombobox.Item>
85
+ );
86
+ });
87
+
88
+ export const ComboboxEmpty = React.forwardRef<
89
+ HTMLDivElement,
90
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Empty>>
91
+ >(function ComboboxEmpty({ className, ...props }, ref) {
92
+ return (
93
+ <BaseCombobox.Empty
94
+ ref={ref}
95
+ className={cx(
96
+ "py-[var(--space-3)] px-[var(--space-2)] text-center text-[0.8125rem] text-foreground-muted",
97
+ className,
98
+ )}
99
+ {...props}
100
+ />
101
+ );
102
+ });
103
+
104
+ export const ComboboxGroupLabel = React.forwardRef<
105
+ HTMLDivElement,
106
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.GroupLabel>>
107
+ >(function ComboboxGroupLabel({ className, ...props }, ref) {
108
+ return (
109
+ <BaseCombobox.GroupLabel
110
+ ref={ref}
111
+ className={cx(
112
+ "py-1.5 px-[var(--space-2)] pb-[var(--space-1)] text-[length:var(--text-xs)] font-semibold text-foreground-muted uppercase tracking-[0.04em]",
113
+ className,
114
+ )}
115
+ {...props}
116
+ />
117
+ );
118
+ });
119
+
120
+ export const ComboboxChip = React.forwardRef<
121
+ HTMLDivElement,
122
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.Chip>>
123
+ >(function ComboboxChip({ className, ...props }, ref) {
124
+ return (
125
+ <BaseCombobox.Chip
126
+ ref={ref}
127
+ className={cx(
128
+ "inline-flex items-center gap-[var(--space-1)] py-0.5 pr-1.5 pl-[var(--space-2)] mr-[var(--space-1)] text-[length:var(--text-xs)] leading-5 bg-background-muted rounded-[calc(var(--radius)-2px)] whitespace-nowrap",
129
+ className,
130
+ )}
131
+ {...props}
132
+ />
133
+ );
134
+ });
135
+
136
+ export const ComboboxChipRemove = React.forwardRef<
137
+ HTMLButtonElement,
138
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseCombobox.ChipRemove>>
139
+ >(function ComboboxChipRemove({ className, children, ...props }, ref) {
140
+ return (
141
+ <BaseCombobox.ChipRemove
142
+ ref={ref}
143
+ className={cx(
144
+ "inline-flex items-center justify-center w-4 h-4 p-0 border-0 rounded-full bg-transparent text-foreground-muted text-[length:var(--text-sm)] leading-none cursor-pointer transition-[background-color,color] duration-[var(--duration-fast)] hover:bg-background hover:text-foreground motion-reduce:transition-none",
145
+ className,
146
+ )}
147
+ {...props}
148
+ >
149
+ {children ?? "×"}
150
+ </BaseCombobox.ChipRemove>
151
+ );
152
+ });
153
+
154
+ function CheckIcon() {
155
+ return (
156
+ <svg viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden>
157
+ <path d="M3 8.5l3 3 7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
158
+ </svg>
159
+ );
160
+ }
@@ -0,0 +1,170 @@
1
+ import * as React from "react";
2
+ import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
3
+
4
+ type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
5
+
6
+ function cx(...args: (string | undefined | false | null)[]) {
7
+ return args.filter(Boolean).join(" ");
8
+ }
9
+
10
+ const itemBase =
11
+ "relative flex items-center gap-[var(--space-2)] py-2 px-3 rounded-[calc(var(--radius)-2px)] cursor-pointer outline-none select-none transition-colors duration-[80ms] data-[highlighted]:bg-background-muted hover:bg-background-muted data-[disabled]:opacity-[var(--opacity-disabled)] data-[disabled]:pointer-events-none motion-reduce:transition-none";
12
+ const itemCheck = "pl-7";
13
+ const contentClasses =
14
+ "min-w-40 max-h-[min(24rem,var(--available-height,24rem))] overflow-y-auto p-[var(--space-1)] bg-background text-foreground border border-border rounded-[var(--radius)] shadow-[0_4px_6px_-1px_rgba(0,0,0,0.08),0_2px_4px_-2px_rgba(0,0,0,0.05)] text-[length:var(--text-sm)] origin-[var(--transform-origin)] animate-[sh-ui-cm-in_140ms_ease-out] data-[ending-style]:animate-[sh-ui-cm-out_100ms_ease-in_forwards] outline-none motion-reduce:animate-none motion-reduce:data-[ending-style]:animate-none";
15
+
16
+ export const ContextMenu = BaseContextMenu.Root;
17
+
18
+ export const ContextMenuTrigger = React.forwardRef<
19
+ HTMLDivElement,
20
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Trigger>>
21
+ >(function ContextMenuTrigger({ className, ...props }, ref) {
22
+ return <BaseContextMenu.Trigger ref={ref} className={cx("contents", className)} {...props} />;
23
+ });
24
+
25
+ export interface ContextMenuContentProps
26
+ extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Popup>> {
27
+ container?: React.ComponentPropsWithoutRef<typeof BaseContextMenu.Portal>["container"];
28
+ }
29
+
30
+ export const ContextMenuContent = React.forwardRef<HTMLDivElement, ContextMenuContentProps>(
31
+ function ContextMenuContent({ className, children, container, ...props }, ref) {
32
+ return (
33
+ <BaseContextMenu.Portal container={container}>
34
+ <BaseContextMenu.Positioner className="outline-none z-[var(--z-dropdown)]">
35
+ <BaseContextMenu.Popup ref={ref} className={cx(contentClasses, className)} {...props}>
36
+ {children}
37
+ </BaseContextMenu.Popup>
38
+ </BaseContextMenu.Positioner>
39
+ </BaseContextMenu.Portal>
40
+ );
41
+ },
42
+ );
43
+
44
+ export const ContextMenuItem = React.forwardRef<
45
+ HTMLDivElement,
46
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Item>>
47
+ >(function ContextMenuItem({ className, ...props }, ref) {
48
+ return <BaseContextMenu.Item ref={ref} className={cx(itemBase, className)} {...props} />;
49
+ });
50
+
51
+ export const ContextMenuCheckboxItem = React.forwardRef<
52
+ HTMLDivElement,
53
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.CheckboxItem>>
54
+ >(function ContextMenuCheckboxItem({ className, children, ...props }, ref) {
55
+ return (
56
+ <BaseContextMenu.CheckboxItem ref={ref} className={cx(itemBase, itemCheck, className)} {...props}>
57
+ <span className="absolute left-2 inline-flex items-center justify-center w-4 h-4 text-foreground" aria-hidden>
58
+ <BaseContextMenu.CheckboxItemIndicator>
59
+ <CheckIcon />
60
+ </BaseContextMenu.CheckboxItemIndicator>
61
+ </span>
62
+ <span className="flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">{children}</span>
63
+ </BaseContextMenu.CheckboxItem>
64
+ );
65
+ });
66
+
67
+ export const ContextMenuRadioGroup = BaseContextMenu.RadioGroup;
68
+
69
+ export const ContextMenuRadioItem = React.forwardRef<
70
+ HTMLDivElement,
71
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.RadioItem>>
72
+ >(function ContextMenuRadioItem({ className, children, ...props }, ref) {
73
+ return (
74
+ <BaseContextMenu.RadioItem ref={ref} className={cx(itemBase, itemCheck, className)} {...props}>
75
+ <span className="absolute left-2 inline-flex items-center justify-center w-4 h-4 text-foreground" aria-hidden>
76
+ <BaseContextMenu.RadioItemIndicator>
77
+ <DotIcon />
78
+ </BaseContextMenu.RadioItemIndicator>
79
+ </span>
80
+ <span className="flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">{children}</span>
81
+ </BaseContextMenu.RadioItem>
82
+ );
83
+ });
84
+
85
+ export const ContextMenuGroup = React.forwardRef<
86
+ HTMLDivElement,
87
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.Group>>
88
+ >(function ContextMenuGroup({ className, ...props }, ref) {
89
+ return <BaseContextMenu.Group ref={ref} className={cx("p-0", className)} {...props} />;
90
+ });
91
+
92
+ export const ContextMenuLabel = React.forwardRef<
93
+ HTMLDivElement,
94
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.GroupLabel>>
95
+ >(function ContextMenuLabel({ className, ...props }, ref) {
96
+ return (
97
+ <BaseContextMenu.GroupLabel
98
+ ref={ref}
99
+ className={cx(
100
+ "py-[var(--space-2)] px-[var(--space-2)] pb-[var(--space-1)] text-[length:var(--text-xs)] font-semibold text-foreground-muted uppercase tracking-[0.04em]",
101
+ className,
102
+ )}
103
+ {...props}
104
+ />
105
+ );
106
+ });
107
+
108
+ export const ContextMenuSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
109
+ function ContextMenuSeparator({ className, ...props }, ref) {
110
+ return (
111
+ <div
112
+ ref={ref}
113
+ role="separator"
114
+ aria-orientation="horizontal"
115
+ className={cx("h-px bg-border my-[var(--space-1)]", className)}
116
+ {...props}
117
+ />
118
+ );
119
+ },
120
+ );
121
+
122
+ export const ContextMenuSub = BaseContextMenu.SubmenuRoot;
123
+
124
+ export const ContextMenuSubTrigger = React.forwardRef<
125
+ HTMLDivElement,
126
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseContextMenu.SubmenuTrigger>>
127
+ >(function ContextMenuSubTrigger({ className, children, ...props }, ref) {
128
+ return (
129
+ <BaseContextMenu.SubmenuTrigger
130
+ ref={ref}
131
+ className={cx(itemBase, "data-[popup-open]:bg-background-muted", className)}
132
+ {...props}
133
+ >
134
+ <span className="flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">{children}</span>
135
+ <span className="inline-flex items-center justify-center ml-auto text-foreground-muted" aria-hidden>
136
+ <ChevronRightIcon />
137
+ </span>
138
+ </BaseContextMenu.SubmenuTrigger>
139
+ );
140
+ });
141
+
142
+ export const ContextMenuSubContent = ContextMenuContent;
143
+
144
+ function CheckIcon() {
145
+ return (
146
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
147
+ <path d="M3.5 8.5l3 3 6-7" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
148
+ </svg>
149
+ );
150
+ }
151
+ function DotIcon() {
152
+ return <svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor" aria-hidden><circle cx="4" cy="4" r="3" /></svg>;
153
+ }
154
+ function ChevronRightIcon() {
155
+ return (
156
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden>
157
+ <path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
158
+ </svg>
159
+ );
160
+ }
161
+
162
+ if (typeof document !== "undefined" && !document.querySelector("style[data-sh-ui-cm]")) {
163
+ const style = document.createElement("style");
164
+ style.setAttribute("data-sh-ui-cm", "");
165
+ style.textContent = `
166
+ @keyframes sh-ui-cm-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
167
+ @keyframes sh-ui-cm-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.96); } }
168
+ `;
169
+ document.head.appendChild(style);
170
+ }