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.
- package/README.md +6 -1
- package/data/changelog/versions.json +25 -0
- package/data/registry/flutter/registry.json +1 -1
- package/data/registry/react/components/accordion/index.tailwind.tsx +88 -0
- package/data/registry/react/components/avatar/index.tailwind.tsx +74 -0
- package/data/registry/react/components/badge/index.tailwind.tsx +47 -0
- package/data/registry/react/components/breadcrumb/index.tailwind.tsx +138 -0
- package/data/registry/react/components/button/index.tailwind.tsx +70 -0
- package/data/registry/react/components/card/index.tailwind.tsx +111 -0
- package/data/registry/react/components/checkbox/index.tailwind.tsx +72 -0
- package/data/registry/react/components/code-panel/index.tailwind.tsx +107 -0
- package/data/registry/react/components/combobox/index.tailwind.tsx +160 -0
- package/data/registry/react/components/context-menu/index.tailwind.tsx +170 -0
- package/data/registry/react/components/date-picker/index.tailwind.tsx +294 -0
- package/data/registry/react/components/dialog/index.tailwind.tsx +96 -0
- package/data/registry/react/components/dropdown-menu/index.tailwind.tsx +205 -0
- package/data/registry/react/components/input/index.tailwind.tsx +405 -0
- package/data/registry/react/components/label/index.tailwind.tsx +78 -0
- package/data/registry/react/components/menubar/index.tailwind.tsx +32 -0
- package/data/registry/react/components/numeric-input/index.tailwind.tsx +113 -0
- package/data/registry/react/components/page-toc/index.tailwind.tsx +149 -0
- package/data/registry/react/components/pagination/index.tailwind.tsx +148 -0
- package/data/registry/react/components/popover/index.tailwind.tsx +77 -0
- package/data/registry/react/components/progress/index.tailwind.tsx +60 -0
- package/data/registry/react/components/radio/index.tailwind.tsx +54 -0
- package/data/registry/react/components/select/index.tailwind.tsx +199 -0
- package/data/registry/react/components/separator/index.tailwind.tsx +42 -0
- package/data/registry/react/components/skeleton/index.tailwind.tsx +39 -0
- package/data/registry/react/components/slider/index.tailwind.tsx +255 -0
- package/data/registry/react/components/spinner/index.tailwind.tsx +63 -0
- package/data/registry/react/components/switch/index.tailwind.tsx +62 -0
- package/data/registry/react/components/tabs/index.tailwind.tsx +113 -0
- package/data/registry/react/components/textarea/index.tailwind.tsx +21 -0
- package/data/registry/react/components/toggle/index.tailwind.tsx +111 -0
- package/data/registry/react/components/tooltip/index.tailwind.tsx +55 -0
- package/data/registry/react/peer-versions.json +1 -0
- package/data/registry/react/registry.json +530 -72
- package/data/tokens/build.mjs +66 -0
- package/package.json +1 -1
- package/src/add.mjs +54 -6
- package/src/api.d.ts +14 -0
- package/src/api.js +4 -0
- package/src/constants.js +19 -0
- package/src/create/cli-args.js +18 -2
- package/src/create/generator.js +55 -6
- package/src/create/index.mjs +3 -1
- package/src/init.mjs +25 -7
- package/src/mcp.mjs +13 -2
- package/templates/flutter-standalone/sh-ui.config.json +1 -1
- package/templates/nextjs-standalone/app/globals.css +1 -21
- package/templates/nextjs-standalone/sh-ui.config.json +1 -1
- package/templates/ui-app-template/sh-ui.config.json +1 -1
- 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
|
+
}
|