harebear-ui 0.0.0-stage → 0.2.2

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 (198) hide show
  1. package/AGENTS.md +66 -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 +151 -0
  31. package/dist/components/ai/code-block.d.ts +27 -0
  32. package/dist/components/ai/code-block.js +63 -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/auth/auth.d.ts +85 -0
  40. package/dist/components/auth/auth.js +325 -0
  41. package/dist/components/data/bar-list.d.ts +14 -0
  42. package/dist/components/data/bar-list.js +27 -0
  43. package/dist/components/data/data-table.d.ts +70 -0
  44. package/dist/components/data/data-table.js +208 -0
  45. package/dist/components/data/sparkline.d.ts +16 -0
  46. package/dist/components/data/sparkline.js +72 -0
  47. package/dist/components/data/status-bars.d.ts +17 -0
  48. package/dist/components/data/status-bars.js +37 -0
  49. package/dist/components/data/trend-chart.d.ts +31 -0
  50. package/dist/components/data/trend-chart.js +136 -0
  51. package/dist/components/theme/theme-provider.d.ts +42 -0
  52. package/dist/components/theme/theme-provider.js +110 -0
  53. package/dist/components/theme/theme-toggle.d.ts +5 -0
  54. package/dist/components/theme/theme-toggle.js +21 -0
  55. package/dist/components/ui/accordion.d.ts +7 -0
  56. package/dist/components/ui/accordion.js +53 -0
  57. package/dist/components/ui/alert-dialog.d.ts +18 -0
  58. package/dist/components/ui/alert-dialog.js +99 -0
  59. package/dist/components/ui/alert.d.ts +10 -0
  60. package/dist/components/ui/alert.js +42 -0
  61. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  62. package/dist/components/ui/aspect-ratio.js +11 -0
  63. package/dist/components/ui/attachment.d.ts +24 -0
  64. package/dist/components/ui/attachment.js +100 -0
  65. package/dist/components/ui/avatar.d.ts +11 -0
  66. package/dist/components/ui/avatar.js +49 -0
  67. package/dist/components/ui/badge.d.ts +9 -0
  68. package/dist/components/ui/badge.js +30 -0
  69. package/dist/components/ui/breadcrumb.d.ts +11 -0
  70. package/dist/components/ui/breadcrumb.js +70 -0
  71. package/dist/components/ui/bubble.d.ts +17 -0
  72. package/dist/components/ui/bubble.js +71 -0
  73. package/dist/components/ui/button-group.d.ts +11 -0
  74. package/dist/components/ui/button-group.js +39 -0
  75. package/dist/components/ui/button.d.ts +11 -0
  76. package/dist/components/ui/button.js +55 -0
  77. package/dist/components/ui/calendar.d.ts +10 -0
  78. package/dist/components/ui/calendar.js +108 -0
  79. package/dist/components/ui/card.d.ts +11 -0
  80. package/dist/components/ui/card.js +55 -0
  81. package/dist/components/ui/carousel.d.ts +28 -0
  82. package/dist/components/ui/carousel.js +131 -0
  83. package/dist/components/ui/chart.d.ts +44 -0
  84. package/dist/components/ui/chart.js +148 -0
  85. package/dist/components/ui/checkbox.d.ts +4 -0
  86. package/dist/components/ui/checkbox.js +19 -0
  87. package/dist/components/ui/code-block.d.ts +26 -0
  88. package/dist/components/ui/code-block.js +123 -0
  89. package/dist/components/ui/collapsible.d.ts +5 -0
  90. package/dist/components/ui/collapsible.js +23 -0
  91. package/dist/components/ui/combobox.d.ts +24 -0
  92. package/dist/components/ui/combobox.js +162 -0
  93. package/dist/components/ui/command.d.ts +18 -0
  94. package/dist/components/ui/command.js +86 -0
  95. package/dist/components/ui/context-menu.d.ts +31 -0
  96. package/dist/components/ui/context-menu.js +123 -0
  97. package/dist/components/ui/dialog.d.ts +17 -0
  98. package/dist/components/ui/dialog.js +94 -0
  99. package/dist/components/ui/direction.d.ts +7 -0
  100. package/dist/components/ui/direction.js +12 -0
  101. package/dist/components/ui/drawer.d.ts +13 -0
  102. package/dist/components/ui/drawer.js +76 -0
  103. package/dist/components/ui/dropdown-menu.d.ts +29 -0
  104. package/dist/components/ui/dropdown-menu.js +126 -0
  105. package/dist/components/ui/empty.d.ts +11 -0
  106. package/dist/components/ui/empty.js +59 -0
  107. package/dist/components/ui/field.d.ts +24 -0
  108. package/dist/components/ui/field.js +109 -0
  109. package/dist/components/ui/hover-card.d.ts +6 -0
  110. package/dist/components/ui/hover-card.js +30 -0
  111. package/dist/components/ui/input-group.d.ts +16 -0
  112. package/dist/components/ui/input-group.js +77 -0
  113. package/dist/components/ui/input-otp.d.ts +11 -0
  114. package/dist/components/ui/input-otp.js +46 -0
  115. package/dist/components/ui/input.d.ts +3 -0
  116. package/dist/components/ui/input.js +13 -0
  117. package/dist/components/ui/item.d.ts +23 -0
  118. package/dist/components/ui/item.js +117 -0
  119. package/dist/components/ui/kbd.d.ts +3 -0
  120. package/dist/components/ui/kbd.js +19 -0
  121. package/dist/components/ui/label.d.ts +4 -0
  122. package/dist/components/ui/label.js +13 -0
  123. package/dist/components/ui/marker.d.ts +11 -0
  124. package/dist/components/ui/marker.js +39 -0
  125. package/dist/components/ui/menubar.d.ts +30 -0
  126. package/dist/components/ui/menubar.js +133 -0
  127. package/dist/components/ui/message-scroller.d.ts +10 -0
  128. package/dist/components/ui/message-scroller.js +59 -0
  129. package/dist/components/ui/message.d.ts +10 -0
  130. package/dist/components/ui/message.js +48 -0
  131. package/dist/components/ui/native-select.d.ts +8 -0
  132. package/dist/components/ui/native-select.js +37 -0
  133. package/dist/components/ui/navigation-menu.d.ts +14 -0
  134. package/dist/components/ui/navigation-menu.js +79 -0
  135. package/dist/components/ui/pagination.d.ts +17 -0
  136. package/dist/components/ui/pagination.js +79 -0
  137. package/dist/components/ui/popover.d.ts +10 -0
  138. package/dist/components/ui/popover.js +54 -0
  139. package/dist/components/ui/progress.d.ts +4 -0
  140. package/dist/components/ui/progress.js +18 -0
  141. package/dist/components/ui/questionnaire.d.ts +19 -0
  142. package/dist/components/ui/questionnaire.js +168 -0
  143. package/dist/components/ui/radio-group.d.ts +5 -0
  144. package/dist/components/ui/radio-group.js +25 -0
  145. package/dist/components/ui/resizable.d.ts +7 -0
  146. package/dist/components/ui/resizable.js +27 -0
  147. package/dist/components/ui/scroll-area.d.ts +5 -0
  148. package/dist/components/ui/scroll-area.js +35 -0
  149. package/dist/components/ui/select.d.ts +15 -0
  150. package/dist/components/ui/select.js +98 -0
  151. package/dist/components/ui/separator.d.ts +4 -0
  152. package/dist/components/ui/separator.js +15 -0
  153. package/dist/components/ui/sheet.d.ts +14 -0
  154. package/dist/components/ui/sheet.js +88 -0
  155. package/dist/components/ui/sidebar.d.ts +69 -0
  156. package/dist/components/ui/sidebar.js +377 -0
  157. package/dist/components/ui/skeleton.d.ts +2 -0
  158. package/dist/components/ui/skeleton.js +12 -0
  159. package/dist/components/ui/slider.d.ts +4 -0
  160. package/dist/components/ui/slider.js +35 -0
  161. package/dist/components/ui/sonner.d.ts +3 -0
  162. package/dist/components/ui/sonner.js +29 -0
  163. package/dist/components/ui/spinner.d.ts +2 -0
  164. package/dist/components/ui/spinner.js +15 -0
  165. package/dist/components/ui/switch.d.ts +6 -0
  166. package/dist/components/ui/switch.js +18 -0
  167. package/dist/components/ui/table.d.ts +10 -0
  168. package/dist/components/ui/table.js +65 -0
  169. package/dist/components/ui/tabs.d.ts +11 -0
  170. package/dist/components/ui/tabs.js +45 -0
  171. package/dist/components/ui/textarea.d.ts +3 -0
  172. package/dist/components/ui/textarea.js +12 -0
  173. package/dist/components/ui/toggle-group.d.ts +10 -0
  174. package/dist/components/ui/toggle-group.js +50 -0
  175. package/dist/components/ui/toggle.d.ts +9 -0
  176. package/dist/components/ui/toggle.js +35 -0
  177. package/dist/components/ui/tooltip.d.ts +7 -0
  178. package/dist/components/ui/tooltip.js +34 -0
  179. package/dist/components/ui/trace.d.ts +18 -0
  180. package/dist/components/ui/trace.js +74 -0
  181. package/dist/hooks/use-copy-to-clipboard.d.ts +5 -0
  182. package/dist/hooks/use-copy-to-clipboard.js +24 -0
  183. package/dist/hooks/use-highlighted-code.d.ts +2 -0
  184. package/dist/hooks/use-highlighted-code.js +54 -0
  185. package/dist/hooks/use-mobile.d.ts +1 -0
  186. package/dist/hooks/use-mobile.js +17 -0
  187. package/dist/index.d.ts +94 -0
  188. package/dist/index.js +94 -0
  189. package/dist/lib/highlighter.d.ts +9 -0
  190. package/dist/lib/highlighter.js +77 -0
  191. package/dist/lib/utils.d.ts +3 -0
  192. package/dist/lib/utils.js +8 -0
  193. package/dist/shadcn-base.css +638 -0
  194. package/dist/styles.css +188 -0
  195. package/docs/COMPONENTS.md +405 -0
  196. package/llms.txt +36 -0
  197. package/package.json +119 -4
  198. package/skills/harebear-ui/SKILL.md +34 -0
