@hogoshi/ui 0.0.0-stage → 0.1.1

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 (57) hide show
  1. package/package.json +63 -4
  2. package/src/components/avatar.tsx +30 -0
  3. package/src/components/badge.tsx +26 -0
  4. package/src/components/breadcrumb.tsx +37 -0
  5. package/src/components/button.tsx +58 -0
  6. package/src/components/calendar.tsx +244 -0
  7. package/src/components/card.tsx +18 -0
  8. package/src/components/chart.tsx +387 -0
  9. package/src/components/checkbox.tsx +26 -0
  10. package/src/components/code-block.tsx +118 -0
  11. package/src/components/collapsible.tsx +31 -0
  12. package/src/components/command.tsx +111 -0
  13. package/src/components/context-menu.tsx +150 -0
  14. package/src/components/counter.tsx +56 -0
  15. package/src/components/dialog.tsx +36 -0
  16. package/src/components/dropdown-menu.tsx +54 -0
  17. package/src/components/file-upload.tsx +381 -0
  18. package/src/components/glide.tsx +82 -0
  19. package/src/components/icon.tsx +28 -0
  20. package/src/components/image-viewer.tsx +370 -0
  21. package/src/components/input.tsx +187 -0
  22. package/src/components/kbd.tsx +14 -0
  23. package/src/components/logo-mark.ts +8 -0
  24. package/src/components/logo.tsx +36 -0
  25. package/src/components/navbar.tsx +144 -0
  26. package/src/components/otp.tsx +93 -0
  27. package/src/components/page-loader.tsx +37 -0
  28. package/src/components/pophover.tsx +28 -0
  29. package/src/components/popover.tsx +36 -0
  30. package/src/components/popup.tsx +75 -0
  31. package/src/components/progress.tsx +26 -0
  32. package/src/components/result.tsx +22 -0
  33. package/src/components/scroll-area.tsx +38 -0
  34. package/src/components/select.tsx +52 -0
  35. package/src/components/separator.tsx +14 -0
  36. package/src/components/sheet.tsx +96 -0
  37. package/src/components/sidebar.tsx +195 -0
  38. package/src/components/skeleton.tsx +14 -0
  39. package/src/components/slider.tsx +31 -0
  40. package/src/components/smooth-scroll.tsx +174 -0
  41. package/src/components/spinner.tsx +18 -0
  42. package/src/components/switch.tsx +22 -0
  43. package/src/components/table.tsx +52 -0
  44. package/src/components/tabs.tsx +48 -0
  45. package/src/components/theme-toggle.tsx +214 -0
  46. package/src/components/toaster.tsx +38 -0
  47. package/src/components/tooltip.tsx +43 -0
  48. package/src/components/video-player.tsx +559 -0
  49. package/src/components/video-settings.tsx +233 -0
  50. package/src/index.ts +102 -0
  51. package/src/lib/cn.ts +6 -0
  52. package/src/lib/jsx-highlight.ts +128 -0
  53. package/src/styles.css +443 -0
  54. package/src/theme/theme-script.ts +13 -0
  55. package/src/theme/theme.tsx +77 -0
  56. package/styles.css +1 -0
  57. package/README.md +0 -3
