sh-ui-cli 0.43.0 → 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 (33) hide show
  1. package/data/changelog/versions.json +12 -0
  2. package/data/registry/react/components/accordion/index.tailwind.tsx +88 -0
  3. package/data/registry/react/components/avatar/index.tailwind.tsx +74 -0
  4. package/data/registry/react/components/badge/index.tailwind.tsx +47 -0
  5. package/data/registry/react/components/breadcrumb/index.tailwind.tsx +138 -0
  6. package/data/registry/react/components/checkbox/index.tailwind.tsx +72 -0
  7. package/data/registry/react/components/code-panel/index.tailwind.tsx +107 -0
  8. package/data/registry/react/components/combobox/index.tailwind.tsx +160 -0
  9. package/data/registry/react/components/context-menu/index.tailwind.tsx +170 -0
  10. package/data/registry/react/components/date-picker/index.tailwind.tsx +294 -0
  11. package/data/registry/react/components/dialog/index.tailwind.tsx +96 -0
  12. package/data/registry/react/components/dropdown-menu/index.tailwind.tsx +205 -0
  13. package/data/registry/react/components/label/index.tailwind.tsx +78 -0
  14. package/data/registry/react/components/menubar/index.tailwind.tsx +32 -0
  15. package/data/registry/react/components/numeric-input/index.tailwind.tsx +113 -0
  16. package/data/registry/react/components/page-toc/index.tailwind.tsx +149 -0
  17. package/data/registry/react/components/pagination/index.tailwind.tsx +148 -0
  18. package/data/registry/react/components/popover/index.tailwind.tsx +77 -0
  19. package/data/registry/react/components/progress/index.tailwind.tsx +60 -0
  20. package/data/registry/react/components/radio/index.tailwind.tsx +54 -0
  21. package/data/registry/react/components/select/index.tailwind.tsx +199 -0
  22. package/data/registry/react/components/separator/index.tailwind.tsx +42 -0
  23. package/data/registry/react/components/skeleton/index.tailwind.tsx +39 -0
  24. package/data/registry/react/components/slider/index.tailwind.tsx +255 -0
  25. package/data/registry/react/components/spinner/index.tailwind.tsx +63 -0
  26. package/data/registry/react/components/switch/index.tailwind.tsx +62 -0
  27. package/data/registry/react/components/tabs/index.tailwind.tsx +113 -0
  28. package/data/registry/react/components/textarea/index.tailwind.tsx +21 -0
  29. package/data/registry/react/components/toggle/index.tailwind.tsx +111 -0
  30. package/data/registry/react/components/tooltip/index.tailwind.tsx +55 -0
  31. package/data/registry/react/registry.json +509 -74
  32. package/package.json +1 -1
  33. package/src/mcp.mjs +1 -1