package/AGENTS.md ADDED
@@ -0,0 +1,66 @@
1
+ # harebear-ui: agent guide
2
+
3
+ This repo is the `harebear-ui` npm package: shadcn/ui primitives restyled to one design system, plus admin, data, and AI chat composites. React 19, Tailwind CSS v4, Radix (`radix-ui`), TanStack Table v9, Recharts 3, TypeScript 7, pnpm.
4
+
5
+ Consumers get compiled ESM + types from `dist/` and a Tailwind stylesheet (`harebear-ui/styles.css`). The source of truth for the look is `src/styles.css` (tokens) and the class strings in components.
6
+
7
+ ## Commands
8
+
9
+ | Command | What it does |
10
+ | ------------------------ | ------------------------------------------------------------------------------------------------- |
11
+ | `pnpm dev` | Playground at http://localhost:5199 with the reference screens. |
12
+ | `pnpm check` | Everything CI runs: lint, typecheck, tests, build, publint, attw. Run before you say you're done. |
13
+ | `pnpm test` | Vitest. `pnpm test -u` updates the export snapshot after an intended API change. |
14
+ | `pnpm gen` | Regenerate `src/index.ts` from component export lists. Run after adding or renaming exports. |
15
+ | `pnpm format` | Biome format + safe fixes. |
16
+ | `pnpm shadcn add <name>` | Pull a new shadcn component into `src/components/ui/`. |
17
+
18
+ ## Layout
19
+
20
+ ```
21
+ src/
22
+ styles.css Theme: tokens on :root/.dark, @theme mapping, @source for consumers. Shipped as harebear-ui/styles.css.
23
+ shadcn-base.css Vendored copy of shadcn/tailwind.css (variants, keyframes). Re-copy on shadcn upgrades.
24
+ index.ts Generated barrel. Do not edit by hand.
25
+ components/ui/ shadcn primitives (radix-nova style), restyled. Keep upstream structure.
26
+ components/theme/ ThemeProvider, ThemeToggle, ThemeScript.
27
+ components/admin/ Layout and dashboard composites (AppShell, StatCard, PageHeader...).
28
+ components/data/ DataTable, TrendChart, Sparkline, BarList, StatusBars.
29
+ components/ai/ Chat composites (ChatThread, ChatMessage, PromptInput, ModelPicker, CodeSnippet...).
30
+ components/auth/ AuthLayout, LoginForm, SignupForm (parameterized shadcn login/signup blocks).
31
+ hooks/, lib/utils.ts Shared hooks and cn().
32
+ playground/screens/ Reference screens that use only public exports (#admin, #chat, #records, #home, #auth, #catalog). Visual spec and usage examples.
33
+ docs/COMPONENTS.md Consumer-facing catalog. Shipped. Test-enforced to mention every export.
34
+ llms.txt, skills/ Consumer-facing agent docs. Shipped.
35
+ bin/harebear-ui.mjs `npx harebear-ui init|docs` for consuming apps.
36
+ test/ Vitest: API snapshot, docs coverage, token integrity, behavior.
37
+ ```
38
+
39
+ ## Adding a component
40
+
41
+ 1. Pick the folder by purpose (`admin`, `data`, `ai`, `auth`; `ui` only for shadcn primitives).
42
+ 2. Follow the existing files: `"use client"` first line, named function component, props interface exported as `<Name>Props`, `data-slot="<kebab-name>"` on the root, `className` merged last with `cn()`, rest props spread on the root.
43
+ 3. Style only with tokens (`bg-card`, `text-muted-foreground`, `bg-primary-soft`, `shadow-card`, `rounded-xl`). No hex, no palette colors. Status takes a `tone` (`success | warning | danger | info | neutral`).
44
+ 4. `pnpm gen`, then add the component to `docs/COMPONENTS.md` (the docs test fails otherwise) and to the index in `llms.txt`.
45
+ 5. Use it in a playground screen if it is visual, and look at it in light and dark mode and at 375px width.
46
+ 6. Add a behavior test in `test/components.test.tsx` if it has logic. `pnpm test -u` to accept the new export in the snapshot.
47
+ 7. `pnpm check`.
48
+
49
+ ## Changing shadcn primitives
50
+
51
+ - Keep upstream file structure so `pnpm shadcn add <name> -o` diffs stay readable. Change class strings, add variants; avoid restructuring.
52
+ - shadcn sometimes rewrites the utils import to `from "cn"`. It must be `from "@/lib/utils"`. Remove any `cn` dependency it adds. Same for `next-themes` (sonner uses our `useTheme`).
53
+ - New shadcn files need `"use client"` as the first line.
54
+ - Restyle new primitives to harebear's look: form controls are 40px `rounded-md bg-muted border-transparent` with `focus-visible:border-primary focus-visible:bg-card focus-visible:ring-4 focus-visible:ring-primary/15`; replace `border-input` / `dark:bg-input/30` patterns; selected states use `bg-primary-soft`.
55
+ - The package includes every `registry:ui` item. Check for gaps with `pnpm shadcn list @shadcn --json` and compare names against `src/components/ui/`.
56
+
57
+ ## Gotchas
58
+
59
+ - `@/` is a source alias. tsdown rewrites it to relative paths in `dist`. Do not import from `harebear-ui` inside `src`.
60
+ - TanStack Table v9 needs features registered in `tableFeatures()` with row-model slots; see `dataTableFeatures` in `src/components/data/data-table.tsx` and the skills shipped in `node_modules/@tanstack/react-table/skills/`.
61
+ - `src/styles.css` has `@source "./"` so a consumer's Tailwind scans `dist/`. Classes must be complete literal strings (no string concatenation) or consumers will not get the CSS.
62
+ - Biome formats with no semicolons and double quotes. Rules for `src/components/ui/**` are relaxed in `biome.json` to keep upstream code as is.
63
+
64
+ ## Releasing
65
+
66
+ Bump `version` in package.json, commit, tag `vX.Y.Z`, push the tag. The `release` workflow publishes to npm with provenance (trusted publishing, no token in the repo). Ask the maintainer before tagging.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harrison Milbradt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # harebear-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Pre-themed React components for admin tools, data apps, and AI chat. shadcn/ui primitives restyled to one opinionated design system, plus the composites you end up building every time: app shell, KPI cards, data table, trend charts, chat thread, prompt composer, model picker.
4
+
5
+ React 19 · Tailwind CSS v4 · Radix · TanStack Table v9 · Recharts 3 · TypeScript
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i harebear-ui
11
+ ```
12
+
13
+ ```bash
14
+ npx harebear-ui init
15
+ ```
16
+
17
+ `init` adds the stylesheet import to your Tailwind entry CSS, writes a harebear-ui section into `AGENTS.md` (or `CLAUDE.md`), and installs a Claude Code skill in `.claude/skills/`. Flags: `--css <file>`, `--no-agents`, `--no-skill`.
18
+
19
+ Manual setup, if you prefer:
20
+
21
+ ```css
22
+ /* app.css */
23
+ @import "tailwindcss";
24
+ @import "harebear-ui/styles.css";
25
+ ```
26
+
27
+ ```tsx
28
+ import { ThemeProvider, Toaster, TooltipProvider } from "harebear-ui"
29
+
30
+ export function Root() {
31
+ return (
32
+ <ThemeProvider accent="#2F5BD3" radius={14}>
33
+ <TooltipProvider>
34
+ <App />
35
+ <Toaster />
36
+ </TooltipProvider>
37
+ </ThemeProvider>
38
+ )
39
+ }
40
+ ```
41
+
42
+ Requires Tailwind CSS v4 in the app. Geist and Geist Mono are bundled.
43
+
44
+ ## Use
45
+
46
+ ```tsx
47
+ import { AppShell, PageHeader, StatCard, Button, DataTable } from "harebear-ui"
48
+ ```
49
+
50
+ Everything imports from the package root. Primitives keep shadcn/ui names and props.
51
+
52
+ - **Component catalog:** [docs/COMPONENTS.md](docs/COMPONENTS.md), also at `node_modules/harebear-ui/docs/COMPONENTS.md` or `npx harebear-ui docs`.
53
+ - **Reference screens:** [playground/screens](playground/screens) has an admin dashboard, an AI chat, a data table with detail panel, and a status home page built only from public exports.
54
+
55
+ ## Theming
56
+
57
+ Light and dark mode, accent, radius, and font are runtime props on `ThemeProvider` (`useTheme()` changes them live). Every component reads shadcn-named tokens, so you can override any of them in CSS:
58
+
59
+ ```css
60
+ :root {
61
+ --brand: #0f8a7e;
62
+ --radius: 0.625rem;
63
+ }
64
+ .dark {
65
+ --background: #0b0b0d;
66
+ }
67
+ ```
68
+
69
+ For SSR apps, render `<ThemeScript />` in `<head>` to avoid a flash of the wrong mode.
70
+
71
+ ## For AI agents
72
+
73
+ The package ships its own agent docs:
74
+
75
+ | File | For |
76
+ | ----------------------------- | ------------------------------------------------------ |
77
+ | `llms.txt` | Short index: setup, rules, component list. |
78
+ | `docs/COMPONENTS.md` | Every export with props and examples. |
79
+ | `skills/harebear-ui/SKILL.md` | Claude Code skill; `npx harebear-ui init` installs it. |
80
+
81
+ Working on this repo with an agent: see [AGENTS.md](AGENTS.md).
82
+
83
+ ## Develop
84
+
85
+ ```bash
86
+ pnpm install
87
+ pnpm dev # playground on :5199
88
+ pnpm check # lint, typecheck, test, build, package lint
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ // harebear-ui CLI. Zero dependencies.
3
+ // npx harebear-ui init wire the stylesheet and install agent docs into this project
4
+ // npx harebear-ui docs print the component catalog
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
6
+ import { dirname, join, relative } from "node:path"
7
+ import { fileURLToPath } from "node:url"
8
+
9
+ const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..")
10
+ const cwd = process.cwd()
11
+ const [cmd = "help", ...args] = process.argv.slice(2)
12
+ const flag = (name) => args.includes(`--${name}`)
13
+ const option = (name) => {
14
+ const i = args.indexOf(`--${name}`)
15
+ return i >= 0 ? args[i + 1] : undefined
16
+ }
17
+
18
+ const IMPORT_LINE = '@import "harebear-ui/styles.css";'
19
+ const START = "<!-- harebear-ui:start -->"
20
+ const END = "<!-- harebear-ui:end -->"
21
+ const AGENT_BLOCK = `${START}
22
+ ## UI: harebear-ui
23
+
24
+ All UI in this project uses the \`harebear-ui\` component library (pre-themed shadcn/ui on Tailwind v4, plus admin, data table, chart, and AI chat components).
25
+
26
+ - Read \`node_modules/harebear-ui/docs/COMPONENTS.md\` before building UI. It lists every component with props and examples.
27
+ - Import from the package root: \`import { Button, Card, DataTable } from "harebear-ui"\`.
28
+ - Style only with theme tokens (\`bg-card\`, \`bg-muted\`, \`text-muted-foreground\`, \`bg-primary-soft\`, \`text-success\`, \`shadow-card\`, \`rounded-xl\`). No hex or Tailwind palette colors.
29
+ - Check new screens in light and dark mode and at phone width.
30
+ ${END}`
31
+
32
+ function findCss(dir, depth = 0, out = []) {
33
+ if (depth > 4) return out
34
+ for (const name of readdirSync(dir)) {
35
+ if (name.startsWith(".") || ["node_modules", "dist", "build", ".next", "out", "coverage"].includes(name)) continue
36
+ const p = join(dir, name)
37
+ const st = statSync(p)
38
+ if (st.isDirectory()) findCss(p, depth + 1, out)
39
+ else if (/\.css$/.test(name) && /@import\s+["']tailwindcss["']/.test(readFileSync(p, "utf8"))) out.push(p)
40
+ }
41
+ return out
42
+ }
43
+
44
+ function wireCss() {
45
+ const explicit = option("css")
46
+ const files = explicit ? [join(cwd, explicit)] : findCss(cwd)
47
+ if (files.length === 0) {
48
+ console.log(
49
+ `! No CSS file with @import "tailwindcss" found. Add this after your Tailwind import:\n ${IMPORT_LINE}`,
50
+ )
51
+ return
52
+ }
53
+ if (files.length > 1 && !explicit) {
54
+ console.log(
55
+ `! Several Tailwind entry files found; pass one with --css:\n${files.map((f) => ` ${relative(cwd, f)}`).join("\n")}`,
56
+ )
57
+ return
58
+ }
59
+ const file = files[0]
60
+ const css = readFileSync(file, "utf8")
61
+ if (css.includes("harebear-ui/styles.css")) {
62
+ console.log(`✓ ${relative(cwd, file)} already imports harebear-ui/styles.css`)
63
+ return
64
+ }
65
+ const next = css.replace(/(@import\s+["']tailwindcss["'][^;]*;)/, `$1\n${IMPORT_LINE}`)
66
+ writeFileSync(file, next)
67
+ console.log(`✓ Added ${IMPORT_LINE} to ${relative(cwd, file)}`)
68
+ }
69
+
70
+ function writeAgentInstructions() {
71
+ const target = ["AGENTS.md", "CLAUDE.md"].map((f) => join(cwd, f)).find(existsSync) ?? join(cwd, "AGENTS.md")
72
+ const current = existsSync(target) ? readFileSync(target, "utf8") : ""
73
+ const block = new RegExp(`${START}[\\s\\S]*?${END}`)
74
+ const next = block.test(current)
75
+ ? current.replace(block, AGENT_BLOCK)
76
+ : `${current}${current && !current.endsWith("\n") ? "\n" : ""}${current ? "\n" : ""}${AGENT_BLOCK}\n`
77
+ writeFileSync(target, next)
78
+ console.log(`✓ Wrote harebear-ui section to ${relative(cwd, target)}`)
79
+ }
80
+
81
+ function installSkill() {
82
+ const src = join(pkgRoot, "skills/harebear-ui/SKILL.md")
83
+ const destDir = join(cwd, ".claude/skills/harebear-ui")
84
+ mkdirSync(destDir, { recursive: true })
85
+ writeFileSync(join(destDir, "SKILL.md"), readFileSync(src, "utf8"))
86
+ console.log(`✓ Installed Claude Code skill to ${relative(cwd, destDir)}/SKILL.md`)
87
+ }
88
+
89
+ switch (cmd) {
90
+ case "init": {
91
+ if (!existsSync(join(cwd, "package.json"))) {
92
+ console.error("Run this from your app's root (no package.json here).")
93
+ process.exit(1)
94
+ }
95
+ wireCss()
96
+ if (!flag("no-agents")) writeAgentInstructions()
97
+ if (!flag("no-skill")) installSkill()
98
+ console.log(`
99
+ Next: wrap your app root.
100
+
101
+ import { ThemeProvider, Toaster, TooltipProvider } from "harebear-ui"
102
+
103
+ <ThemeProvider>
104
+ <TooltipProvider>
105
+ <App />
106
+ <Toaster />
107
+ </TooltipProvider>
108
+ </ThemeProvider>
109
+ `)
110
+ break
111
+ }
112
+ case "docs":
113
+ process.stdout.write(readFileSync(join(pkgRoot, "docs/COMPONENTS.md"), "utf8"))
114
+ break
115
+ default:
116
+ console.log(`harebear-ui
117
+
118
+ npx harebear-ui init [--css <file>] [--no-agents] [--no-skill]
119
+ Add the stylesheet import after @import "tailwindcss", add a harebear-ui
120
+ section to AGENTS.md (or CLAUDE.md), and install the Claude Code skill.
121
+
122
+ npx harebear-ui docs
123
+ Print the component catalog (props and examples for every export).
124
+ `)
125
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ export interface ActivityItem {
3
+ id?: string;
4
+ actor: string;
5
+ avatarUrl?: string;
6
+ /** Verb phrase, e.g. "commented on". Rendered muted. */
7
+ action: React.ReactNode;
8
+ target?: React.ReactNode;
9
+ time: React.ReactNode;
10
+ }
11
+ /** "Maya published Pricing page v2 · 12 min ago" list. */
12
+ declare function ActivityFeed({ items, className, ...props }: Omit<React.ComponentProps<"ul">, "children"> & {
13
+ items: ActivityItem[];
14
+ }): React.JSX.Element;
15
+ export { ActivityFeed };
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { initials } from "./user-chip.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ // src/components/admin/activity-feed.tsx
7
+ /** "Maya published Pricing page v2 · 12 min ago" list. */
8
+ function ActivityFeed({ items, className, ...props }) {
9
+ return /* @__PURE__ */ jsx("ul", {
10
+ "data-slot": "activity-feed",
11
+ className: cn("flex flex-col gap-4.5", className),
12
+ ...props,
13
+ children: items.map((a, i) => /* @__PURE__ */ jsxs("li", {
14
+ className: "flex items-start gap-3",
15
+ children: [/* @__PURE__ */ jsxs(Avatar, {
16
+ size: "sm",
17
+ className: "size-8",
18
+ children: [a.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, {
19
+ src: a.avatarUrl,
20
+ alt: ""
21
+ }), /* @__PURE__ */ jsx(AvatarFallback, {
22
+ className: "text-xs",
23
+ children: initials(a.actor)
24
+ })]
25
+ }), /* @__PURE__ */ jsxs("div", {
26
+ className: "flex min-w-0 flex-1 flex-col gap-0.5 text-[13px]",
27
+ children: [/* @__PURE__ */ jsxs("span", { children: [
28
+ /* @__PURE__ */ jsx("span", {
29
+ className: "font-semibold",
30
+ children: a.actor
31
+ }),
32
+ " ",
33
+ /* @__PURE__ */ jsx("span", {
34
+ className: "text-muted-foreground",
35
+ children: a.action
36
+ }),
37
+ " ",
38
+ a.target && /* @__PURE__ */ jsx("span", {
39
+ className: "font-medium",
40
+ children: a.target
41
+ })
42
+ ] }), /* @__PURE__ */ jsx("span", {
43
+ className: "text-xs font-medium text-muted-foreground",
44
+ children: a.time
45
+ })]
46
+ })]
47
+ }, a.id ?? i))
48
+ });
49
+ }
50
+ export { ActivityFeed };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+ export interface AppNavItem {
3
+ label: string;
4
+ /** Icon element, e.g. <LayoutDashboardIcon />. */
5
+ icon?: React.ReactNode;
6
+ href?: string;
7
+ onSelect?: () => void;
8
+ active?: boolean;
9
+ /** Count or short label shown at the right. */
10
+ badge?: React.ReactNode;
11
+ }
12
+ export interface AppNavSection {
13
+ label?: string;
14
+ items: AppNavItem[];
15
+ }
16
+ export interface AppBrandProps extends React.ComponentProps<"div"> {
17
+ name: React.ReactNode;
18
+ subtitle?: React.ReactNode;
19
+ /** Icon element shown in the primary-colored tile. */
20
+ icon?: React.ReactNode;
21
+ /** Optional trailing control, e.g. a workspace switcher button. */
22
+ action?: React.ReactNode;
23
+ }
24
+ /** Logo tile + product name. Used in the sidebar header or a top bar. */
25
+ declare function AppBrand({ name, subtitle, icon, action, className, ...props }: AppBrandProps): React.JSX.Element;
26
+ export interface AppShellProps {
27
+ /** Sidebar header content, usually <AppBrand />. */
28
+ brand?: React.ReactNode;
29
+ nav?: AppNavSection[];
30
+ /** Extra sidebar content under the nav (e.g. a usage card). */
31
+ sidebarExtra?: React.ReactNode;
32
+ /** Sidebar bottom content, usually <UserChip />. */
33
+ sidebarFooter?: React.ReactNode;
34
+ /** Top bar content inside the main area (search, actions). */
35
+ header?: React.ReactNode;
36
+ /** Component used for nav links. Default "a". Pass Next.js Link or React Router Link. */
37
+ linkComponent?: React.ElementType;
38
+ defaultOpen?: boolean;
39
+ className?: string;
40
+ children?: React.ReactNode;
41
+ }
42
+ /**
43
+ * Full admin layout: collapsible sidebar (sheet on mobile) + main column.
44
+ * Built on the Sidebar primitives; drop to those for anything custom.
45
+ */
46
+ declare function AppShell({ brand, nav, sidebarExtra, sidebarFooter, header, linkComponent: Link, defaultOpen, className, children }: AppShellProps): React.JSX.Element;
47
+ export { AppBrand, AppShell };
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { Badge } from "../ui/badge.js";
3
+ import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "../ui/sidebar.js";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ // src/components/admin/app-shell.tsx
7
+ /** Logo tile + product name. Used in the sidebar header or a top bar. */
8
+ function AppBrand({ name, subtitle, icon, action, className, ...props }) {
9
+ return /* @__PURE__ */ jsxs("div", {
10
+ "data-slot": "app-brand",
11
+ className: cn("flex items-center gap-3", className),
12
+ ...props,
13
+ children: [
14
+ icon && /* @__PURE__ */ jsx("div", {
15
+ className: "grid size-9 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground [&_svg]:size-[18px]",
16
+ children: icon
17
+ }),
18
+ /* @__PURE__ */ jsxs("div", {
19
+ className: "min-w-0 flex-1",
20
+ children: [/* @__PURE__ */ jsx("div", {
21
+ className: "truncate text-[15px] font-semibold",
22
+ children: name
23
+ }), subtitle && /* @__PURE__ */ jsx("div", {
24
+ className: "truncate text-xs font-medium text-muted-foreground",
25
+ children: subtitle
26
+ })]
27
+ }),
28
+ action
29
+ ]
30
+ });
31
+ }
32
+ /**
33
+ * Full admin layout: collapsible sidebar (sheet on mobile) + main column.
34
+ * Built on the Sidebar primitives; drop to those for anything custom.
35
+ */
36
+ function AppShell({ brand, nav = [], sidebarExtra, sidebarFooter, header, linkComponent: Link = "a", defaultOpen = true, className, children }) {
37
+ return /* @__PURE__ */ jsxs(SidebarProvider, {
38
+ defaultOpen,
39
+ style: { "--sidebar-width": "15.75rem" },
40
+ children: [/* @__PURE__ */ jsxs(Sidebar, {
41
+ className: "border-r-0",
42
+ children: [
43
+ brand && /* @__PURE__ */ jsx(SidebarHeader, {
44
+ className: "px-4 pt-6 pb-2",
45
+ children: brand
46
+ }),
47
+ /* @__PURE__ */ jsxs(SidebarContent, {
48
+ className: "gap-5 px-2",
49
+ children: [nav.map((section, i) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [section.label && /* @__PURE__ */ jsx(SidebarGroupLabel, {
50
+ className: "text-xs font-medium",
51
+ children: section.label
52
+ }), /* @__PURE__ */ jsx(SidebarMenu, {
53
+ className: "gap-1",
54
+ children: section.items.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, {
55
+ asChild: !!item.href,
56
+ isActive: item.active,
57
+ onClick: item.onSelect,
58
+ children: item.href ? /* @__PURE__ */ jsxs(Link, {
59
+ href: item.href,
60
+ children: [
61
+ item.icon,
62
+ /* @__PURE__ */ jsx("span", {
63
+ className: "flex-1",
64
+ children: item.label
65
+ }),
66
+ item.badge !== void 0 && /* @__PURE__ */ jsx(NavBadge, { children: item.badge })
67
+ ]
68
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ item.icon,
70
+ /* @__PURE__ */ jsx("span", {
71
+ className: "flex-1",
72
+ children: item.label
73
+ }),
74
+ item.badge !== void 0 && /* @__PURE__ */ jsx(NavBadge, { children: item.badge })
75
+ ] })
76
+ }) }, item.label))
77
+ })] }, section.label ?? i)), sidebarExtra && /* @__PURE__ */ jsx("div", {
78
+ className: "mt-auto px-2",
79
+ children: sidebarExtra
80
+ })]
81
+ }),
82
+ sidebarFooter && /* @__PURE__ */ jsx(SidebarFooter, {
83
+ className: "px-4 pb-6",
84
+ children: sidebarFooter
85
+ })
86
+ ]
87
+ }), /* @__PURE__ */ jsxs(SidebarInset, {
88
+ className: cn("min-w-0", className),
89
+ children: [header !== void 0 && /* @__PURE__ */ jsxs("header", {
90
+ "data-slot": "app-header",
91
+ className: "flex flex-wrap items-center gap-3 px-4 pt-5 md:px-8",
92
+ children: [/* @__PURE__ */ jsx(SidebarTrigger, { className: "md:hidden" }), header]
93
+ }), /* @__PURE__ */ jsx("div", {
94
+ "data-slot": "app-content",
95
+ className: "flex min-w-0 flex-col gap-7 px-4 pt-7 pb-12 md:px-8",
96
+ children
97
+ })]
98
+ })]
99
+ });
100
+ }
101
+ function NavBadge({ children }) {
102
+ return /* @__PURE__ */ jsx(Badge, {
103
+ variant: "soft",
104
+ className: "h-5 px-2",
105
+ children
106
+ });
107
+ }
108
+ export { AppBrand, AppShell };
@@ -0,0 +1,7 @@
1
+ import * as React from "react";
2
+ /** Quiet inline note with an icon, e.g. an insight or tip. Defaults to a sparkle icon. */
3
+ declare function Callout({ icon, tone, className, children, ...props }: React.ComponentProps<"div"> & {
4
+ icon?: React.ReactNode;
5
+ tone?: "muted" | "soft";
6
+ }): React.JSX.Element;
7
+ export { Callout };
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { SparklesIcon } from "lucide-react";
5
+ // src/components/admin/callout.tsx
6
+ /** Quiet inline note with an icon, e.g. an insight or tip. Defaults to a sparkle icon. */
7
+ function Callout({ icon = /* @__PURE__ */ jsx(SparklesIcon, {}), tone = "muted", className, children, ...props }) {
8
+ return /* @__PURE__ */ jsxs("div", {
9
+ "data-slot": "callout",
10
+ className: cn("flex items-center gap-3 rounded-lg px-4 py-3.5 text-[13px] [&>svg]:size-[18px] [&>svg]:shrink-0 [&>svg]:text-primary", tone === "muted" ? "bg-muted" : "bg-primary-soft", className),
11
+ ...props,
12
+ children: [icon, /* @__PURE__ */ jsx("div", {
13
+ className: "min-w-0 flex-1",
14
+ children
15
+ })]
16
+ });
17
+ }
18
+ export { Callout };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ export interface KeyValueItem {
3
+ label: React.ReactNode;
4
+ value: React.ReactNode;
5
+ }
6
+ /** Label-over-value pairs in a responsive grid. Renders a <dl>. */
7
+ declare function KeyValueGrid({ items, columns, className, ...props }: Omit<React.ComponentProps<"dl">, "children"> & {
8
+ items: KeyValueItem[];
9
+ columns?: 1 | 2 | 3;
10
+ }): React.JSX.Element;
11
+ export { KeyValueGrid };
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ // src/components/admin/key-value-grid.tsx
5
+ /** Label-over-value pairs in a responsive grid. Renders a <dl>. */
6
+ function KeyValueGrid({ items, columns = 2, className, ...props }) {
7
+ return /* @__PURE__ */ jsx("dl", {
8
+ "data-slot": "key-value-grid",
9
+ className: cn("grid gap-x-5 gap-y-4.5", columns === 1 && "grid-cols-1", columns === 2 && "grid-cols-2", columns === 3 && "grid-cols-2 sm:grid-cols-3", className),
10
+ ...props,
11
+ children: items.map((item, i) => /* @__PURE__ */ jsxs("div", {
12
+ className: "flex min-w-0 flex-col gap-0.5",
13
+ children: [/* @__PURE__ */ jsx("dt", {
14
+ className: "text-xs font-medium text-muted-foreground",
15
+ children: item.label
16
+ }), /* @__PURE__ */ jsx("dd", {
17
+ className: "truncate",
18
+ children: item.value
19
+ })]
20
+ }, i))
21
+ });
22
+ }
23
+ export { KeyValueGrid };
@@ -0,0 +1,17 @@
1
+ import { StatusTone } from "./status.js";
2
+ import * as React from "react";
3
+ export interface LinkTileProps extends Omit<React.ComponentProps<"a">, "title"> {
4
+ title: React.ReactNode;
5
+ /** Mono secondary line, e.g. a host:port. */
6
+ subtitle?: React.ReactNode;
7
+ icon?: React.ReactNode;
8
+ status?: {
9
+ tone: StatusTone;
10
+ label: React.ReactNode;
11
+ };
12
+ /** Pass your router's <Link href> as the only child; the tile content renders inside it. */
13
+ asChild?: boolean;
14
+ }
15
+ /** Muted tile that lifts on hover. For service launchers and quick links. */
16
+ declare function LinkTile({ title, subtitle, icon, status, asChild, className, children, ...props }: LinkTileProps): React.JSX.Element;
17
+ export { LinkTile };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { StatusDot } from "./status.js";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { Slot } from "radix-ui";
6
+ // src/components/admin/link-tile.tsx
7
+ /** Muted tile that lifts on hover. For service launchers and quick links. */
8
+ function LinkTile({ title, subtitle, icon, status, asChild, className, children, ...props }) {
9
+ const Comp = asChild ? Slot.Root : "a";
10
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
11
+ className: "flex items-start justify-between",
12
+ children: [/* @__PURE__ */ jsx("span", {
13
+ className: "grid size-9.5 place-items-center rounded-md bg-card text-primary shadow-card [&_svg]:size-[18px]",
14
+ children: icon
15
+ }), status && /* @__PURE__ */ jsxs("span", {
16
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground tabular-nums",
17
+ children: [/* @__PURE__ */ jsx(StatusDot, { tone: status.tone }), status.label]
18
+ })]
19
+ }), /* @__PURE__ */ jsxs("div", {
20
+ className: "flex min-w-0 flex-col gap-px",
21
+ children: [/* @__PURE__ */ jsx("span", {
22
+ className: "font-semibold",
23
+ children: title
24
+ }), subtitle && /* @__PURE__ */ jsx("span", {
25
+ className: "truncate font-mono text-xs text-muted-foreground",
26
+ children: subtitle
27
+ })]
28
+ })] });
29
+ return /* @__PURE__ */ jsxs(Comp, {
30
+ "data-slot": "link-tile",
31
+ className: cn("flex flex-col gap-3.5 rounded-lg bg-muted p-4 text-foreground no-underline transition-[background-color,transform,box-shadow] outline-none hover:-translate-y-px hover:bg-card hover:shadow-card focus-visible:ring-4 focus-visible:ring-ring/20", className),
32
+ ...props,
33
+ children: [asChild && /* @__PURE__ */ jsx(Slot.Slottable, { children }), body]
34
+ });
35
+ }
36
+ export { LinkTile };