@@ -0,0 +1,387 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useId,
8
+ useRef,
9
+ useState,
10
+ type CSSProperties,
11
+ type PointerEvent,
12
+ type ReactElement,
13
+ type ReactNode,
14
+ } from "react";
15
+ import {
16
+ Area,
17
+ AreaChart as AreaRoot,
18
+ Bar,
19
+ BarChart as BarRoot,
20
+ CartesianGrid,
21
+ Cell,
22
+ Line,
23
+ LineChart as LineRoot,
24
+ Pie,
25
+ PieChart,
26
+ ResponsiveContainer,
27
+ Tooltip,
28
+ XAxis,
29
+ YAxis,
30
+ } from "recharts";
31
+ import { cn } from "../lib/cn";
32
+
33
+ export type ChartConfig = Record<string, { label: string; color?: string }>;
34
+
35
+ type Row = Record<string, string | number>;
36
+ type Format = (value: number) => string;
37
+
38
+ const compact = new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 });
39
+ const formatValue: Format = (value) => compact.format(value);
40
+ const series = (key: string) => `var(--series-${key})`;
41
+
42
+ const axis = {
43
+ axisLine: false,
44
+ tickLine: false,
45
+ tickMargin: 8,
46
+ tick: { fill: "var(--fg-subtle)", fontSize: 12 },
47
+ } as const;
48
+
49
+ const margin = { top: 8, right: 8, bottom: 0, left: 0 };
50
+
51
+ function seriesVars(config: ChartConfig) {
52
+ return Object.fromEntries(
53
+ Object.entries(config).map(([key, item], index) => [`--series-${key}`, item.color ?? `var(--chart-${(index % 6) + 1})`]),
54
+ ) as CSSProperties;
55
+ }
56
+
57
+ function Dot({ name }: { name: string }) {
58
+ return <span className="size-2 shrink-0 rounded-full" style={{ background: series(name) }} />;
59
+ }
60
+
61
+ const ChartCursor = createContext<{ x: number; y: number } | null>(null);
62
+
63
+ function TooltipCard({
64
+ active,
65
+ payload,
66
+ label,
67
+ config,
68
+ format,
69
+ }: { active?: boolean; payload?: ReadonlyArray<{ name?: string | number; value?: string | number }>; label?: string | number; config: ChartConfig; format: Format }) {
70
+ if (!active || !payload?.length) return null;
71
+ return (
72
+ <div className="bg-bg-elevated/60 border-border grid min-w-36 gap-1.5 rounded-[var(--radius-md)] border px-3 py-2 text-xs shadow-[var(--shadow-md)] backdrop-blur-2xl">
73
+ {label !== undefined && label !== "" ? <p className="text-fg font-medium">{label}</p> : null}
74
+ {payload.map((item) => {
75
+ const name = String(item.name);
76
+ return (
77
+ <div key={name} className="flex items-center gap-2">
78
+ <Dot name={name} />
79
+ <span className="text-fg-muted">{config[name]?.label ?? name}</span>
80
+ <span className="text-fg ml-auto pl-3 font-medium tabular-nums">{format(Number(item.value))}</span>
81
+ </div>
82
+ );
83
+ })}
84
+ </div>
85
+ );
86
+ }
87
+
88
+ export function ChartLegend({ config, className }: { config: ChartConfig; className?: string }) {
89
+ return (
90
+ <div className={cn("text-fg-muted flex flex-wrap items-center justify-center gap-x-4 gap-y-1 text-xs", className)}>
91
+ {Object.entries(config).map(([key, item]) => (
92
+ <span key={key} className="flex items-center gap-1.5">
93
+ <Dot name={key} />
94
+ {item.label}
95
+ </span>
96
+ ))}
97
+ </div>
98
+ );
99
+ }
100
+
101
+ export function ChartContainer({
102
+ config,
103
+ legend = false,
104
+ className,
105
+ overlay,
106
+ children,
107
+ }: {
108
+ config: ChartConfig;
109
+ legend?: boolean;
110
+ className?: string;
111
+ overlay?: ReactNode;
112
+ children: ReactElement;
113
+ }) {
114
+ const [cursor, setCursor] = useState<{ x: number; y: number } | null>(null);
115
+ const frame = useRef(0);
116
+ const pending = useRef({ x: 0, y: 0 });
117
+
118
+ useEffect(() => () => cancelAnimationFrame(frame.current), []);
119
+
120
+ const track = (event: PointerEvent<HTMLDivElement>) => {
121
+ const plot = event.currentTarget;
122
+ const root = plot.querySelector(".recharts-wrapper") ?? plot;
123
+ const box = root.getBoundingClientRect();
124
+ const tip = plot.querySelector(".recharts-tooltip-wrapper") as HTMLElement | null;
125
+ const tw = Math.max(tip?.offsetWidth ?? 0, 148);
126
+ const th = Math.max(tip?.offsetHeight ?? 0, 52);
127
+ const gap = 12;
128
+ const cx = event.clientX - box.left;
129
+ const cy = event.clientY - box.top;
130
+ let x = cx + gap;
131
+ let y = cy + gap;
132
+ if (x + tw > box.width) x = cx - tw - gap;
133
+ if (y + th > box.height) y = cy - th - gap;
134
+ pending.current = {
135
+ x: Math.min(Math.max(0, x), Math.max(0, box.width - tw)),
136
+ y: Math.min(Math.max(0, y), Math.max(0, box.height - th)),
137
+ };
138
+ if (frame.current) return;
139
+ frame.current = requestAnimationFrame(() => {
140
+ frame.current = 0;
141
+ setCursor({ ...pending.current });
142
+ });
143
+ };
144
+
145
+ return (
146
+ <ChartCursor.Provider value={cursor}>
147
+ <div style={seriesVars(config)} className="grid w-full gap-3">
148
+ <div
149
+ className={cn(
150
+ "hogoshi-chart relative h-64 w-full text-xs select-none outline-none [&_*]:outline-none [&_.recharts-surface]:outline-none [&_.recharts-wrapper]:outline-none",
151
+ className,
152
+ )}
153
+ onMouseDown={(event) => event.preventDefault()}
154
+ onPointerEnter={track}
155
+ onPointerMove={track}
156
+ >
157
+ <ResponsiveContainer width="100%" height="100%">
158
+ {children}
159
+ </ResponsiveContainer>
160
+ {overlay}
161
+ </div>
162
+ {legend ? <ChartLegend config={config} /> : null}
163
+ </div>
164
+ </ChartCursor.Provider>
165
+ );
166
+ }
167
+
168
+ export interface CartesianChartProps {
169
+ data: Row[];
170
+ index: string;
171
+ config: ChartConfig;
172
+ stacked?: boolean;
173
+ legend?: boolean;
174
+ grid?: boolean;
175
+ valueFormatter?: Format;
176
+ className?: string;
177
+ }
178
+
179
+ function ChartTooltip({ config, format, cursor }: { config: ChartConfig; format: Format; cursor: object | false }) {
180
+ const pos = useContext(ChartCursor);
181
+ const [smooth, setSmooth] = useState(false);
182
+
183
+ useEffect(() => {
184
+ if (!pos || smooth) return;
185
+ const id = requestAnimationFrame(() => setSmooth(true));
186
+ return () => cancelAnimationFrame(id);
187
+ }, [pos, smooth]);
188
+
189
+ return (
190
+ <Tooltip
191
+ cursor={cursor}
192
+ isAnimationActive={false}
193
+ allowEscapeViewBox={{ x: true, y: true }}
194
+ wrapperStyle={
195
+ pos
196
+ ? {
197
+ transform: `translate(${pos.x}px, ${pos.y}px)`,
198
+ transition: smooth ? "transform var(--motion-duration) var(--motion-ease)" : "none",
199
+ }
200
+ : { transition: "none" }
201
+ }
202
+ content={({ active, payload, label }) => (
203
+ <TooltipCard
204
+ active={active}
205
+ payload={payload?.map((item) => ({ name: item.name, value: Array.isArray(item.value) ? item.value[0] : item.value }))}
206
+ label={typeof label === "object" ? undefined : label}
207
+ config={config}
208
+ format={format}
209
+ />
210
+ )}
211
+ />
212
+ );
213
+ }
214
+
215
+ function Axes({ index, format, grid }: { index: string; format: Format; grid: boolean }) {
216
+ return [
217
+ grid ? <CartesianGrid key="grid" vertical={false} stroke="var(--border)" strokeDasharray="3 3" /> : null,
218
+ <XAxis key="x" dataKey={index} minTickGap={24} {...axis} />,
219
+ <YAxis key="y" width={40} tickFormatter={format} {...axis} />,
220
+ ];
221
+ }
222
+
223
+ export function AreaChart({ data, index, config, stacked, legend, grid = true, valueFormatter = formatValue, className }: CartesianChartProps) {
224
+ const id = useId().replaceAll(":", "");
225
+ const keys = Object.keys(config);
226
+ return (
227
+ <ChartContainer config={config} legend={legend} className={className}>
228
+ <AreaRoot data={data} margin={margin}>
229
+ <defs>
230
+ {keys.map((key) => (
231
+ <linearGradient key={key} id={`${id}-${key}`} x1="0" y1="0" x2="0" y2="1">
232
+ <stop offset="0%" stopColor={series(key)} stopOpacity={0.32} />
233
+ <stop offset="100%" stopColor={series(key)} stopOpacity={0} />
234
+ </linearGradient>
235
+ ))}
236
+ </defs>
237
+ {Axes({ index, format: valueFormatter, grid })}
238
+ <ChartTooltip config={config} format={valueFormatter} cursor={{ stroke: "var(--border-strong)" }} />
239
+ {keys.map((key) => (
240
+ <Area
241
+ key={key}
242
+ name={key}
243
+ dataKey={key}
244
+ type="monotone"
245
+ stackId={stacked ? "stack" : undefined}
246
+ stroke={series(key)}
247
+ strokeWidth={2}
248
+ fill={`url(#${id}-${key})`}
249
+ activeDot={{ r: 4, strokeWidth: 0 }}
250
+ />
251
+ ))}
252
+ </AreaRoot>
253
+ </ChartContainer>
254
+ );
255
+ }
256
+
257
+ export function BarChart({
258
+ data,
259
+ index,
260
+ config,
261
+ stacked,
262
+ legend,
263
+ grid = true,
264
+ valueFormatter = formatValue,
265
+ className,
266
+ }: CartesianChartProps) {
267
+ const keys = Object.keys(config);
268
+ return (
269
+ <ChartContainer config={config} legend={legend} className={className}>
270
+ <BarRoot data={data} margin={margin} barGap={4}>
271
+ {Axes({ index, format: valueFormatter, grid })}
272
+ <ChartTooltip config={config} format={valueFormatter} cursor={{ fill: "var(--bg-subtle)" }} />
273
+ {keys.map((key, at) => (
274
+ <Bar
275
+ key={key}
276
+ name={key}
277
+ dataKey={key}
278
+ fill={series(key)}
279
+ maxBarSize={36}
280
+ stackId={stacked ? "stack" : undefined}
281
+ radius={!stacked || at === keys.length - 1 ? [6, 6, 0, 0] : 0}
282
+ />
283
+ ))}
284
+ </BarRoot>
285
+ </ChartContainer>
286
+ );
287
+ }
288
+
289
+ export function LineChart({ data, index, config, legend, grid = true, valueFormatter = formatValue, className }: Omit<CartesianChartProps, "stacked">) {
290
+ const keys = Object.keys(config);
291
+ return (
292
+ <ChartContainer config={config} legend={legend} className={className}>
293
+ <LineRoot data={data} margin={margin}>
294
+ {Axes({ index, format: valueFormatter, grid })}
295
+ <ChartTooltip config={config} format={valueFormatter} cursor={{ stroke: "var(--border-strong)" }} />
296
+ {keys.map((key) => (
297
+ <Line
298
+ key={key}
299
+ name={key}
300
+ dataKey={key}
301
+ type="monotone"
302
+ stroke={series(key)}
303
+ strokeWidth={2}
304
+ dot={false}
305
+ activeDot={{ r: 4, strokeWidth: 0 }}
306
+ />
307
+ ))}
308
+ </LineRoot>
309
+ </ChartContainer>
310
+ );
311
+ }
312
+
313
+ export function DonutChart({
314
+ data,
315
+ nameKey,
316
+ valueKey,
317
+ config,
318
+ label,
319
+ legend = true,
320
+ valueFormatter = formatValue,
321
+ className,
322
+ }: {
323
+ data: Row[];
324
+ nameKey: string;
325
+ valueKey: string;
326
+ config: ChartConfig;
327
+ label?: ReactNode;
328
+ legend?: boolean;
329
+ valueFormatter?: Format;
330
+ className?: string;
331
+ }) {
332
+ const total = data.reduce((sum, row) => sum + Number(row[valueKey]), 0);
333
+ return (
334
+ <ChartContainer
335
+ config={config}
336
+ legend={legend}
337
+ className={cn("h-56", className)}
338
+ overlay={
339
+ <div className="hogoshi-chart-label pointer-events-none absolute inset-0 grid place-content-center text-center">
340
+ <span className="text-fg text-2xl font-semibold tabular-nums">{valueFormatter(total)}</span>
341
+ {label ? <span className="text-fg-muted text-xs">{label}</span> : null}
342
+ </div>
343
+ }
344
+ >
345
+ <PieChart>
346
+ <ChartTooltip config={config} format={valueFormatter} cursor={false} />
347
+ <Pie data={data} dataKey={valueKey} nameKey={nameKey} innerRadius="70%" outerRadius="100%" paddingAngle={2} cornerRadius={6} stroke="none">
348
+ {data.map((row) => (
349
+ <Cell key={String(row[nameKey])} fill={series(String(row[nameKey]))} />
350
+ ))}
351
+ </Pie>
352
+ </PieChart>
353
+ </ChartContainer>
354
+ );
355
+ }
356
+
357
+ export function Sparkline({
358
+ data,
359
+ dataKey,
360
+ color = "var(--chart-1)",
361
+ className,
362
+ }: {
363
+ data: Row[];
364
+ dataKey: string;
365
+ color?: string;
366
+ className?: string;
367
+ }) {
368
+ const id = useId().replaceAll(":", "");
369
+ return (
370
+ <div
371
+ className={cn("h-10 w-full select-none outline-none [&_svg]:outline-none", className)}
372
+ onMouseDown={(event) => event.preventDefault()}
373
+ >
374
+ <ResponsiveContainer width="100%" height="100%">
375
+ <AreaRoot data={data} margin={{ top: 2, right: 0, bottom: 2, left: 0 }}>
376
+ <defs>
377
+ <linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
378
+ <stop offset="0%" stopColor={color} stopOpacity={0.3} />
379
+ <stop offset="100%" stopColor={color} stopOpacity={0} />
380
+ </linearGradient>
381
+ </defs>
382
+ <Area dataKey={dataKey} type="monotone" stroke={color} strokeWidth={1.5} fill={`url(#${id})`} isAnimationActive={false} />
383
+ </AreaRoot>
384
+ </ResponsiveContainer>
385
+ </div>
386
+ );
387
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
4
+ import { Tick02Icon } from "@hugeicons-pro/core-solid-rounded";
5
+ import type { ComponentProps } from "react";
6
+ import { cn } from "../lib/cn";
7
+ import { Icon } from "./icon";
8
+
9
+ export function Checkbox({ className, ...props }: ComponentProps<typeof BaseCheckbox.Root>) {
10
+ return (
11
+ <BaseCheckbox.Root
12
+ className={cn(
13
+ "border-border bg-bg-elevated text-accent-fg hover:border-border-strong flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-[min(4px,var(--radius-sm))] border transition-colors duration-[var(--motion-duration)] outline-none",
14
+ "focus-visible:ring-ring focus-visible:ring-2",
15
+ "data-[checked]:border-accent data-[checked]:bg-accent data-[checked]:hover:bg-accent-hover",
16
+ "data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:hover:border-border",
17
+ className,
18
+ )}
19
+ {...props}
20
+ >
21
+ <BaseCheckbox.Indicator className="flex scale-100 items-center justify-center transition-transform duration-[var(--motion-duration)] data-[ending-style]:scale-0 data-[starting-style]:scale-0">
22
+ <Icon icon={Tick02Icon} size={12} />
23
+ </BaseCheckbox.Indicator>
24
+ </BaseCheckbox.Root>
25
+ );
26
+ }
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import hljs from "highlight.js/lib/core";
5
+ import bash from "highlight.js/lib/languages/bash";
6
+ import json from "highlight.js/lib/languages/json";
7
+ import typescript from "highlight.js/lib/languages/typescript";
8
+ import xml from "highlight.js/lib/languages/xml";
9
+ import { Copy01Icon, Tick02Icon } from "@hugeicons-pro/core-stroke-rounded";
10
+ import { highlightJsx } from "../lib/jsx-highlight";
11
+ import { cn } from "../lib/cn";
12
+ import { Icon } from "./icon";
13
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
14
+
15
+ hljs.registerLanguage("typescript", typescript);
16
+ hljs.registerLanguage("ts", typescript);
17
+ hljs.registerLanguage("tsx", xml);
18
+ hljs.registerLanguage("jsx", xml);
19
+ hljs.registerLanguage("javascript", typescript);
20
+ hljs.registerLanguage("json", json);
21
+ hljs.registerLanguage("bash", bash);
22
+ hljs.registerLanguage("html", xml);
23
+
24
+ const languages = ["typescript", "ts", "tsx", "jsx", "javascript", "json", "bash", "html"] as const;
25
+ const known = new Set<string>(languages);
26
+ const jsxLanguages = new Set(["tsx", "jsx"]);
27
+
28
+ const labels: Record<string, string> = {
29
+ typescript: "TS",
30
+ ts: "TS",
31
+ tsx: "TSX",
32
+ jsx: "JSX",
33
+ javascript: "JS",
34
+ json: "JSON",
35
+ bash: "Shell",
36
+ html: "HTML",
37
+ text: "Text",
38
+ };
39
+
40
+ function guess(code: string) {
41
+ const trimmed = code.trim();
42
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
43
+ if (/^(pnpm|npm|yarn|bun|git|cd|ls|echo)\b/.test(trimmed) || trimmed.startsWith("$")) return "bash";
44
+ if (trimmed.includes("</") || trimmed.includes("/>")) return "tsx";
45
+ return null;
46
+ }
47
+
48
+ function highlight(code: string, language?: string) {
49
+ const hinted = language && language !== "auto" && known.has(language) ? language : guess(code);
50
+ if (hinted && jsxLanguages.has(hinted)) return { id: hinted, html: highlightJsx(code) };
51
+ if (hinted) return { id: hinted, html: hljs.highlight(code, { language: hinted, ignoreIllegals: true }).value };
52
+ const result = hljs.highlightAuto(code, [...languages]);
53
+ const id = result.language && known.has(result.language) ? result.language : "text";
54
+ return { id, html: id === "text" ? code.replaceAll("&", "&amp;").replaceAll("<", "&lt;") : result.value };
55
+ }
56
+
57
+ export function CodeBlock({
58
+ code,
59
+ language = "auto",
60
+ variant = "full",
61
+ showLanguage = true,
62
+ className,
63
+ }: {
64
+ code: string;
65
+ language?: string;
66
+ variant?: "full" | "snippet";
67
+ showLanguage?: boolean;
68
+ className?: string;
69
+ }) {
70
+ const [copied, setCopied] = useState(false);
71
+ if (variant === "snippet") {
72
+ return (
73
+ <div className={cn("bg-bg-subtle flex max-w-full overflow-hidden rounded-[var(--radius-md)]", className)}>
74
+ <span className="bg-fg-subtle/35 w-1 shrink-0" />
75
+ <pre className="max-h-28 min-w-0 flex-1 overflow-auto px-3 py-2 font-mono text-xs whitespace-pre">{code}</pre>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ const painted = highlight(code, language);
81
+ const copy = () => {
82
+ void navigator.clipboard.writeText(code).then(() => {
83
+ setCopied(true);
84
+ window.setTimeout(() => setCopied(false), 1200);
85
+ });
86
+ };
87
+
88
+ return (
89
+ <div className={cn("border-border bg-bg-subtle relative w-full min-w-0 max-w-full overflow-hidden rounded-[var(--radius-md)] border", className)}>
90
+ <div className="absolute top-2 right-2 flex items-center gap-1.5">
91
+ {showLanguage ? <span className="text-fg-subtle text-[10px] font-medium tracking-wide uppercase">{labels[painted.id] ?? painted.id}</span> : null}
92
+ <Tooltip>
93
+ <TooltipTrigger
94
+ render={
95
+ <button
96
+ type="button"
97
+ aria-label={copied ? "Copied" : "Copy"}
98
+ className="text-fg-muted hover:bg-bg-muted hover:text-fg inline-flex size-7 items-center justify-center rounded-[var(--radius-sm)]"
99
+ onClick={copy}
100
+ />
101
+ }
102
+ >
103
+ <Icon
104
+ icon={copied ? Tick02Icon : Copy01Icon}
105
+ variant="stroke"
106
+ size={14}
107
+ className={copied ? "text-success origin-center animate-[check-pop_var(--motion-duration)_var(--motion-ease)]" : undefined}
108
+ />
109
+ </TooltipTrigger>
110
+ <TooltipContent>{copied ? "Copied" : "Copy"}</TooltipContent>
111
+ </Tooltip>
112
+ </div>
113
+ <pre className="hogoshi-code w-full min-w-0 max-w-full p-4 pr-24 font-mono text-xs wrap-anywhere whitespace-pre-wrap">
114
+ <code className="wrap-anywhere whitespace-pre-wrap" dangerouslySetInnerHTML={{ __html: painted.html }} />
115
+ </pre>
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible";
4
+ import type { ComponentProps } from "react";
5
+ import { cn } from "../lib/cn";
6
+
7
+ export const Collapsible = BaseCollapsible.Root;
8
+
9
+ export function CollapsibleTrigger({ className, ...props }: ComponentProps<typeof BaseCollapsible.Trigger>) {
10
+ return (
11
+ <BaseCollapsible.Trigger
12
+ className={cn(
13
+ "flex w-full cursor-pointer items-center gap-1 rounded-[var(--radius-sm)] text-left outline-none focus-visible:ring-2 focus-visible:ring-ring",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export function CollapsiblePanel({ className, ...props }: ComponentProps<typeof BaseCollapsible.Panel>) {
22
+ return (
23
+ <BaseCollapsible.Panel
24
+ className={cn(
25
+ "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-[var(--motion-duration)] ease-[var(--motion-ease)] data-[ending-style]:h-0 data-[starting-style]:h-0 motion-reduce:transition-none",
26
+ className,
27
+ )}
28
+ {...props}
29
+ />
30
+ );
31
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import { Command as CommandPrimitive } from "cmdk";
4
+ import { useState, type ComponentProps } from "react";
5
+ import { cn } from "../lib/cn";
6
+ import { Glide } from "./glide";
7
+ import { SearchChrome, searchControlClass } from "./input";
8
+ import { Kbd } from "./kbd";
9
+
10
+ export function Command({ className, ...props }: ComponentProps<typeof CommandPrimitive>) {
11
+ return (
12
+ <CommandPrimitive
13
+ className={cn("text-fg flex h-full min-h-0 w-full flex-col overflow-hidden bg-transparent", className)}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export function CommandDialog({
20
+ className,
21
+ children,
22
+ ...props
23
+ }: ComponentProps<typeof CommandPrimitive.Dialog>) {
24
+ return (
25
+ <CommandPrimitive.Dialog
26
+ overlayClassName="hogoshi-command-overlay fixed inset-0 z-[var(--z-overlay)] bg-transparent backdrop-blur-2xl"
27
+ contentClassName="hogoshi-command border-border bg-bg-elevated/70 fixed top-[12vh] left-1/2 z-[var(--z-modal)] flex max-h-[min(32rem,calc(100dvh-8rem-var(--safe-bottom)))] w-[calc(100vw-2rem)] max-w-xl flex-col overflow-hidden rounded-[var(--radius-lg)] border shadow-[var(--shadow-lg)] backdrop-blur-xl outline-none"
28
+ className={cn("flex flex-col", className)}
29
+ {...props}
30
+ >
31
+ {children}
32
+ </CommandPrimitive.Dialog>
33
+ );
34
+ }
35
+
36
+ export function CommandInput({ className, value, onValueChange, ...props }: ComponentProps<typeof CommandPrimitive.Input>) {
37
+ const [query, setQuery] = useState(value ?? "");
38
+ const shown = value !== undefined ? value : query;
39
+ const change = (next: string) => {
40
+ setQuery(next);
41
+ onValueChange?.(next);
42
+ };
43
+ return (
44
+ <div className="border-border border-b px-2 py-2">
45
+ <SearchChrome value={shown} onClear={() => change("")}>
46
+ <CommandPrimitive.Input
47
+ {...props}
48
+ value={shown}
49
+ onValueChange={change}
50
+ className={cn(
51
+ searchControlClass,
52
+ "border-0 bg-transparent shadow-none hover:border-transparent focus-visible:border-transparent focus-visible:ring-0",
53
+ className,
54
+ )}
55
+ />
56
+ </SearchChrome>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ export function CommandList({ className, children, ...props }: ComponentProps<typeof CommandPrimitive.List>) {
62
+ return (
63
+ <CommandPrimitive.List
64
+ className={cn(
65
+ "relative max-h-[min(24rem,calc(100dvh-14rem))] touch-pan-y overflow-y-auto overscroll-contain p-1",
66
+ className,
67
+ )}
68
+ onTouchMove={(event) => event.stopPropagation()}
69
+ {...props}
70
+ >
71
+ <Glide selector="[cmdk-item][data-selected=true]" />
72
+ {children}
73
+ </CommandPrimitive.List>
74
+ );
75
+ }
76
+
77
+ export function CommandEmpty({ className, ...props }: ComponentProps<typeof CommandPrimitive.Empty>) {
78
+ return <CommandPrimitive.Empty className={cn("text-fg-muted py-6 text-center text-sm", className)} {...props} />;
79
+ }
80
+
81
+ export function CommandGroup({ className, ...props }: ComponentProps<typeof CommandPrimitive.Group>) {
82
+ return (
83
+ <CommandPrimitive.Group
84
+ className={cn(
85
+ "text-fg p-1 [&_[cmdk-group-heading]]:text-fg-subtle [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:uppercase",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+
93
+ export function CommandItem({ className, ...props }: ComponentProps<typeof CommandPrimitive.Item>) {
94
+ return (
95
+ <CommandPrimitive.Item
96
+ className={cn(
97
+ "relative flex cursor-pointer items-center gap-2 rounded-[var(--radius-sm)] px-2 py-2 text-sm outline-none",
98
+ className,
99
+ )}
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ export function CommandShortcut({ className, children, ...props }: ComponentProps<"span">) {
106
+ return (
107
+ <span className={cn("text-fg-muted ml-auto text-xs", className)} {...props}>
108
+ <Kbd>{children}</Kbd>
109
+ </span>
110
+ );
111
+ }