@@ -0,0 +1,255 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ function cx(...args: (string | undefined | false | null)[]) {
6
+ return args.filter(Boolean).join(" ");
7
+ }
8
+
9
+ function clamp(n: number, min: number, max: number) {
10
+ return Math.min(max, Math.max(min, n));
11
+ }
12
+
13
+ function snap(value: number, step: number, min: number) {
14
+ if (step <= 0) return value;
15
+ return min + Math.round((value - min) / step) * step;
16
+ }
17
+
18
+ interface SliderContextValue {
19
+ value: number;
20
+ setValue: (next: number) => void;
21
+ min: number;
22
+ max: number;
23
+ step: number;
24
+ disabled: boolean;
25
+ ariaLabel?: string;
26
+ trackRef: React.RefObject<HTMLDivElement | null>;
27
+ setTrackEl: (el: HTMLDivElement | null) => void;
28
+ percent: string;
29
+ }
30
+
31
+ const SliderContext = React.createContext<SliderContextValue | null>(null);
32
+
33
+ function useSliderContext(): SliderContextValue {
34
+ const ctx = React.useContext(SliderContext);
35
+ if (!ctx) throw new Error("Slider 하위 컴포넌트는 <Slider> 안에서만 사용할 수 있습니다.");
36
+ return ctx;
37
+ }
38
+
39
+ export function useSliderState(): Pick<
40
+ SliderContextValue,
41
+ "value" | "min" | "max" | "step" | "disabled"
42
+ > {
43
+ const { value, min, max, step, disabled } = useSliderContext();
44
+ return { value, min, max, step, disabled };
45
+ }
46
+
47
+ export interface SliderProps
48
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
49
+ value?: number;
50
+ defaultValue?: number;
51
+ onValueChange?: (value: number) => void;
52
+ min?: number;
53
+ max?: number;
54
+ step?: number;
55
+ disabled?: boolean;
56
+ "aria-label"?: string;
57
+ }
58
+
59
+ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
60
+ function Slider(
61
+ {
62
+ value: valueProp,
63
+ defaultValue = 0,
64
+ onValueChange,
65
+ min = 0,
66
+ max = 100,
67
+ step = 1,
68
+ disabled = false,
69
+ className,
70
+ children,
71
+ "aria-label": ariaLabel,
72
+ ...rest
73
+ },
74
+ ref,
75
+ ) {
76
+ const isControlled = valueProp !== undefined;
77
+ const [internal, setInternal] = React.useState(defaultValue);
78
+ const rawValue = isControlled ? (valueProp as number) : internal;
79
+ const value = clamp(rawValue, min, max);
80
+
81
+ const trackRef = React.useRef<HTMLDivElement | null>(null);
82
+ const setTrackEl = React.useCallback((el: HTMLDivElement | null) => {
83
+ trackRef.current = el;
84
+ }, []);
85
+
86
+ const setValue = React.useCallback(
87
+ (next: number) => {
88
+ const snapped = clamp(snap(next, step, min), min, max);
89
+ if (snapped === value) return;
90
+ if (!isControlled) setInternal(snapped);
91
+ onValueChange?.(snapped);
92
+ },
93
+ [isControlled, max, min, onValueChange, step, value],
94
+ );
95
+
96
+ const ratio = max === min ? 0 : (value - min) / (max - min);
97
+ const percent = `${ratio * 100}%`;
98
+
99
+ const ctxValue = React.useMemo<SliderContextValue>(
100
+ () => ({
101
+ value, setValue, min, max, step, disabled, ariaLabel,
102
+ trackRef, setTrackEl, percent,
103
+ }),
104
+ [ariaLabel, disabled, max, min, percent, setTrackEl, setValue, step, value],
105
+ );
106
+
107
+ return (
108
+ <SliderContext.Provider value={ctxValue}>
109
+ <div
110
+ ref={ref}
111
+ {...rest}
112
+ className={cx(
113
+ "relative w-full py-[var(--space-2)] select-none",
114
+ disabled && "opacity-[var(--opacity-disabled)] pointer-events-none",
115
+ className,
116
+ )}
117
+ data-disabled={disabled || undefined}
118
+ >
119
+ {children ?? (
120
+ <SliderTrack>
121
+ <SliderRange />
122
+ <SliderThumb />
123
+ </SliderTrack>
124
+ )}
125
+ </div>
126
+ </SliderContext.Provider>
127
+ );
128
+ },
129
+ );
130
+ Slider.displayName = "Slider";
131
+
132
+ export const SliderTrack = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
133
+ function SliderTrack({ className, onPointerDown: userOnPointerDown, children, ...props }, ref) {
134
+ const { disabled, setValue, min, max, setTrackEl, trackRef } = useSliderContext();
135
+
136
+ const mergedRef = React.useCallback(
137
+ (el: HTMLDivElement | null) => {
138
+ setTrackEl(el);
139
+ if (typeof ref === "function") ref(el);
140
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;
141
+ },
142
+ [ref, setTrackEl],
143
+ );
144
+
145
+ const moveToClient = (clientX: number) => {
146
+ const el = trackRef.current;
147
+ if (!el) return;
148
+ const r = el.getBoundingClientRect();
149
+ const ratio = clamp((clientX - r.left) / r.width, 0, 1);
150
+ setValue(min + ratio * (max - min));
151
+ };
152
+
153
+ const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
154
+ userOnPointerDown?.(e);
155
+ if (e.defaultPrevented || disabled) return;
156
+ const el = trackRef.current;
157
+ if (!el) return;
158
+ el.setPointerCapture(e.pointerId);
159
+ moveToClient(e.clientX);
160
+
161
+ const onMove = (ev: PointerEvent) => moveToClient(ev.clientX);
162
+ const onUp = (ev: PointerEvent) => {
163
+ el.releasePointerCapture(ev.pointerId);
164
+ el.removeEventListener("pointermove", onMove);
165
+ el.removeEventListener("pointerup", onUp);
166
+ };
167
+ el.addEventListener("pointermove", onMove);
168
+ el.addEventListener("pointerup", onUp);
169
+ };
170
+
171
+ return (
172
+ <div
173
+ ref={mergedRef}
174
+ className={cx(
175
+ "relative w-full h-1.5 bg-background-muted rounded-full cursor-pointer touch-none",
176
+ className,
177
+ )}
178
+ onPointerDown={onPointerDown}
179
+ {...props}
180
+ >
181
+ {children ?? (
182
+ <>
183
+ <SliderRange />
184
+ <SliderThumb />
185
+ </>
186
+ )}
187
+ </div>
188
+ );
189
+ },
190
+ );
191
+ SliderTrack.displayName = "SliderTrack";
192
+
193
+ export const SliderRange = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
194
+ function SliderRange({ className, style, ...props }, ref) {
195
+ const { percent } = useSliderContext();
196
+ return (
197
+ <div
198
+ ref={ref}
199
+ className={cx(
200
+ "absolute top-0 left-0 h-full bg-primary rounded-full pointer-events-none",
201
+ className,
202
+ )}
203
+ style={{ width: percent, ...style }}
204
+ {...props}
205
+ />
206
+ );
207
+ },
208
+ );
209
+ SliderRange.displayName = "SliderRange";
210
+
211
+ export const SliderThumb = React.forwardRef<
212
+ HTMLDivElement,
213
+ Omit<React.HTMLAttributes<HTMLDivElement>, "role" | "tabIndex">
214
+ >(function SliderThumb({ className, style, onKeyDown: userOnKeyDown, ...props }, ref) {
215
+ const { value, setValue, min, max, step, disabled, ariaLabel, percent } = useSliderContext();
216
+
217
+ const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
218
+ userOnKeyDown?.(e);
219
+ if (e.defaultPrevented || disabled) return;
220
+ const big = e.shiftKey ? step * 10 : step;
221
+ switch (e.key) {
222
+ case "ArrowRight":
223
+ case "ArrowUp":
224
+ e.preventDefault(); setValue(value + big); break;
225
+ case "ArrowLeft":
226
+ case "ArrowDown":
227
+ e.preventDefault(); setValue(value - big); break;
228
+ case "Home": e.preventDefault(); setValue(min); break;
229
+ case "End": e.preventDefault(); setValue(max); break;
230
+ case "PageUp": e.preventDefault(); setValue(value + step * 10); break;
231
+ case "PageDown": e.preventDefault(); setValue(value - step * 10); break;
232
+ }
233
+ };
234
+
235
+ return (
236
+ <div
237
+ ref={ref}
238
+ role="slider"
239
+ tabIndex={disabled ? -1 : 0}
240
+ aria-label={ariaLabel}
241
+ aria-valuemin={min}
242
+ aria-valuemax={max}
243
+ aria-valuenow={value}
244
+ aria-disabled={disabled || undefined}
245
+ onKeyDown={onKeyDown}
246
+ className={cx(
247
+ "absolute top-1/2 w-4 h-4 -ml-2 -translate-y-1/2 bg-background border-2 border-primary rounded-full shadow-[0_1px_2px_rgba(0,0,0,0.1)] cursor-grab transition-transform duration-[80ms] active:cursor-grabbing active:scale-110 active:-translate-y-1/2 focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 [@media(hover:none)_and_(pointer:coarse)]:w-5 [@media(hover:none)_and_(pointer:coarse)]:h-5 [@media(hover:none)_and_(pointer:coarse)]:-ml-2.5",
248
+ className,
249
+ )}
250
+ style={{ left: percent, ...style }}
251
+ {...props}
252
+ />
253
+ );
254
+ });
255
+ SliderThumb.displayName = "SliderThumb";
@@ -0,0 +1,63 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ function cx(...args: (string | undefined | false | null)[]) {
5
+ return args.filter(Boolean).join(" ");
6
+ }
7
+
8
+ const spinnerVariants = cva(
9
+ "inline-flex items-center justify-center align-middle text-current",
10
+ {
11
+ variants: {
12
+ size: {
13
+ sm: "w-3.5 h-3.5",
14
+ md: "w-[1.125rem] h-[1.125rem]",
15
+ lg: "w-6 h-6",
16
+ },
17
+ },
18
+ defaultVariants: { size: "md" },
19
+ },
20
+ );
21
+
22
+ export type SpinnerSize = NonNullable<VariantProps<typeof spinnerVariants>["size"]>;
23
+
24
+ export interface SpinnerProps
25
+ extends Omit<React.HTMLAttributes<HTMLSpanElement>, "role"> {
26
+ size?: SpinnerSize;
27
+ "aria-label"?: string;
28
+ }
29
+
30
+ export const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
31
+ function Spinner(
32
+ { size = "md", className, "aria-label": ariaLabel = "로딩 중", ...props },
33
+ ref,
34
+ ) {
35
+ const ringBorder = size === "sm" ? "border-[1.5px]" : "border-2";
36
+ return (
37
+ <span
38
+ ref={ref}
39
+ role="status"
40
+ aria-live="polite"
41
+ aria-label={ariaLabel}
42
+ className={cx(spinnerVariants({ size }), className)}
43
+ {...props}
44
+ >
45
+ <span
46
+ aria-hidden
47
+ className={cx(
48
+ "inline-block w-full h-full rounded-full border-current border-t-transparent opacity-80 animate-[sh-ui-spinner-rotate_0.8s_linear_infinite] motion-reduce:[animation-duration:3s]",
49
+ ringBorder,
50
+ )}
51
+ />
52
+ </span>
53
+ );
54
+ },
55
+ );
56
+ Spinner.displayName = "Spinner";
57
+
58
+ if (typeof document !== "undefined" && !document.querySelector("style[data-sh-ui-spinner]")) {
59
+ const style = document.createElement("style");
60
+ style.setAttribute("data-sh-ui-spinner", "");
61
+ style.textContent = `@keyframes sh-ui-spinner-rotate { to { transform: rotate(360deg) } }`;
62
+ document.head.appendChild(style);
63
+ }
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ import { Switch as BaseSwitch } from "@base-ui/react/switch";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ function cx(...args: (string | undefined | false)[]) {
6
+ return args.filter(Boolean).join(" ");
7
+ }
8
+
9
+ const switchRoot = cva(
10
+ "inline-flex items-center border-none rounded-full bg-background-muted cursor-pointer shrink-0 p-0.5 transition-colors duration-150 hover:not-data-[disabled]:bg-border-strong focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 data-[checked]:bg-primary data-[checked]:hover:not-data-[disabled]:bg-primary-hover data-[disabled]:opacity-[var(--opacity-disabled)] data-[disabled]:cursor-not-allowed motion-reduce:transition-none",
11
+ {
12
+ variants: {
13
+ size: {
14
+ sm: "w-8 h-[1.125rem]",
15
+ md: "w-10 h-[1.375rem]",
16
+ },
17
+ },
18
+ defaultVariants: { size: "md" },
19
+ },
20
+ );
21
+
22
+ const switchThumb = cva(
23
+ "block rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.12)] transition-transform duration-150 ease-out motion-reduce:transition-none",
24
+ {
25
+ variants: {
26
+ size: {
27
+ sm: "w-3.5 h-3.5",
28
+ md: "w-[1.125rem] h-[1.125rem]",
29
+ },
30
+ },
31
+ defaultVariants: { size: "md" },
32
+ },
33
+ );
34
+
35
+ type SwitchSize = NonNullable<VariantProps<typeof switchRoot>["size"]>;
36
+
37
+ export type SwitchProps = Omit<
38
+ React.ComponentPropsWithoutRef<typeof BaseSwitch.Root>,
39
+ "className"
40
+ > & {
41
+ className?: string;
42
+ size?: SwitchSize;
43
+ };
44
+
45
+ export const Switch = React.forwardRef<HTMLElement, SwitchProps>(
46
+ ({ className, size = "md", ...props }, ref) => (
47
+ <BaseSwitch.Root
48
+ ref={ref}
49
+ className={cx(switchRoot({ size }), className)}
50
+ {...props}
51
+ >
52
+ <BaseSwitch.Thumb
53
+ className={cx(
54
+ switchThumb({ size }),
55
+ size === "sm" && "data-[checked]:translate-x-3.5",
56
+ size === "md" && "data-[checked]:translate-x-[1.125rem]",
57
+ )}
58
+ />
59
+ </BaseSwitch.Root>
60
+ ),
61
+ );
62
+ Switch.displayName = "Switch";
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Tabs as BaseTabs } from "@base-ui/react/tabs";
5
+
6
+ function cx(...args: (string | undefined | false)[]) {
7
+ return args.filter(Boolean).join(" ");
8
+ }
9
+
10
+ type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
11
+
12
+ export type TabsVariant = "underline" | "pill" | "plain";
13
+
14
+ interface TabsContextValue { variant: TabsVariant; }
15
+ const TabsContext = React.createContext<TabsContextValue>({ variant: "underline" });
16
+
17
+ export type TabsProps = WithStringClassName<
18
+ React.ComponentPropsWithoutRef<typeof BaseTabs.Root>
19
+ > & {
20
+ variant?: TabsVariant;
21
+ };
22
+
23
+ export const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
24
+ ({ className, variant = "underline", ...props }, ref) => (
25
+ <TabsContext.Provider value={{ variant }}>
26
+ <BaseTabs.Root
27
+ ref={ref}
28
+ data-variant={variant}
29
+ className={cx("flex flex-col gap-[var(--space-3)] w-full data-[orientation=vertical]:flex-row", className)}
30
+ {...props}
31
+ />
32
+ </TabsContext.Provider>
33
+ ),
34
+ );
35
+ Tabs.displayName = "Tabs";
36
+
37
+ const listVariantClasses =
38
+ "data-[variant=underline]:[&]:w-full data-[variant=underline]:[&]:gap-0 data-[variant=underline]:[&]:shadow-[inset_0_-1px_0_var(--border)] data-[variant=pill]:[&]:p-[var(--space-1)] data-[variant=pill]:[&]:bg-[var(--background-muted,var(--background))] data-[variant=pill]:[&]:border data-[variant=pill]:[&]:border-border data-[variant=pill]:[&]:rounded-[var(--radius)] [[data-orientation=vertical]_&]:flex-col [[data-orientation=vertical]_&]:items-stretch [[data-orientation=vertical][data-variant=underline]_&]:w-auto [[data-orientation=vertical][data-variant=underline]_&]:shadow-[inset_-1px_0_0_var(--border)]";
39
+
40
+ export const TabsList = React.forwardRef<
41
+ HTMLDivElement,
42
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseTabs.List>>
43
+ >(({ className, ...props }, ref) => {
44
+ const { variant } = React.useContext(TabsContext);
45
+ return (
46
+ <BaseTabs.List
47
+ ref={ref}
48
+ data-variant={variant}
49
+ className={cx(
50
+ "relative inline-flex items-center gap-[var(--space-1)] w-fit",
51
+ listVariantClasses,
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ );
57
+ });
58
+ TabsList.displayName = "TabsList";
59
+
60
+ const triggerVariantClasses =
61
+ "[[data-variant=underline]_&]:py-2.5 [[data-variant=underline]_&]:px-[var(--space-4)] [[data-variant=pill]_&]:py-1.5 [[data-variant=pill]_&]:px-[var(--space-3)] [[data-variant=pill]_&]:rounded-[calc(var(--radius)-2px)] [[data-variant=plain]_&]:py-1.5 [[data-variant=plain]_&]:px-[var(--space-2)] [[data-variant=plain]_&]:rounded-[calc(var(--radius)-2px)] data-[selected]:[[data-variant=plain]_&]:bg-[var(--background-muted,transparent)]";
62
+
63
+ export const TabsTrigger = React.forwardRef<
64
+ HTMLButtonElement,
65
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseTabs.Tab>>
66
+ >(({ className, ...props }, ref) => (
67
+ <BaseTabs.Tab
68
+ ref={ref}
69
+ className={cx(
70
+ "relative z-[1] inline-flex items-center justify-center gap-1.5 py-[var(--space-2)] px-[var(--space-3)] bg-transparent text-foreground-muted border-0 text-[length:var(--text-sm)] font-medium leading-none cursor-pointer transition-[color,background-color] duration-[var(--duration-fast)] select-none whitespace-nowrap hover:not-disabled:not-data-[selected]:text-foreground focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 data-[selected]:text-foreground disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed",
71
+ triggerVariantClasses,
72
+ className,
73
+ )}
74
+ {...props}
75
+ />
76
+ ));
77
+ TabsTrigger.displayName = "TabsTrigger";
78
+
79
+ const indicatorVariantClasses =
80
+ "[[data-variant=underline]_&]:shadow-[inset_0_-2px_0_var(--foreground)] [[data-variant=pill]_&]:bg-background [[data-variant=pill]_&]:rounded-[calc(var(--radius)-2px)] [[data-variant=pill]_&]:shadow-[0_1px_2px_rgba(0,0,0,0.06)] [[data-variant=plain]_&]:hidden [[data-orientation=vertical][data-variant=underline]_&]:shadow-[inset_-2px_0_0_var(--foreground)]";
81
+
82
+ export const TabsIndicator = React.forwardRef<
83
+ HTMLSpanElement,
84
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseTabs.Indicator>>
85
+ >(({ className, ...props }, ref) => (
86
+ <BaseTabs.Indicator
87
+ ref={ref}
88
+ className={cx(
89
+ "absolute z-0 pointer-events-none transition-[top,left,width,height] duration-[180ms] data-[activation-direction=none]:transition-none top-[var(--active-tab-top)] left-[var(--active-tab-left)] w-[var(--active-tab-width)] h-[var(--active-tab-height)]",
90
+ indicatorVariantClasses,
91
+ className,
92
+ )}
93
+ {...props}
94
+ />
95
+ ));
96
+ TabsIndicator.displayName = "TabsIndicator";
97
+
98
+ export const TabsContent = React.forwardRef<
99
+ HTMLDivElement,
100
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseTabs.Panel>>
101
+ >(({ className, ...props }, ref) => (
102
+ <BaseTabs.Panel
103
+ ref={ref}
104
+ className={cx(
105
+ "outline-none focus-visible:outline-[length:var(--border-width-strong)] focus-visible:outline-foreground focus-visible:outline-offset-2 focus-visible:rounded-[var(--radius)]",
106
+ className,
107
+ )}
108
+ {...props}
109
+ />
110
+ ));
111
+ TabsContent.displayName = "TabsContent";
112
+
113
+ export const useTabsVariant = () => React.useContext(TabsContext).variant;
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
4
+
5
+ function cx(...args: (string | undefined | false)[]) {
6
+ return args.filter(Boolean).join(" ");
7
+ }
8
+
9
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
10
+ ({ className, ...props }, ref) => (
11
+ <textarea
12
+ ref={ref}
13
+ className={cx(
14
+ "block w-full min-h-20 px-[var(--space-3)] py-[var(--space-2)] bg-background text-foreground border border-border rounded-[var(--radius)] font-[inherit] text-[length:var(--text-sm)] leading-normal resize-y transition-[border-color,box-shadow] duration-[var(--duration-fast)] placeholder:text-foreground-subtle hover:not-disabled:not-focus:border-border-strong focus:outline-none focus:border-foreground focus:shadow-[0_0_0_1px_var(--foreground)] disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed disabled:bg-background-subtle read-only:bg-background-subtle aria-[invalid=true]:border-danger aria-[invalid=true]:focus:shadow-[0_0_0_1px_var(--danger)] [@media(hover:none)_and_(pointer:coarse)]:text-[length:var(--text-base)]",
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ ),
20
+ );
21
+ Textarea.displayName = "Textarea";
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Toggle as BaseToggle } from "@base-ui/react/toggle";
5
+ import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
6
+ import { cva, type VariantProps } from "class-variance-authority";
7
+
8
+ function cx(...args: (string | undefined | false)[]) {
9
+ return args.filter(Boolean).join(" ");
10
+ }
11
+
12
+ const toggleVariants = cva(
13
+ "inline-flex items-center justify-center gap-1.5 border border-transparent rounded-[var(--radius)] font-medium leading-none cursor-pointer text-foreground-muted bg-transparent transition-[background-color,color,border-color] duration-[var(--duration-fast)] select-none 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 data-[pressed]:bg-background-muted data-[pressed]:text-foreground motion-reduce:transition-none",
14
+ {
15
+ variants: {
16
+ variant: {
17
+ outline:
18
+ "border-border hover:not-disabled:not-data-[pressed]:bg-background-muted hover:not-disabled:not-data-[pressed]:text-foreground data-[pressed]:border-border-strong",
19
+ ghost:
20
+ "hover:not-disabled:not-data-[pressed]:bg-background-muted hover:not-disabled:not-data-[pressed]:text-foreground",
21
+ },
22
+ size: {
23
+ sm: "h-[var(--control-sm)] px-2.5 text-[length:var(--text-sm)] [@media(hover:none)_and_(pointer:coarse)]:h-9",
24
+ md: "h-[var(--control-md)] px-[var(--space-3)] text-[length:var(--text-sm)] [@media(hover:none)_and_(pointer:coarse)]:h-11",
25
+ lg: "h-[var(--control-lg)] px-[var(--space-4)] text-[length:var(--text-base)]",
26
+ },
27
+ },
28
+ defaultVariants: { variant: "ghost", size: "md" },
29
+ },
30
+ );
31
+
32
+ export type ToggleVariant = NonNullable<VariantProps<typeof toggleVariants>["variant"]>;
33
+ export type ToggleSize = NonNullable<VariantProps<typeof toggleVariants>["size"]>;
34
+
35
+ export type ToggleProps = Omit<
36
+ React.ComponentPropsWithoutRef<typeof BaseToggle>,
37
+ "className"
38
+ > & {
39
+ className?: string;
40
+ variant?: ToggleVariant;
41
+ size?: ToggleSize;
42
+ };
43
+
44
+ export const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
45
+ ({ className, variant = "ghost", size = "md", ...props }, ref) => (
46
+ <BaseToggle
47
+ ref={ref}
48
+ className={cx(toggleVariants({ variant, size }), className)}
49
+ {...props}
50
+ />
51
+ ),
52
+ );
53
+ Toggle.displayName = "Toggle";
54
+
55
+ export type ToggleGroupProps = Omit<
56
+ React.ComponentPropsWithoutRef<typeof BaseToggleGroup>,
57
+ "className"
58
+ > & {
59
+ className?: string;
60
+ variant?: ToggleVariant;
61
+ size?: ToggleSize;
62
+ };
63
+
64
+ interface ToggleGroupContextValue {
65
+ variant: ToggleVariant;
66
+ size: ToggleSize;
67
+ }
68
+
69
+ const ToggleGroupContext = React.createContext<ToggleGroupContextValue>({
70
+ variant: "ghost",
71
+ size: "md",
72
+ });
73
+
74
+ export const useToggleGroupStyle = () => React.useContext(ToggleGroupContext);
75
+
76
+ export const ToggleGroup = React.forwardRef<HTMLDivElement, ToggleGroupProps>(
77
+ ({ className, variant = "ghost", size = "md", ...props }, ref) => (
78
+ <ToggleGroupContext.Provider value={{ variant, size }}>
79
+ <BaseToggleGroup
80
+ ref={ref}
81
+ className={cx(
82
+ "inline-flex items-center gap-[var(--space-1)] data-[orientation=vertical]:flex-col",
83
+ className,
84
+ )}
85
+ {...props}
86
+ />
87
+ </ToggleGroupContext.Provider>
88
+ ),
89
+ );
90
+ ToggleGroup.displayName = "ToggleGroup";
91
+
92
+ export type ToggleGroupItemProps = Omit<
93
+ React.ComponentPropsWithoutRef<typeof BaseToggle>,
94
+ "className"
95
+ > & {
96
+ className?: string;
97
+ };
98
+
99
+ export const ToggleGroupItem = React.forwardRef<HTMLButtonElement, ToggleGroupItemProps>(
100
+ ({ className, ...props }, ref) => {
101
+ const { variant, size } = useToggleGroupStyle();
102
+ return (
103
+ <BaseToggle
104
+ ref={ref}
105
+ className={cx(toggleVariants({ variant, size }), className)}
106
+ {...props}
107
+ />
108
+ );
109
+ },
110
+ );
111
+ ToggleGroupItem.displayName = "ToggleGroupItem";
@@ -0,0 +1,55 @@
1
+ import * as React from "react";
2
+ import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
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
+ export const TooltipProvider = BaseTooltip.Provider;
11
+ export const Tooltip = BaseTooltip.Root;
12
+ export const TooltipTrigger = BaseTooltip.Trigger;
13
+
14
+ export interface TooltipContentProps
15
+ extends WithStringClassName<
16
+ React.ComponentPropsWithoutRef<typeof BaseTooltip.Popup>
17
+ > {
18
+ side?: "top" | "right" | "bottom" | "left";
19
+ align?: "start" | "center" | "end";
20
+ sideOffset?: number;
21
+ showArrow?: boolean;
22
+ container?: React.ComponentPropsWithoutRef<
23
+ typeof BaseTooltip.Portal
24
+ >["container"];
25
+ }
26
+
27
+ export const TooltipContent = React.forwardRef<HTMLDivElement, TooltipContentProps>(
28
+ function TooltipContent(
29
+ { className, children, side, align, sideOffset = 6, showArrow, container, ...props },
30
+ ref,
31
+ ) {
32
+ return (
33
+ <BaseTooltip.Portal container={container}>
34
+ <BaseTooltip.Positioner
35
+ className="z-[var(--z-tooltip,var(--z-popover))] outline-none"
36
+ side={side}
37
+ align={align}
38
+ sideOffset={sideOffset}
39
+ >
40
+ <BaseTooltip.Popup
41
+ ref={ref}
42
+ className={cx(
43
+ "px-2.5 py-1.5 bg-foreground text-background rounded-[calc(var(--radius)-2px)] text-[length:var(--text-xs)] leading-snug max-w-xs shadow-[0_4px_12px_rgba(0,0,0,0.12)] origin-[var(--transform-origin)] outline-none transition-[opacity,transform] duration-[120ms] ease-out motion-reduce:transition-none data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 motion-reduce:data-[starting-style]:scale-100 motion-reduce:data-[ending-style]:scale-100",
44
+ className,
45
+ )}
46
+ {...props}
47
+ >
48
+ {showArrow && <BaseTooltip.Arrow className="text-foreground [&_svg]:block" />}
49
+ {children}
50
+ </BaseTooltip.Popup>
51
+ </BaseTooltip.Positioner>
52
+ </BaseTooltip.Portal>
53
+ );
54
+ },
55
+ );