harebear-ui 0.0.0-stage → 0.1.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 (170) hide show
  1. package/AGENTS.md +63 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -2
  4. package/bin/harebear-ui.mjs +125 -0
  5. package/dist/components/admin/activity-feed.d.ts +15 -0
  6. package/dist/components/admin/activity-feed.js +50 -0
  7. package/dist/components/admin/app-shell.d.ts +47 -0
  8. package/dist/components/admin/app-shell.js +108 -0
  9. package/dist/components/admin/callout.d.ts +7 -0
  10. package/dist/components/admin/callout.js +18 -0
  11. package/dist/components/admin/key-value-grid.d.ts +11 -0
  12. package/dist/components/admin/key-value-grid.js +23 -0
  13. package/dist/components/admin/link-tile.d.ts +17 -0
  14. package/dist/components/admin/link-tile.js +36 -0
  15. package/dist/components/admin/page-header.d.ts +14 -0
  16. package/dist/components/admin/page-header.js +41 -0
  17. package/dist/components/admin/search-input.d.ts +12 -0
  18. package/dist/components/admin/search-input.js +32 -0
  19. package/dist/components/admin/stat-card.d.ts +20 -0
  20. package/dist/components/admin/stat-card.js +53 -0
  21. package/dist/components/admin/status.d.ts +12 -0
  22. package/dist/components/admin/status.js +32 -0
  23. package/dist/components/admin/timeline.d.ts +13 -0
  24. package/dist/components/admin/timeline.js +36 -0
  25. package/dist/components/admin/usage-meter.d.ts +17 -0
  26. package/dist/components/admin/usage-meter.js +43 -0
  27. package/dist/components/admin/user-chip.d.ts +14 -0
  28. package/dist/components/admin/user-chip.js +38 -0
  29. package/dist/components/ai/chat.d.ts +44 -0
  30. package/dist/components/ai/chat.js +147 -0
  31. package/dist/components/ai/code-block.d.ts +21 -0
  32. package/dist/components/ai/code-block.js +53 -0
  33. package/dist/components/ai/conversation-list.d.ts +18 -0
  34. package/dist/components/ai/conversation-list.js +30 -0
  35. package/dist/components/ai/model-picker.d.ts +21 -0
  36. package/dist/components/ai/model-picker.js +55 -0
  37. package/dist/components/ai/prompt-input.d.ts +27 -0
  38. package/dist/components/ai/prompt-input.js +99 -0
  39. package/dist/components/data/bar-list.d.ts +14 -0
  40. package/dist/components/data/bar-list.js +27 -0
  41. package/dist/components/data/data-table.d.ts +70 -0
  42. package/dist/components/data/data-table.js +208 -0
  43. package/dist/components/data/sparkline.d.ts +16 -0
  44. package/dist/components/data/sparkline.js +72 -0
  45. package/dist/components/data/status-bars.d.ts +17 -0
  46. package/dist/components/data/status-bars.js +37 -0
  47. package/dist/components/data/trend-chart.d.ts +31 -0
  48. package/dist/components/data/trend-chart.js +136 -0
  49. package/dist/components/theme/theme-provider.d.ts +42 -0
  50. package/dist/components/theme/theme-provider.js +110 -0
  51. package/dist/components/theme/theme-toggle.d.ts +5 -0
  52. package/dist/components/theme/theme-toggle.js +20 -0
  53. package/dist/components/ui/accordion.d.ts +7 -0
  54. package/dist/components/ui/accordion.js +53 -0
  55. package/dist/components/ui/alert-dialog.d.ts +18 -0
  56. package/dist/components/ui/alert-dialog.js +99 -0
  57. package/dist/components/ui/alert.d.ts +10 -0
  58. package/dist/components/ui/alert.js +42 -0
  59. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  60. package/dist/components/ui/aspect-ratio.js +11 -0
  61. package/dist/components/ui/avatar.d.ts +11 -0
  62. package/dist/components/ui/avatar.js +49 -0
  63. package/dist/components/ui/badge.d.ts +9 -0
  64. package/dist/components/ui/badge.js +30 -0
  65. package/dist/components/ui/breadcrumb.d.ts +11 -0
  66. package/dist/components/ui/breadcrumb.js +70 -0
  67. package/dist/components/ui/button-group.d.ts +11 -0
  68. package/dist/components/ui/button-group.js +39 -0
  69. package/dist/components/ui/button.d.ts +11 -0
  70. package/dist/components/ui/button.js +55 -0
  71. package/dist/components/ui/calendar.d.ts +10 -0
  72. package/dist/components/ui/calendar.js +108 -0
  73. package/dist/components/ui/card.d.ts +11 -0
  74. package/dist/components/ui/card.js +55 -0
  75. package/dist/components/ui/carousel.d.ts +28 -0
  76. package/dist/components/ui/carousel.js +131 -0
  77. package/dist/components/ui/chart.d.ts +44 -0
  78. package/dist/components/ui/chart.js +148 -0
  79. package/dist/components/ui/checkbox.d.ts +4 -0
  80. package/dist/components/ui/checkbox.js +19 -0
  81. package/dist/components/ui/collapsible.d.ts +5 -0
  82. package/dist/components/ui/collapsible.js +23 -0
  83. package/dist/components/ui/command.d.ts +18 -0
  84. package/dist/components/ui/command.js +86 -0
  85. package/dist/components/ui/context-menu.d.ts +31 -0
  86. package/dist/components/ui/context-menu.js +123 -0
  87. package/dist/components/ui/dialog.d.ts +17 -0
  88. package/dist/components/ui/dialog.js +94 -0
  89. package/dist/components/ui/drawer.d.ts +13 -0
  90. package/dist/components/ui/drawer.js +76 -0
  91. package/dist/components/ui/dropdown-menu.d.ts +29 -0
  92. package/dist/components/ui/dropdown-menu.js +126 -0
  93. package/dist/components/ui/empty.d.ts +11 -0
  94. package/dist/components/ui/empty.js +59 -0
  95. package/dist/components/ui/field.d.ts +24 -0
  96. package/dist/components/ui/field.js +109 -0
  97. package/dist/components/ui/hover-card.d.ts +6 -0
  98. package/dist/components/ui/hover-card.js +30 -0
  99. package/dist/components/ui/input-group.d.ts +16 -0
  100. package/dist/components/ui/input-group.js +77 -0
  101. package/dist/components/ui/input-otp.d.ts +11 -0
  102. package/dist/components/ui/input-otp.js +46 -0
  103. package/dist/components/ui/input.d.ts +3 -0
  104. package/dist/components/ui/input.js +13 -0
  105. package/dist/components/ui/item.d.ts +23 -0
  106. package/dist/components/ui/item.js +117 -0
  107. package/dist/components/ui/kbd.d.ts +3 -0
  108. package/dist/components/ui/kbd.js +19 -0
  109. package/dist/components/ui/label.d.ts +4 -0
  110. package/dist/components/ui/label.js +13 -0
  111. package/dist/components/ui/menubar.d.ts +30 -0
  112. package/dist/components/ui/menubar.js +133 -0
  113. package/dist/components/ui/navigation-menu.d.ts +14 -0
  114. package/dist/components/ui/navigation-menu.js +79 -0
  115. package/dist/components/ui/pagination.d.ts +17 -0
  116. package/dist/components/ui/pagination.js +79 -0
  117. package/dist/components/ui/popover.d.ts +10 -0
  118. package/dist/components/ui/popover.js +54 -0
  119. package/dist/components/ui/progress.d.ts +4 -0
  120. package/dist/components/ui/progress.js +18 -0
  121. package/dist/components/ui/radio-group.d.ts +5 -0
  122. package/dist/components/ui/radio-group.js +25 -0
  123. package/dist/components/ui/resizable.d.ts +7 -0
  124. package/dist/components/ui/resizable.js +27 -0
  125. package/dist/components/ui/scroll-area.d.ts +5 -0
  126. package/dist/components/ui/scroll-area.js +35 -0
  127. package/dist/components/ui/select.d.ts +15 -0
  128. package/dist/components/ui/select.js +98 -0
  129. package/dist/components/ui/separator.d.ts +4 -0
  130. package/dist/components/ui/separator.js +15 -0
  131. package/dist/components/ui/sheet.d.ts +14 -0
  132. package/dist/components/ui/sheet.js +88 -0
  133. package/dist/components/ui/sidebar.d.ts +69 -0
  134. package/dist/components/ui/sidebar.js +377 -0
  135. package/dist/components/ui/skeleton.d.ts +2 -0
  136. package/dist/components/ui/skeleton.js +12 -0
  137. package/dist/components/ui/slider.d.ts +4 -0
  138. package/dist/components/ui/slider.js +35 -0
  139. package/dist/components/ui/sonner.d.ts +3 -0
  140. package/dist/components/ui/sonner.js +29 -0
  141. package/dist/components/ui/spinner.d.ts +2 -0
  142. package/dist/components/ui/spinner.js +15 -0
  143. package/dist/components/ui/switch.d.ts +6 -0
  144. package/dist/components/ui/switch.js +18 -0
  145. package/dist/components/ui/table.d.ts +10 -0
  146. package/dist/components/ui/table.js +65 -0
  147. package/dist/components/ui/tabs.d.ts +11 -0
  148. package/dist/components/ui/tabs.js +45 -0
  149. package/dist/components/ui/textarea.d.ts +3 -0
  150. package/dist/components/ui/textarea.js +12 -0
  151. package/dist/components/ui/toggle-group.d.ts +10 -0
  152. package/dist/components/ui/toggle-group.js +50 -0
  153. package/dist/components/ui/toggle.d.ts +9 -0
  154. package/dist/components/ui/toggle.js +35 -0
  155. package/dist/components/ui/tooltip.d.ts +7 -0
  156. package/dist/components/ui/tooltip.js +34 -0
  157. package/dist/hooks/use-copy-to-clipboard.d.ts +5 -0
  158. package/dist/hooks/use-copy-to-clipboard.js +24 -0
  159. package/dist/hooks/use-mobile.d.ts +1 -0
  160. package/dist/hooks/use-mobile.js +17 -0
  161. package/dist/index.d.ts +80 -0
  162. package/dist/index.js +80 -0
  163. package/dist/lib/utils.d.ts +3 -0
  164. package/dist/lib/utils.js +8 -0
  165. package/dist/shadcn-base.css +638 -0
  166. package/dist/styles.css +188 -0
  167. package/docs/COMPONENTS.md +352 -0
  168. package/llms.txt +34 -0
  169. package/package.json +116 -4
  170. package/skills/harebear-ui/SKILL.md +33 -0
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/admin/page-header.tsx
6
+ /** Page title row with description and actions. Wraps on narrow screens. */
7
+ function PageHeader({ title, description, count, actions, eyebrow, className, ...props }) {
8
+ return /* @__PURE__ */ jsxs("section", {
9
+ "data-slot": "page-header",
10
+ className: cn("flex flex-wrap items-end justify-between gap-4", className),
11
+ ...props,
12
+ children: [/* @__PURE__ */ jsxs("div", {
13
+ className: "flex min-w-0 flex-col gap-1.5",
14
+ children: [
15
+ eyebrow && /* @__PURE__ */ jsx("p", {
16
+ className: "font-medium text-muted-foreground",
17
+ children: eyebrow
18
+ }),
19
+ /* @__PURE__ */ jsxs("div", {
20
+ className: "flex items-center gap-3",
21
+ children: [/* @__PURE__ */ jsx("h1", {
22
+ className: "text-[28px] leading-tight font-semibold tracking-[-0.02em]",
23
+ children: title
24
+ }), count !== void 0 && /* @__PURE__ */ jsx(Badge, {
25
+ variant: "secondary",
26
+ className: "tabular-nums",
27
+ children: count
28
+ })]
29
+ }),
30
+ description && /* @__PURE__ */ jsx("p", {
31
+ className: "text-muted-foreground",
32
+ children: description
33
+ })
34
+ ]
35
+ }), actions && /* @__PURE__ */ jsx("div", {
36
+ className: "flex flex-wrap items-center gap-2.5",
37
+ children: actions
38
+ })]
39
+ });
40
+ }
41
+ export { PageHeader };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ export interface SearchInputProps extends Omit<React.ComponentProps<"input">, "size"> {
3
+ /** Keyboard hint shown at the right, e.g. "⌘K" or "/". Display only. */
4
+ shortcut?: React.ReactNode;
5
+ /** "lg" is the tall, pill-shaped hero search. */
6
+ size?: "default" | "lg";
7
+ /** Classes for the wrapping label. */
8
+ wrapperClassName?: string;
9
+ }
10
+ /** Search field with leading icon and optional shortcut hint. */
11
+ declare function SearchInput({ shortcut, size, className, wrapperClassName, "aria-label": ariaLabel, ...props }: SearchInputProps): React.JSX.Element;
12
+ export { SearchInput };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { Input } from "../ui/input.js";
3
+ import { Kbd } from "../ui/kbd.js";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { SearchIcon } from "lucide-react";
7
+ // src/components/admin/search-input.tsx
8
+ /** Search field with leading icon and optional shortcut hint. */
9
+ function SearchInput({ shortcut, size = "default", className, wrapperClassName, "aria-label": ariaLabel = "Search", ...props }) {
10
+ const lg = size === "lg";
11
+ return /* @__PURE__ */ jsxs("label", {
12
+ "data-slot": "search-input",
13
+ className: cn("relative block", wrapperClassName),
14
+ children: [
15
+ /* @__PURE__ */ jsx(SearchIcon, {
16
+ "aria-hidden": "true",
17
+ className: cn("pointer-events-none absolute top-1/2 size-[18px] -translate-y-1/2 text-muted-foreground", lg ? "left-5" : "left-3.5")
18
+ }),
19
+ /* @__PURE__ */ jsx(Input, {
20
+ type: "search",
21
+ "aria-label": ariaLabel,
22
+ className: cn(lg ? "h-13.5 rounded-full border-border bg-card pl-13.5 text-[15px] shadow-card md:text-[15px]" : "pl-10.5", shortcut ? lg ? "pr-15" : "pr-13" : void 0, className),
23
+ ...props
24
+ }),
25
+ shortcut && /* @__PURE__ */ jsx(Kbd, {
26
+ className: cn("absolute top-1/2 -translate-y-1/2", lg ? "right-4.5" : "right-2.5"),
27
+ children: shortcut
28
+ })
29
+ ]
30
+ });
31
+ }
32
+ export { SearchInput };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ export interface StatCardProps extends Omit<React.ComponentProps<"div">, "title"> {
3
+ label: React.ReactNode;
4
+ value: React.ReactNode;
5
+ /** Change text, e.g. "12.4%" or "18 ms". */
6
+ delta?: React.ReactNode;
7
+ /** Arrow direction for the delta. */
8
+ trend?: "up" | "down";
9
+ /** Whether the change is good (green) or bad (red). Defaults to trend === "up". */
10
+ positive?: boolean;
11
+ /** Text after the delta, e.g. "vs last month". */
12
+ note?: React.ReactNode;
13
+ /** Icon element shown top-right in a tinted circle. */
14
+ icon?: React.ReactNode;
15
+ /** Optional sparkline series. */
16
+ data?: number[];
17
+ }
18
+ /** KPI tile: label, big number, delta badge, optional sparkline. */
19
+ declare function StatCard({ label, value, delta, trend, positive, note, icon, data, className, ...props }: StatCardProps): React.JSX.Element;
20
+ export { StatCard };
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { Card } from "../ui/card.js";
4
+ import { Sparkline } from "../data/sparkline.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { ArrowDownRightIcon, ArrowUpRightIcon } from "lucide-react";
8
+ // src/components/admin/stat-card.tsx
9
+ /** KPI tile: label, big number, delta badge, optional sparkline. */
10
+ function StatCard({ label, value, delta, trend = "up", positive, note, icon, data, className, ...props }) {
11
+ const good = positive ?? trend === "up";
12
+ const Arrow = trend === "up" ? ArrowUpRightIcon : ArrowDownRightIcon;
13
+ return /* @__PURE__ */ jsxs(Card, {
14
+ "data-slot": "stat-card",
15
+ className: cn("gap-3.5 px-5.5 pt-5.5 pb-4.5", className),
16
+ ...props,
17
+ children: [
18
+ /* @__PURE__ */ jsxs("div", {
19
+ className: "flex items-center justify-between gap-3",
20
+ children: [/* @__PURE__ */ jsx("span", {
21
+ className: "font-medium text-muted-foreground",
22
+ children: label
23
+ }), icon && /* @__PURE__ */ jsx("span", {
24
+ className: "grid size-8.5 shrink-0 place-items-center rounded-full bg-primary-soft text-primary [&_svg]:size-4",
25
+ children: icon
26
+ })]
27
+ }),
28
+ /* @__PURE__ */ jsx("div", {
29
+ className: "text-[30px] leading-none font-semibold tracking-[-0.02em] tabular-nums",
30
+ children: value
31
+ }),
32
+ (delta || note) && /* @__PURE__ */ jsxs("div", {
33
+ className: "flex items-center gap-2 text-[13px]",
34
+ children: [delta && /* @__PURE__ */ jsxs(Badge, {
35
+ variant: "secondary",
36
+ className: "gap-1 px-2",
37
+ children: [/* @__PURE__ */ jsx(Arrow, {
38
+ className: cn(good ? "text-success" : "text-destructive"),
39
+ strokeWidth: 2.2
40
+ }), /* @__PURE__ */ jsx("span", {
41
+ className: "tabular-nums",
42
+ children: delta
43
+ })]
44
+ }), note && /* @__PURE__ */ jsx("span", {
45
+ className: "text-muted-foreground",
46
+ children: note
47
+ })]
48
+ }),
49
+ data && data.length > 1 && /* @__PURE__ */ jsx(Sparkline, { data })
50
+ ]
51
+ });
52
+ }
53
+ export { StatCard };
@@ -0,0 +1,12 @@
1
+ import { Badge } from "../ui/badge.js";
2
+ import * as React from "react";
3
+ export type StatusTone = "success" | "warning" | "danger" | "info" | "neutral";
4
+ /** Small colored dot. Use for status, legends, and project markers. */
5
+ declare function StatusDot({ tone, className, ...props }: React.ComponentProps<"span"> & {
6
+ tone?: StatusTone;
7
+ }): React.JSX.Element;
8
+ /** Pill with a status dot and label, e.g. Paid / Pending / Refunded. */
9
+ declare function StatusBadge({ tone, variant, children, className, ...props }: React.ComponentProps<typeof Badge> & {
10
+ tone?: StatusTone;
11
+ }): React.JSX.Element;
12
+ export { StatusBadge, StatusDot };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/admin/status.tsx
6
+ const toneColor = {
7
+ success: "bg-success",
8
+ warning: "bg-warning",
9
+ danger: "bg-destructive",
10
+ info: "bg-primary",
11
+ neutral: "bg-muted-foreground"
12
+ };
13
+ /** Small colored dot. Use for status, legends, and project markers. */
14
+ function StatusDot({ tone = "neutral", className, ...props }) {
15
+ return /* @__PURE__ */ jsx("span", {
16
+ "data-slot": "status-dot",
17
+ "aria-hidden": "true",
18
+ className: cn("inline-block size-2 shrink-0 rounded-full", toneColor[tone], className),
19
+ ...props
20
+ });
21
+ }
22
+ /** Pill with a status dot and label, e.g. Paid / Pending / Refunded. */
23
+ function StatusBadge({ tone = "neutral", variant = "secondary", children, className, ...props }) {
24
+ return /* @__PURE__ */ jsxs(Badge, {
25
+ "data-slot": "status-badge",
26
+ variant,
27
+ className,
28
+ ...props,
29
+ children: [/* @__PURE__ */ jsx(StatusDot, { tone }), children]
30
+ });
31
+ }
32
+ export { StatusBadge, StatusDot };
@@ -0,0 +1,13 @@
1
+ import { StatusTone } from "./status.js";
2
+ import * as React from "react";
3
+ export interface TimelineItem {
4
+ id?: string;
5
+ title: React.ReactNode;
6
+ time: React.ReactNode;
7
+ tone?: StatusTone;
8
+ }
9
+ /** Vertical event list with connecting rail. */
10
+ declare function Timeline({ items, className, ...props }: Omit<React.ComponentProps<"ol">, "children"> & {
11
+ items: TimelineItem[];
12
+ }): React.JSX.Element;
13
+ export { Timeline };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ // src/components/admin/timeline.tsx
5
+ const dot = {
6
+ success: "bg-success",
7
+ warning: "bg-warning",
8
+ danger: "bg-destructive",
9
+ info: "bg-primary",
10
+ neutral: "bg-muted-foreground"
11
+ };
12
+ /** Vertical event list with connecting rail. */
13
+ function Timeline({ items, className, ...props }) {
14
+ return /* @__PURE__ */ jsx("ol", {
15
+ "data-slot": "timeline",
16
+ className: cn("flex flex-col", className),
17
+ ...props,
18
+ children: items.map((t, i) => /* @__PURE__ */ jsxs("li", {
19
+ className: "flex gap-3.5",
20
+ children: [/* @__PURE__ */ jsxs("div", {
21
+ className: "flex w-2.5 flex-col items-center pt-1.5",
22
+ children: [/* @__PURE__ */ jsx("span", { className: cn("size-2.5 shrink-0 rounded-full ring-3 ring-card", dot[t.tone ?? "info"]) }), i < items.length - 1 && /* @__PURE__ */ jsx("span", { className: "mt-1.5 mb-0.5 w-0.5 flex-1 rounded-full bg-chart-grid" })]
23
+ }), /* @__PURE__ */ jsxs("div", {
24
+ className: "flex flex-col gap-px pb-4 text-[13px]",
25
+ children: [/* @__PURE__ */ jsx("span", {
26
+ className: "font-medium",
27
+ children: t.title
28
+ }), /* @__PURE__ */ jsx("span", {
29
+ className: "text-xs text-muted-foreground",
30
+ children: t.time
31
+ })]
32
+ })]
33
+ }, t.id ?? i))
34
+ });
35
+ }
36
+ export { Timeline };
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export interface UsageMeterProps extends Omit<React.ComponentProps<"div">, "children"> {
3
+ label: React.ReactNode;
4
+ /** 0-100. */
5
+ percent: number;
6
+ /** Bold value text at the right, e.g. "68%" or "34 GB". */
7
+ value?: React.ReactNode;
8
+ /** Muted text after the value, e.g. "of 50 GB". */
9
+ detail?: React.ReactNode;
10
+ /** Bar color (CSS color). Default primary. Use "var(--warning)" etc. for thresholds. */
11
+ color?: string;
12
+ /** Track color. Default muted. */
13
+ trackClassName?: string;
14
+ }
15
+ /** Label/value row over a rounded bar. For storage, quotas, traffic share, CPU. */
16
+ declare function UsageMeter({ label, percent, value, detail, color, trackClassName, className, ...props }: UsageMeterProps): React.JSX.Element;
17
+ export { UsageMeter };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ // src/components/admin/usage-meter.tsx
5
+ /** Label/value row over a rounded bar. For storage, quotas, traffic share, CPU. */
6
+ function UsageMeter({ label, percent, value, detail, color, trackClassName, className, ...props }) {
7
+ const pct = Math.max(0, Math.min(100, percent));
8
+ return /* @__PURE__ */ jsxs("div", {
9
+ "data-slot": "usage-meter",
10
+ className: cn("flex flex-col gap-2", className),
11
+ ...props,
12
+ children: [/* @__PURE__ */ jsxs("div", {
13
+ className: "flex items-baseline justify-between gap-3 text-[13px]",
14
+ children: [/* @__PURE__ */ jsx("span", {
15
+ className: "font-medium",
16
+ children: label
17
+ }), /* @__PURE__ */ jsxs("span", {
18
+ className: "tabular-nums",
19
+ children: [value !== void 0 && /* @__PURE__ */ jsx("span", {
20
+ className: "font-semibold",
21
+ children: value
22
+ }), detail !== void 0 && /* @__PURE__ */ jsxs("span", {
23
+ className: "text-muted-foreground",
24
+ children: [" ", detail]
25
+ })]
26
+ })]
27
+ }), /* @__PURE__ */ jsx("div", {
28
+ role: "meter",
29
+ "aria-valuemin": 0,
30
+ "aria-valuemax": 100,
31
+ "aria-valuenow": pct,
32
+ className: cn("h-2 overflow-hidden rounded-full bg-muted", trackClassName),
33
+ children: /* @__PURE__ */ jsx("span", {
34
+ className: "block h-full rounded-full bg-primary transition-[width]",
35
+ style: {
36
+ width: `${pct}%`,
37
+ background: color
38
+ }
39
+ })
40
+ })]
41
+ });
42
+ }
43
+ export { UsageMeter };
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ /** Up to two initials from a name: "Maya Chen" -> "MC". */
3
+ export declare function initials(name: string): string;
4
+ export interface UserChipProps extends React.ComponentProps<"div"> {
5
+ name: string;
6
+ subtitle?: React.ReactNode;
7
+ avatarUrl?: string;
8
+ /** Trailing control, e.g. a menu button. */
9
+ action?: React.ReactNode;
10
+ size?: "sm" | "default" | "lg";
11
+ }
12
+ /** Avatar + name + secondary line. */
13
+ declare function UserChip({ name, subtitle, avatarUrl, action, size, className, ...props }: UserChipProps): React.JSX.Element;
14
+ export { UserChip };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/admin/user-chip.tsx
6
+ /** Up to two initials from a name: "Maya Chen" -> "MC". */
7
+ function initials(name) {
8
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase()).join("");
9
+ }
10
+ /** Avatar + name + secondary line. */
11
+ function UserChip({ name, subtitle, avatarUrl, action, size = "default", className, ...props }) {
12
+ return /* @__PURE__ */ jsxs("div", {
13
+ "data-slot": "user-chip",
14
+ className: cn("flex min-w-0 items-center gap-3", className),
15
+ ...props,
16
+ children: [
17
+ /* @__PURE__ */ jsxs(Avatar, {
18
+ size,
19
+ children: [avatarUrl && /* @__PURE__ */ jsx(AvatarImage, {
20
+ src: avatarUrl,
21
+ alt: ""
22
+ }), /* @__PURE__ */ jsx(AvatarFallback, { children: initials(name) })]
23
+ }),
24
+ /* @__PURE__ */ jsxs("div", {
25
+ className: "min-w-0 flex-1 leading-snug",
26
+ children: [/* @__PURE__ */ jsx("div", {
27
+ className: cn("truncate font-medium", size === "lg" && "text-lg font-semibold"),
28
+ children: name
29
+ }), subtitle && /* @__PURE__ */ jsx("div", {
30
+ className: "truncate text-xs font-medium text-muted-foreground",
31
+ children: subtitle
32
+ })]
33
+ }),
34
+ action
35
+ ]
36
+ });
37
+ }
38
+ export { UserChip, initials };
@@ -0,0 +1,44 @@
1
+ import { Badge } from "../ui/badge.js";
2
+ import * as React from "react";
3
+ export interface ChatThreadProps extends React.ComponentProps<"div"> {
4
+ /** Keep scrolled to the bottom as content grows, unless the user scrolled up. Default true. */
5
+ autoScroll?: boolean;
6
+ /** Classes for the centered inner column (max width 760px). */
7
+ contentClassName?: string;
8
+ }
9
+ /** Scrollable message column. Put ChatMessage children inside; give it a bounded height (flex-1 in a column). */
10
+ declare function ChatThread({ autoScroll, className, contentClassName, children, ...props }: ChatThreadProps): React.JSX.Element;
11
+ export interface ChatMessageProps extends React.ComponentProps<"div"> {
12
+ role: "user" | "assistant";
13
+ /** Assistant avatar. Defaults to a sparkle in a tinted circle. */
14
+ avatar?: React.ReactNode;
15
+ /** Rendered under assistant content, usually <MessageActions />. */
16
+ actions?: React.ReactNode;
17
+ }
18
+ /** One chat turn. User: right-aligned bubble. Assistant: avatar + free-flowing content. */
19
+ declare function ChatMessage({ role, avatar, actions, className, children, ...props }: ChatMessageProps): React.JSX.Element;
20
+ /** Tinted circle with an icon. Default assistant avatar. */
21
+ declare function AssistantAvatar({ icon, className }: {
22
+ icon?: React.ReactNode;
23
+ className?: string;
24
+ }): React.JSX.Element;
25
+ export interface MessageActionsProps extends React.ComponentProps<"div"> {
26
+ /** Text copied by the copy button. Omit to hide it. */
27
+ copyText?: string;
28
+ onRegenerate?: () => void;
29
+ onFeedback?: (value: "up" | "down") => void;
30
+ /** Currently selected feedback, for a pressed state. */
31
+ feedback?: "up" | "down" | null;
32
+ }
33
+ /** Copy / regenerate / thumbs row under an assistant message. Add more buttons as children. */
34
+ declare function MessageActions({ copyText, onRegenerate, onFeedback, feedback, className, children, ...props }: MessageActionsProps): React.JSX.Element;
35
+ /** Three pulsing dots while the assistant is generating. */
36
+ declare function TypingIndicator({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
37
+ export interface ToolCallProps extends React.ComponentProps<typeof Badge> {
38
+ /** "running" shows a spinner; "error" tints red. */
39
+ status?: "running" | "done" | "error";
40
+ icon?: React.ReactNode;
41
+ }
42
+ /** Outlined chip summarizing a tool call, e.g. "Read 2 notes from homelab/". */
43
+ declare function ToolCall({ status, icon, className, children, ...props }: ToolCallProps): React.JSX.Element;
44
+ export { AssistantAvatar, ChatMessage, ChatThread, MessageActions, ToolCall, TypingIndicator };
@@ -0,0 +1,147 @@
1
+ "use client";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { Button } from "../ui/button.js";
4
+ import { Spinner } from "../ui/spinner.js";
5
+ import { useCopyToClipboard } from "../../hooks/use-copy-to-clipboard.js";
6
+ import { cn } from "../../lib/utils.js";
7
+ import * as React from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { CheckIcon, CopyIcon, RefreshCwIcon, SparklesIcon, ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";
10
+ // src/components/ai/chat.tsx
11
+ /** Scrollable message column. Put ChatMessage children inside; give it a bounded height (flex-1 in a column). */
12
+ function ChatThread({ autoScroll = true, className, contentClassName, children, ...props }) {
13
+ const ref = React.useRef(null);
14
+ const pinned = React.useRef(true);
15
+ React.useEffect(() => {
16
+ const el = ref.current;
17
+ if (!el || !autoScroll) return;
18
+ const inner = el.firstElementChild;
19
+ const stick = () => {
20
+ if (pinned.current) el.scrollTop = el.scrollHeight;
21
+ };
22
+ stick();
23
+ const ro = new ResizeObserver(stick);
24
+ if (inner) ro.observe(inner);
25
+ return () => ro.disconnect();
26
+ }, [autoScroll]);
27
+ return /* @__PURE__ */ jsx("div", {
28
+ ref,
29
+ "data-slot": "chat-thread",
30
+ role: "log",
31
+ "aria-live": "polite",
32
+ onScroll: (e) => {
33
+ const el = e.currentTarget;
34
+ pinned.current = el.scrollHeight - el.scrollTop - el.clientHeight < 48;
35
+ },
36
+ className: cn("min-h-0 flex-1 overflow-y-auto px-6 pt-6 pb-2", className),
37
+ ...props,
38
+ children: /* @__PURE__ */ jsx("div", {
39
+ className: cn("mx-auto flex max-w-190 flex-col gap-9", contentClassName),
40
+ children
41
+ })
42
+ });
43
+ }
44
+ /** One chat turn. User: right-aligned bubble. Assistant: avatar + free-flowing content. */
45
+ function ChatMessage({ role, avatar, actions, className, children, ...props }) {
46
+ if (role === "user") return /* @__PURE__ */ jsx("div", {
47
+ "data-slot": "chat-message",
48
+ "data-role": "user",
49
+ className: cn("flex justify-end", className),
50
+ ...props,
51
+ children: /* @__PURE__ */ jsx("div", {
52
+ className: "max-w-[80%] rounded-[calc(var(--radius-xl)*1.2)] rounded-br-sm bg-muted px-4.5 py-3 text-[15px] whitespace-pre-wrap",
53
+ children
54
+ })
55
+ });
56
+ return /* @__PURE__ */ jsxs("div", {
57
+ "data-slot": "chat-message",
58
+ "data-role": "assistant",
59
+ className: cn("flex items-start gap-4", className),
60
+ ...props,
61
+ children: [avatar ?? /* @__PURE__ */ jsx(AssistantAvatar, {}), /* @__PURE__ */ jsxs("div", {
62
+ className: "flex min-w-0 flex-1 flex-col gap-3.5",
63
+ children: [children, actions]
64
+ })]
65
+ });
66
+ }
67
+ /** Tinted circle with an icon. Default assistant avatar. */
68
+ function AssistantAvatar({ icon = /* @__PURE__ */ jsx(SparklesIcon, {}), className }) {
69
+ return /* @__PURE__ */ jsx("div", {
70
+ "data-slot": "assistant-avatar",
71
+ className: cn("grid size-8 shrink-0 place-items-center rounded-full bg-primary-soft text-primary [&_svg]:size-4", className),
72
+ children: icon
73
+ });
74
+ }
75
+ /** Copy / regenerate / thumbs row under an assistant message. Add more buttons as children. */
76
+ function MessageActions({ copyText, onRegenerate, onFeedback, feedback, className, children, ...props }) {
77
+ const { copied, copy } = useCopyToClipboard();
78
+ return /* @__PURE__ */ jsxs("div", {
79
+ "data-slot": "message-actions",
80
+ className: cn("-ml-2 flex gap-0.5", className),
81
+ ...props,
82
+ children: [
83
+ copyText !== void 0 && /* @__PURE__ */ jsx(Button, {
84
+ variant: "ghost",
85
+ size: "icon-sm",
86
+ "aria-label": "Copy response",
87
+ onClick: () => copy(copyText),
88
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
89
+ }),
90
+ onRegenerate && /* @__PURE__ */ jsx(Button, {
91
+ variant: "ghost",
92
+ size: "icon-sm",
93
+ "aria-label": "Regenerate",
94
+ onClick: onRegenerate,
95
+ children: /* @__PURE__ */ jsx(RefreshCwIcon, {})
96
+ }),
97
+ onFeedback && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
98
+ variant: "ghost",
99
+ size: "icon-sm",
100
+ "aria-label": "Good response",
101
+ "aria-pressed": feedback === "up",
102
+ className: "aria-pressed:text-primary",
103
+ onClick: () => onFeedback("up"),
104
+ children: /* @__PURE__ */ jsx(ThumbsUpIcon, {})
105
+ }), /* @__PURE__ */ jsx(Button, {
106
+ variant: "ghost",
107
+ size: "icon-sm",
108
+ "aria-label": "Bad response",
109
+ "aria-pressed": feedback === "down",
110
+ className: "aria-pressed:text-primary",
111
+ onClick: () => onFeedback("down"),
112
+ children: /* @__PURE__ */ jsx(ThumbsDownIcon, {})
113
+ })] }),
114
+ children
115
+ ]
116
+ });
117
+ }
118
+ /** Three pulsing dots while the assistant is generating. */
119
+ function TypingIndicator({ className, ...props }) {
120
+ return /* @__PURE__ */ jsx("div", {
121
+ "data-slot": "typing-indicator",
122
+ role: "status",
123
+ "aria-label": "Assistant is typing",
124
+ className: cn("flex gap-1.25 py-1", className),
125
+ ...props,
126
+ children: [
127
+ 0,
128
+ 150,
129
+ 300
130
+ ].map((d) => /* @__PURE__ */ jsx("span", {
131
+ className: "inline-block size-1.5 animate-typing rounded-full bg-muted-foreground",
132
+ style: { animationDelay: `${d}ms` }
133
+ }, d))
134
+ });
135
+ }
136
+ /** Outlined chip summarizing a tool call, e.g. "Read 2 notes from homelab/". */
137
+ function ToolCall({ status = "done", icon, className, children, ...props }) {
138
+ return /* @__PURE__ */ jsxs(Badge, {
139
+ "data-slot": "tool-call",
140
+ "data-status": status,
141
+ variant: "outline",
142
+ className: cn("self-start bg-transparent text-muted-foreground", status === "error" && "border-destructive/40 text-destructive", className),
143
+ ...props,
144
+ children: [status === "running" ? /* @__PURE__ */ jsx(Spinner, {}) : icon, children]
145
+ });
146
+ }
147
+ export { AssistantAvatar, ChatMessage, ChatThread, MessageActions, ToolCall, TypingIndicator };
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ export interface CodeBlockProps extends Omit<React.ComponentProps<"div">, "children"> {
3
+ code: string;
4
+ /** Label shown top-left, e.g. "bash". */
5
+ language?: string;
6
+ /** Pre-highlighted content (e.g. from shiki). Falls back to plain `code`. */
7
+ children?: React.ReactNode;
8
+ /** Hide the copy button. */
9
+ hideCopy?: boolean;
10
+ }
11
+ /** Code panel with language label and copy button. No highlighter bundled; pass highlighted children if you have one. */
12
+ declare function CodeBlock({ code, language, children, hideCopy, className, ...props }: CodeBlockProps): React.JSX.Element;
13
+ /** Inline monospace span for commands and identifiers. */
14
+ declare function InlineCode({ className, ...props }: React.ComponentProps<"code">): React.JSX.Element;
15
+ /**
16
+ * Typography for long-form and model output. Styles plain HTML children
17
+ * (p, ul, ol, li, strong, a, code, pre, h1-h4, blockquote, table), so it works
18
+ * with react-markdown or any HTML renderer.
19
+ */
20
+ declare function Prose({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ export { CodeBlock, InlineCode, Prose };
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { Button } from "../ui/button.js";
3
+ import { useCopyToClipboard } from "../../hooks/use-copy-to-clipboard.js";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { CheckIcon, CopyIcon } from "lucide-react";
7
+ // src/components/ai/code-block.tsx
8
+ /** Code panel with language label and copy button. No highlighter bundled; pass highlighted children if you have one. */
9
+ function CodeBlock({ code, language, children, hideCopy, className, ...props }) {
10
+ const { copied, copy } = useCopyToClipboard();
11
+ return /* @__PURE__ */ jsxs("div", {
12
+ "data-slot": "code-block",
13
+ className: cn("overflow-hidden rounded-lg bg-muted", className),
14
+ ...props,
15
+ children: [/* @__PURE__ */ jsxs("div", {
16
+ className: "flex min-h-10 items-center justify-between pt-2 pr-2 pl-4.5",
17
+ children: [/* @__PURE__ */ jsx("span", {
18
+ className: "font-mono text-xs font-medium text-muted-foreground",
19
+ children: language
20
+ }), !hideCopy && /* @__PURE__ */ jsxs(Button, {
21
+ variant: "ghost",
22
+ size: "sm",
23
+ onClick: () => copy(code),
24
+ "aria-label": "Copy code",
25
+ children: [copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {}), copied ? "Copied" : "Copy"]
26
+ })]
27
+ }), /* @__PURE__ */ jsx("pre", {
28
+ className: "m-0 overflow-x-auto px-4.5 pt-1 pb-4.5 font-mono text-[13px] leading-[1.7]",
29
+ children: /* @__PURE__ */ jsx("code", { children: children ?? code })
30
+ })]
31
+ });
32
+ }
33
+ /** Inline monospace span for commands and identifiers. */
34
+ function InlineCode({ className, ...props }) {
35
+ return /* @__PURE__ */ jsx("code", {
36
+ "data-slot": "inline-code",
37
+ className: cn("rounded-[6px] bg-muted px-1.5 py-0.5 font-mono text-[12.5px]", className),
38
+ ...props
39
+ });
40
+ }
41
+ /**
42
+ * Typography for long-form and model output. Styles plain HTML children
43
+ * (p, ul, ol, li, strong, a, code, pre, h1-h4, blockquote, table), so it works
44
+ * with react-markdown or any HTML renderer.
45
+ */
46
+ function Prose({ className, ...props }) {
47
+ return /* @__PURE__ */ jsx("div", {
48
+ "data-slot": "prose",
49
+ className: cn("flex flex-col gap-3.5 text-[15px] leading-[1.65] text-foreground", "[&_ol]:flex [&_ol]:list-decimal [&_ol]:flex-col [&_ol]:gap-2 [&_ol]:pl-5.5", "[&_ul]:flex [&_ul]:list-disc [&_ul]:flex-col [&_ul]:gap-2 [&_ul]:pl-5.5", "[&_strong]:font-semibold [&_a]:text-primary [&_a]:underline-offset-4 hover:[&_a]:underline", "[&_h1]:text-xl [&_h1]:font-semibold [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:font-semibold [&_h4]:font-semibold", "[&_blockquote]:border-l-2 [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground", "[&_:not(pre)>code]:rounded-[6px] [&_:not(pre)>code]:bg-muted [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[12.5px]", "[&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-muted [&_pre]:p-4.5 [&_pre]:font-mono [&_pre]:text-[13px]", "[&_table]:w-full [&_table]:text-sm [&_td]:border-b [&_td]:py-2 [&_th]:border-b [&_th]:py-2 [&_th]:text-left [&_th]:font-medium", className),
50
+ ...props
51
+ });
52
+ }
53
+ export { CodeBlock, InlineCode, Prose };