@blurrow/ui 0.1.0 → 0.2.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 (272) hide show
  1. package/README.md +60 -20
  2. package/dist/Accordion.d.ts +17 -0
  3. package/dist/Accordion.js +138 -0
  4. package/dist/Accordion.js.map +1 -0
  5. package/dist/Alert.d.ts +13 -0
  6. package/dist/Alert.js +69 -0
  7. package/dist/Alert.js.map +1 -0
  8. package/dist/AuthorCard.d.ts +12 -0
  9. package/dist/AuthorCard.js +57 -0
  10. package/dist/AuthorCard.js.map +1 -0
  11. package/dist/Avatar.d.ts +21 -0
  12. package/dist/Avatar.js +96 -0
  13. package/dist/Avatar.js.map +1 -0
  14. package/dist/Badge.d.ts +12 -0
  15. package/dist/Badge.js +46 -0
  16. package/dist/Badge.js.map +1 -0
  17. package/dist/Banner.d.ts +15 -0
  18. package/dist/Banner.js +68 -0
  19. package/dist/Banner.js.map +1 -0
  20. package/dist/Breadcrumb.d.ts +13 -0
  21. package/dist/Breadcrumb.js +45 -0
  22. package/dist/Breadcrumb.js.map +1 -0
  23. package/dist/Button.d.ts +19 -0
  24. package/dist/Button.js +282 -0
  25. package/dist/Button.js.map +1 -0
  26. package/dist/ButtonGroup.d.ts +19 -0
  27. package/dist/ButtonGroup.js +62 -0
  28. package/dist/ButtonGroup.js.map +1 -0
  29. package/dist/Card.d.ts +40 -0
  30. package/dist/Card.js +175 -0
  31. package/dist/Card.js.map +1 -0
  32. package/dist/CardShell.d.ts +18 -0
  33. package/dist/CardShell.js +58 -0
  34. package/dist/CardShell.js.map +1 -0
  35. package/dist/Carousel.d.ts +22 -0
  36. package/dist/Carousel.js +247 -0
  37. package/dist/Carousel.js.map +1 -0
  38. package/dist/Checkbox.d.ts +15 -0
  39. package/dist/Checkbox.js +84 -0
  40. package/dist/Checkbox.js.map +1 -0
  41. package/dist/CloseButton.d.ts +12 -0
  42. package/dist/CloseButton.js +63 -0
  43. package/dist/CloseButton.js.map +1 -0
  44. package/dist/CodeBlock.d.ts +12 -0
  45. package/dist/CodeBlock.js +221 -0
  46. package/dist/CodeBlock.js.map +1 -0
  47. package/dist/ColorPicker.d.ts +24 -0
  48. package/dist/ColorPicker.js +366 -0
  49. package/dist/ColorPicker.js.map +1 -0
  50. package/dist/Combobox.d.ts +34 -0
  51. package/dist/Combobox.js +255 -0
  52. package/dist/Combobox.js.map +1 -0
  53. package/dist/CommandPalette.d.ts +25 -0
  54. package/dist/CommandPalette.js +304 -0
  55. package/dist/CommandPalette.js.map +1 -0
  56. package/dist/ContextMenu.d.ts +16 -0
  57. package/dist/ContextMenu.js +209 -0
  58. package/dist/ContextMenu.js.map +1 -0
  59. package/dist/DataList.d.ts +15 -0
  60. package/dist/DataList.js +42 -0
  61. package/dist/DataList.js.map +1 -0
  62. package/dist/DatePicker.d.ts +65 -0
  63. package/dist/DatePicker.js +391 -0
  64. package/dist/DatePicker.js.map +1 -0
  65. package/dist/DateRangePicker.d.ts +34 -0
  66. package/dist/DateRangePicker.js +180 -0
  67. package/dist/DateRangePicker.js.map +1 -0
  68. package/dist/Drawer.d.ts +17 -0
  69. package/dist/Drawer.js +132 -0
  70. package/dist/Drawer.js.map +1 -0
  71. package/dist/DropdownMenu.d.ts +23 -0
  72. package/dist/DropdownMenu.js +185 -0
  73. package/dist/DropdownMenu.js.map +1 -0
  74. package/dist/EmptyState.d.ts +13 -0
  75. package/dist/EmptyState.js +55 -0
  76. package/dist/EmptyState.js.map +1 -0
  77. package/dist/FieldMessage.d.ts +26 -0
  78. package/dist/FieldMessage.js +95 -0
  79. package/dist/FieldMessage.js.map +1 -0
  80. package/dist/FileUpload.d.ts +32 -0
  81. package/dist/FileUpload.js +253 -0
  82. package/dist/FileUpload.js.map +1 -0
  83. package/dist/Floating.d.ts +32 -0
  84. package/dist/Floating.js +95 -0
  85. package/dist/Floating.js.map +1 -0
  86. package/dist/HoverCard.d.ts +18 -0
  87. package/dist/HoverCard.js +90 -0
  88. package/dist/HoverCard.js.map +1 -0
  89. package/dist/Input.d.ts +17 -0
  90. package/dist/Input.js +96 -0
  91. package/dist/Input.js.map +1 -0
  92. package/dist/Kbd.d.ts +9 -0
  93. package/dist/Kbd.js +24 -0
  94. package/dist/Kbd.js.map +1 -0
  95. package/dist/Modal.d.ts +15 -0
  96. package/dist/Modal.js +109 -0
  97. package/dist/Modal.js.map +1 -0
  98. package/dist/NumberInput.d.ts +23 -0
  99. package/dist/NumberInput.js +162 -0
  100. package/dist/NumberInput.js.map +1 -0
  101. package/dist/OtpInput.d.ts +26 -0
  102. package/dist/OtpInput.js +173 -0
  103. package/dist/OtpInput.js.map +1 -0
  104. package/dist/Pagination.d.ts +11 -0
  105. package/dist/Pagination.js +148 -0
  106. package/dist/Pagination.js.map +1 -0
  107. package/dist/Popover.d.ts +13 -0
  108. package/dist/Popover.js +98 -0
  109. package/dist/Popover.js.map +1 -0
  110. package/dist/Progress.d.ts +12 -0
  111. package/dist/Progress.js +61 -0
  112. package/dist/Progress.js.map +1 -0
  113. package/dist/QuoteCard.d.ts +13 -0
  114. package/dist/QuoteCard.js +92 -0
  115. package/dist/QuoteCard.js.map +1 -0
  116. package/dist/Radio.d.ts +19 -0
  117. package/dist/Radio.js +161 -0
  118. package/dist/Radio.js.map +1 -0
  119. package/dist/RadioCards.d.ts +27 -0
  120. package/dist/RadioCards.js +205 -0
  121. package/dist/RadioCards.js.map +1 -0
  122. package/dist/ReactionBar.d.ts +15 -0
  123. package/dist/ReactionBar.js +92 -0
  124. package/dist/ReactionBar.js.map +1 -0
  125. package/dist/ReadingProgress.d.ts +8 -0
  126. package/dist/ReadingProgress.js +51 -0
  127. package/dist/ReadingProgress.js.map +1 -0
  128. package/dist/ReadingTime.d.ts +9 -0
  129. package/dist/ReadingTime.js +39 -0
  130. package/dist/ReadingTime.js.map +1 -0
  131. package/dist/Select.d.ts +20 -0
  132. package/dist/Select.js +244 -0
  133. package/dist/Select.js.map +1 -0
  134. package/dist/Separator.d.ts +14 -0
  135. package/dist/Separator.js +33 -0
  136. package/dist/Separator.js.map +1 -0
  137. package/dist/ShareButtons.d.ts +10 -0
  138. package/dist/ShareButtons.js +107 -0
  139. package/dist/ShareButtons.js.map +1 -0
  140. package/dist/Sidebar.d.ts +30 -0
  141. package/dist/Sidebar.js +140 -0
  142. package/dist/Sidebar.js.map +1 -0
  143. package/dist/Skeleton.d.ts +15 -0
  144. package/dist/Skeleton.js +67 -0
  145. package/dist/Skeleton.js.map +1 -0
  146. package/dist/Slider.d.ts +27 -0
  147. package/dist/Slider.js +182 -0
  148. package/dist/Slider.js.map +1 -0
  149. package/dist/Spinner.d.ts +12 -0
  150. package/dist/Spinner.js +24 -0
  151. package/dist/Spinner.js.map +1 -0
  152. package/dist/SpotlightCard.d.ts +17 -0
  153. package/dist/SpotlightCard.js +87 -0
  154. package/dist/SpotlightCard.js.map +1 -0
  155. package/dist/Stat.d.ts +19 -0
  156. package/dist/Stat.js +71 -0
  157. package/dist/Stat.js.map +1 -0
  158. package/dist/StatCard.d.ts +24 -0
  159. package/dist/StatCard.js +212 -0
  160. package/dist/StatCard.js.map +1 -0
  161. package/dist/Steps.d.ts +14 -0
  162. package/dist/Steps.js +94 -0
  163. package/dist/Steps.js.map +1 -0
  164. package/dist/Table.d.ts +21 -0
  165. package/dist/Table.js +93 -0
  166. package/dist/Table.js.map +1 -0
  167. package/dist/TableOfContents.d.ts +17 -0
  168. package/dist/TableOfContents.js +92 -0
  169. package/dist/TableOfContents.js.map +1 -0
  170. package/dist/Tabs.d.ts +17 -0
  171. package/dist/Tabs.js +123 -0
  172. package/dist/Tabs.js.map +1 -0
  173. package/dist/TagInput.d.ts +16 -0
  174. package/dist/TagInput.js +169 -0
  175. package/dist/TagInput.js.map +1 -0
  176. package/dist/Textarea.d.ts +13 -0
  177. package/dist/Textarea.js +74 -0
  178. package/dist/Textarea.js.map +1 -0
  179. package/dist/TimePicker.d.ts +28 -0
  180. package/dist/TimePicker.js +264 -0
  181. package/dist/TimePicker.js.map +1 -0
  182. package/dist/Timeline.d.ts +19 -0
  183. package/dist/Timeline.js +76 -0
  184. package/dist/Timeline.js.map +1 -0
  185. package/dist/Toast.d.ts +18 -0
  186. package/dist/Toast.js +318 -0
  187. package/dist/Toast.js.map +1 -0
  188. package/dist/Toggle.d.ts +13 -0
  189. package/dist/Toggle.js +84 -0
  190. package/dist/Toggle.js.map +1 -0
  191. package/dist/Tooltip.d.ts +13 -0
  192. package/dist/Tooltip.js +138 -0
  193. package/dist/Tooltip.js.map +1 -0
  194. package/dist/cli.js +1644 -0
  195. package/dist/index.d.ts +68 -631
  196. package/dist/index.js +67 -5558
  197. package/dist/index.js.map +1 -1
  198. package/dist/registry/Accordion.tsx +140 -0
  199. package/dist/registry/Alert.tsx +95 -0
  200. package/dist/registry/AuthorCard.tsx +67 -0
  201. package/dist/registry/Avatar.tsx +116 -0
  202. package/dist/registry/Badge.tsx +55 -0
  203. package/dist/registry/Banner.tsx +86 -0
  204. package/dist/registry/Breadcrumb.tsx +60 -0
  205. package/dist/registry/Button.tsx +361 -0
  206. package/dist/registry/ButtonGroup.tsx +82 -0
  207. package/dist/registry/Card.tsx +241 -0
  208. package/dist/registry/CardShell.tsx +78 -0
  209. package/dist/registry/Carousel.tsx +296 -0
  210. package/dist/registry/Checkbox.tsx +104 -0
  211. package/dist/registry/CloseButton.tsx +68 -0
  212. package/dist/registry/CodeBlock.tsx +202 -0
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +356 -0
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +49 -0
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +166 -0
  221. package/dist/registry/DropdownMenu.tsx +219 -0
  222. package/dist/registry/EmptyState.tsx +55 -0
  223. package/dist/registry/FieldMessage.tsx +123 -0
  224. package/dist/registry/FileUpload.tsx +318 -0
  225. package/dist/registry/Floating.tsx +134 -0
  226. package/dist/registry/HoverCard.tsx +113 -0
  227. package/dist/registry/Input.tsx +108 -0
  228. package/dist/registry/Kbd.tsx +23 -0
  229. package/dist/registry/Modal.tsx +130 -0
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +160 -0
  233. package/dist/registry/Popover.tsx +116 -0
  234. package/dist/registry/Progress.tsx +66 -0
  235. package/dist/registry/QuoteCard.tsx +93 -0
  236. package/dist/registry/Radio.tsx +205 -0
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +101 -0
  239. package/dist/registry/ReadingProgress.tsx +51 -0
  240. package/dist/registry/ReadingTime.tsx +35 -0
  241. package/dist/registry/Select.tsx +265 -0
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +134 -0
  244. package/dist/registry/Sidebar.tsx +182 -0
  245. package/dist/registry/Skeleton.tsx +83 -0
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +106 -0
  249. package/dist/registry/Stat.tsx +82 -0
  250. package/dist/registry/StatCard.tsx +256 -0
  251. package/dist/registry/Steps.tsx +109 -0
  252. package/dist/registry/Table.tsx +123 -0
  253. package/dist/registry/TableOfContents.tsx +116 -0
  254. package/dist/registry/Tabs.tsx +136 -0
  255. package/dist/registry/TagInput.tsx +191 -0
  256. package/dist/registry/Textarea.tsx +84 -0
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +102 -0
  259. package/dist/registry/Toast.tsx +388 -0
  260. package/dist/registry/Toggle.tsx +96 -0
  261. package/dist/registry/Tooltip.tsx +160 -0
  262. package/dist/registry/styles.css +1207 -0
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/registry/useModalBehavior.ts +77 -0
  265. package/dist/styles.css +868 -62
  266. package/dist/useFloatingPanel.d.ts +24 -0
  267. package/dist/useFloatingPanel.js +89 -0
  268. package/dist/useFloatingPanel.js.map +1 -0
  269. package/dist/useModalBehavior.d.ts +15 -0
  270. package/dist/useModalBehavior.js +57 -0
  271. package/dist/useModalBehavior.js.map +1 -0
  272. package/package.json +23 -6
package/README.md CHANGED
@@ -1,20 +1,56 @@
1
+ <a href="https://www.blurrow.org">
2
+ <img src="https://raw.githubusercontent.com/manyports/blurrow/main/.github/readme/banner.png" alt="blurrow/ui: buttons you want to press, fields you want to fill in" width="100%">
3
+ </a>
4
+
1
5
  # @blurrow/ui
2
6
 
3
- buttons you want to press, fields you want to fill in.
7
+ 62 react components that look and feel like physical things. buttons sit in a bezel and sink when you press them, text fields are cut into the surface, and every shadow agrees on where the light is coming from.
8
+
9
+ free, mit licensed, and built for react 19 and tailwind css v4. made with care by [@manyports](https://manyports.com).
4
10
 
5
- 48 react components that share the same materials and the same light. made with care by [@manyports](https://manyports.com).
11
+ **[website](https://www.blurrow.org)** · [docs](https://www.blurrow.org/docs) · [all components](https://www.blurrow.org/docs/components) · [github](https://github.com/manyports/blurrow)
6
12
 
7
- ## install
13
+ <img src="https://raw.githubusercontent.com/manyports/blurrow/main/.github/readme/favourites.png" alt="nine blurrow components: buttons, toggles, an input, tabs, pagination, a radio group, a select, a stat card and an alert" width="100%">
14
+
15
+ ## getting started
8
16
 
9
17
  ```bash
10
- npm install @blurrow/ui
18
+ npx @blurrow/ui init
19
+ ```
20
+
21
+ that's the whole setup. it installs the package, adds two lines to your stylesheet, loads the archivo font and wraps your app in `ToastProvider`, showing you each change before it writes anything.
22
+
23
+ <img src="https://raw.githubusercontent.com/manyports/blurrow/main/.github/readme/cli.png" alt="the blurrow cli setting up a next.js project" width="560">
24
+
25
+ then use whatever you like:
26
+
27
+ ```tsx
28
+ import { Button, Input, Toggle } from "@blurrow/ui";
29
+
30
+ export default function Settings() {
31
+ return (
32
+ <form className="flex flex-col gap-4">
33
+ <Input label="display name" placeholder="ada" />
34
+ <Toggle label="email me when someone replies" defaultChecked />
35
+ <Button variant="primary">save</Button>
36
+ </form>
37
+ );
38
+ }
11
39
  ```
12
40
 
13
- you'll need react 19 and tailwind css v4.
41
+ they drop straight into server components. the interactive ones are marked `"use client"`, and the static ones (badges, cards, avatars, skeletons…) render on the server and send no javascript.
42
+
43
+ ## setting it up by hand
44
+
45
+ if you'd rather not run `init`, it does four things:
46
+
47
+ **1. install**
14
48
 
15
- ## set up
49
+ ```bash
50
+ npm install @blurrow/ui
51
+ ```
16
52
 
17
- **1. styles.** add the tokens to your global stylesheet, after tailwind, and point tailwind at the package so it picks up the classes the components use:
53
+ **2. styles.** add the tokens after tailwind, and let tailwind see the classes the components use. the `@source` path is relative to your css file.
18
54
 
19
55
  ```css
20
56
  /* app/globals.css */
@@ -24,9 +60,7 @@ you'll need react 19 and tailwind css v4.
24
60
  @source "../node_modules/@blurrow/ui";
25
61
  ```
26
62
 
27
- the `@source` path is relative to the css file, so adjust it if your stylesheet lives somewhere else.
28
-
29
- **2. font.** everything is set in archivo. load it as the `--font-archivo` variable. with next.js:
63
+ **3. font.** everything is set in archivo, loaded as the `--font-archivo` variable. with next.js:
30
64
 
31
65
  ```tsx
32
66
  // app/layout.tsx
@@ -39,7 +73,7 @@ const archivo = Archivo({ subsets: ["latin"], variable: "--font-archivo" });
39
73
  export default function RootLayout({ children }: { children: React.ReactNode }) {
40
74
  return (
41
75
  <html lang="en" className={archivo.variable}>
42
- <body className="bg-[#EBEBEB] font-sans text-neutral-900 antialiased">
76
+ <body className="bg-page font-sans text-ink antialiased">
43
77
  <ToastProvider>{children}</ToastProvider>
44
78
  </body>
45
79
  </html>
@@ -47,20 +81,26 @@ export default function RootLayout({ children }: { children: React.ReactNode })
47
81
  }
48
82
  ```
49
83
 
50
- `ToastProvider` is only needed if you use toasts.
84
+ **4. toasts.** `ToastProvider` above is only needed if you use `useToast()`.
51
85
 
52
- ## use
86
+ ## dark mode
53
87
 
54
- ```tsx
55
- import { Button } from "@blurrow/ui";
88
+ every colour, shadow and gradient is a css variable with a light and a dark value. put `data-theme="dark"` on `<html>` (or the class `dark`), or on any element to darken just that part. `data-theme="system"` follows the os setting, and an explicit `light` or `dark` always wins. `bg-page` and `text-ink` let your own page follow along. more in [the docs](https://www.blurrow.org/docs/installation#dark-mode).
56
89
 
57
- export default function Page() {
58
- return <Button variant="primary">say hi</Button>;
59
- }
90
+ ## the cli
91
+
92
+ ```bash
93
+ npx @blurrow/ui init # set up a project, safe to run again
94
+ npx @blurrow/ui add [names…] # copy components' source in, with anything they need
95
+ npx @blurrow/ui list # every component, and which ones you've copied
96
+ npx @blurrow/ui diff [name] # what you've changed in your copies
97
+ npx @blurrow/ui doctor [--fix] # check the setup, and repair the css if it's off
60
98
  ```
61
99
 
62
- every component is marked `"use client"`, so you can import them straight into server components.
100
+ `add` with no names opens a picker: type to filter, space to choose. names are forgiving (`button`, `Button` and `button-group` all work) and typos get a suggestion. files you've edited are never overwritten unless you pass `--overwrite`.
101
+
102
+ everything takes `--yes`, `--dry-run` and `--cwd <path>`. settings live in `blurrow.json`. once the package is installed, `npx blurrow` works as a shorter name.
63
103
 
64
104
  ## license
65
105
 
66
- MIT
106
+ mit. issues and ideas are welcome on [github](https://github.com/manyports/blurrow/issues).
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface AccordionItem {
5
+ key: string;
6
+ title: string;
7
+ content: ReactNode;
8
+ defaultOpen?: boolean;
9
+ }
10
+ interface AccordionProps {
11
+ items: AccordionItem[];
12
+ allowMultiple?: boolean;
13
+ className?: string;
14
+ }
15
+ declare function Accordion({ items, allowMultiple, className }: AccordionProps): react_jsx_runtime.JSX.Element;
16
+
17
+ export { Accordion };
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Accordion.tsx
4
+ import { useState, useCallback, useId } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function Accordion({ items, allowMultiple = false, className }) {
7
+ const [openKeys, setOpenKeys] = useState(() => {
8
+ const defaults = /* @__PURE__ */ new Set();
9
+ for (const item of items) {
10
+ if (item.defaultOpen) {
11
+ defaults.add(item.key);
12
+ }
13
+ }
14
+ return defaults;
15
+ });
16
+ const toggle = useCallback(
17
+ (key) => {
18
+ setOpenKeys((prev) => {
19
+ const next = new Set(prev);
20
+ if (next.has(key)) {
21
+ next.delete(key);
22
+ } else {
23
+ if (allowMultiple) {
24
+ next.add(key);
25
+ } else {
26
+ next.clear();
27
+ next.add(key);
28
+ }
29
+ }
30
+ return next;
31
+ });
32
+ },
33
+ [allowMultiple]
34
+ );
35
+ const uid = useId();
36
+ return /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ className: ["rounded-[18px] p-1.5", className].filter(Boolean).join(" "),
40
+ style: { background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" },
41
+ children: /* @__PURE__ */ jsx(
42
+ "div",
43
+ {
44
+ className: "bg-panel-face rounded-[13px] overflow-hidden p-1",
45
+ style: { boxShadow: "var(--panel-face-shadow)" },
46
+ children: items.map((item, index) => {
47
+ const isOpen = openKeys.has(item.key);
48
+ const isLast = index === items.length - 1;
49
+ const headerId = `${uid}-${item.key}-header`;
50
+ const panelId = `${uid}-${item.key}-panel`;
51
+ return /* @__PURE__ */ jsxs(
52
+ "div",
53
+ {
54
+ className: [
55
+ "relative",
56
+ // Hairline between rows, inset so it doesn't touch the rounded corners
57
+ !isLast && "after:absolute after:left-3 after:right-3 after:bottom-0 after:h-px after:bg-line"
58
+ ].filter(Boolean).join(" "),
59
+ children: [
60
+ /* @__PURE__ */ jsx("h3", { children: /* @__PURE__ */ jsxs(
61
+ "button",
62
+ {
63
+ type: "button",
64
+ id: headerId,
65
+ "aria-expanded": isOpen,
66
+ "aria-controls": panelId,
67
+ onClick: () => toggle(item.key),
68
+ className: [
69
+ "group/acc w-full flex items-center justify-between gap-3 py-3 px-3 text-left text-[13.5px] font-medium cursor-pointer rounded-[10px]",
70
+ "transition-colors duration-150",
71
+ isOpen ? "text-ink" : "text-ink-label hover:text-ink hover:bg-surface-hover"
72
+ ].join(" "),
73
+ children: [
74
+ /* @__PURE__ */ jsx("span", { children: item.title }),
75
+ /* @__PURE__ */ jsxs(
76
+ "span",
77
+ {
78
+ "aria-hidden": true,
79
+ className: "relative shrink-0 w-6 h-6 rounded-[7px] flex items-center justify-center text-ink-muted group-hover/acc:text-ink transition-colors",
80
+ style: { background: "var(--face)", boxShadow: "var(--face-shadow)" },
81
+ children: [
82
+ /* @__PURE__ */ jsx("span", { className: "absolute w-2.5 h-[1.5px] rounded-full bg-current" }),
83
+ /* @__PURE__ */ jsx(
84
+ "span",
85
+ {
86
+ className: "absolute w-2.5 h-[1.5px] rounded-full bg-current",
87
+ style: {
88
+ transform: isOpen ? "rotate(0deg)" : "rotate(90deg)",
89
+ transition: "transform 300ms var(--ease-spring)"
90
+ }
91
+ }
92
+ )
93
+ ]
94
+ }
95
+ )
96
+ ]
97
+ }
98
+ ) }),
99
+ /* @__PURE__ */ jsx(
100
+ "div",
101
+ {
102
+ id: panelId,
103
+ role: "region",
104
+ "aria-labelledby": headerId,
105
+ inert: !isOpen,
106
+ style: {
107
+ display: "grid",
108
+ gridTemplateRows: isOpen ? "1fr" : "0fr",
109
+ transition: "grid-template-rows 320ms var(--ease-out-soft)"
110
+ },
111
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx(
112
+ "div",
113
+ {
114
+ className: "px-3 pb-4 pt-0.5 text-[13px] text-ink-body leading-relaxed",
115
+ style: {
116
+ opacity: isOpen ? 1 : 0,
117
+ transform: isOpen ? "translateY(0)" : "translateY(-4px)",
118
+ transition: "opacity 240ms ease-out, transform 320ms var(--ease-out-soft)"
119
+ },
120
+ children: item.content
121
+ }
122
+ ) })
123
+ }
124
+ )
125
+ ]
126
+ },
127
+ item.key
128
+ );
129
+ })
130
+ }
131
+ )
132
+ }
133
+ );
134
+ }
135
+ export {
136
+ Accordion
137
+ };
138
+ //# sourceMappingURL=Accordion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Accordion.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useCallback, useId, ReactNode } from \"react\";\n\ninterface AccordionItem {\n key: string;\n title: string;\n content: ReactNode;\n defaultOpen?: boolean;\n}\n\ninterface AccordionProps {\n items: AccordionItem[];\n allowMultiple?: boolean;\n className?: string;\n}\n\nexport function Accordion({ items, allowMultiple = false, className }: AccordionProps) {\n const [openKeys, setOpenKeys] = useState<Set<string>>(() => {\n const defaults = new Set<string>();\n for (const item of items) {\n if (item.defaultOpen) {\n defaults.add(item.key);\n }\n }\n return defaults;\n });\n\n const toggle = useCallback(\n (key: string) => {\n setOpenKeys((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n if (allowMultiple) {\n next.add(key);\n } else {\n next.clear();\n next.add(key);\n }\n }\n return next;\n });\n },\n [allowMultiple]\n );\n const uid = useId();\n\n return (\n <div\n className={[\"rounded-[18px] p-1.5\", className].filter(Boolean).join(\" \")}\n style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--panel-shadow)\" }}\n >\n <div\n className=\"bg-panel-face rounded-[13px] overflow-hidden p-1\"\n style={{ boxShadow: \"var(--panel-face-shadow)\" }}\n >\n {items.map((item, index) => {\n const isOpen = openKeys.has(item.key);\n const isLast = index === items.length - 1;\n const headerId = `${uid}-${item.key}-header`;\n const panelId = `${uid}-${item.key}-panel`;\n\n return (\n <div\n key={item.key}\n className={[\n \"relative\",\n // Hairline between rows, inset so it doesn't touch the rounded corners\n !isLast &&\n \"after:absolute after:left-3 after:right-3 after:bottom-0 after:h-px after:bg-line\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <h3>\n <button\n type=\"button\"\n id={headerId}\n aria-expanded={isOpen}\n aria-controls={panelId}\n onClick={() => toggle(item.key)}\n className={[\n \"group/acc w-full flex items-center justify-between gap-3 py-3 px-3 text-left text-[13.5px] font-medium cursor-pointer rounded-[10px]\",\n \"transition-colors duration-150\",\n isOpen ? \"text-ink\" : \"text-ink-label hover:text-ink hover:bg-surface-hover\",\n ].join(\" \")}\n >\n <span>{item.title}</span>\n {/* Plus that turns into a minus: says \"more inside\", not \"navigate\" */}\n <span\n aria-hidden\n className=\"relative shrink-0 w-6 h-6 rounded-[7px] flex items-center justify-center text-ink-muted group-hover/acc:text-ink transition-colors\"\n style={{ background: \"var(--face)\", boxShadow: \"var(--face-shadow)\" }}\n >\n <span className=\"absolute w-2.5 h-[1.5px] rounded-full bg-current\" />\n <span\n className=\"absolute w-2.5 h-[1.5px] rounded-full bg-current\"\n style={{\n transform: isOpen ? \"rotate(0deg)\" : \"rotate(90deg)\",\n transition: \"transform 300ms var(--ease-spring)\",\n }}\n />\n </span>\n </button>\n </h3>\n\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={headerId}\n // Closed panels are removed from the tab order and the a11y tree\n inert={!isOpen}\n style={{\n display: \"grid\",\n gridTemplateRows: isOpen ? \"1fr\" : \"0fr\",\n transition: \"grid-template-rows 320ms var(--ease-out-soft)\",\n }}\n >\n <div className=\"overflow-hidden\">\n <div\n className=\"px-3 pb-4 pt-0.5 text-[13px] text-ink-body leading-relaxed\"\n style={{\n opacity: isOpen ? 1 : 0,\n transform: isOpen ? \"translateY(0)\" : \"translateY(-4px)\",\n transition: \"opacity 240ms ease-out, transform 320ms var(--ease-out-soft)\",\n }}\n >\n {item.content}\n </div>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type AlertVariant = "default" | "success" | "warning" | "error";
4
+ interface AlertProps {
5
+ variant?: AlertVariant;
6
+ title: string;
7
+ description?: string;
8
+ onDismiss?: () => void;
9
+ className?: string;
10
+ }
11
+ declare function Alert({ variant, title, description, onDismiss, className }: AlertProps): react_jsx_runtime.JSX.Element;
12
+
13
+ export { Alert };
package/dist/Alert.js ADDED
@@ -0,0 +1,69 @@
1
+ // ../../components/ui/Alert.tsx
2
+ import { CloseButton } from "./CloseButton.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var variantConfig = {
5
+ default: {
6
+ tile: "var(--face-inverse)",
7
+ icon: /* @__PURE__ */ jsxs("svg", { width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.75", viewBox: "0 0 24 24", children: [
8
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
9
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M12 8v4m0 4h.01" })
10
+ ] })
11
+ },
12
+ success: {
13
+ tile: "var(--tile-success)",
14
+ icon: /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) })
15
+ },
16
+ warning: {
17
+ tile: "var(--tile-warning)",
18
+ icon: /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.75", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" }) })
19
+ },
20
+ error: {
21
+ tile: "var(--tile-error)",
22
+ icon: /* @__PURE__ */ jsx("svg", { width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
23
+ }
24
+ };
25
+ function Alert({ variant = "default", title, description, onDismiss, className = "" }) {
26
+ const { tile, icon } = variantConfig[variant];
27
+ return /* @__PURE__ */ jsx(
28
+ "div",
29
+ {
30
+ role: variant === "error" || variant === "warning" ? "alert" : "status",
31
+ className: ["rounded-[18px] p-1.5", className].join(" "),
32
+ style: { background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" },
33
+ children: /* @__PURE__ */ jsxs(
34
+ "div",
35
+ {
36
+ className: "bg-panel-face rounded-[13px] pl-3 pr-2 py-3 flex items-start gap-3",
37
+ style: { boxShadow: "var(--panel-face-shadow)" },
38
+ children: [
39
+ /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ "aria-hidden": true,
43
+ className: [
44
+ "shrink-0 w-7 h-7 rounded-[8px] flex items-center justify-center",
45
+ // the default tile is the inverse face; status tiles stay white-on-colour
46
+ tile === "var(--face-inverse)" ? "text-ink-inverse" : "text-white"
47
+ ].join(" "),
48
+ style: {
49
+ background: tile,
50
+ boxShadow: "var(--tile-status-shadow)"
51
+ },
52
+ children: icon
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 pt-[5px]", children: [
56
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-ink leading-tight tracking-[-0.01em]", children: title }),
57
+ description && /* @__PURE__ */ jsx("p", { className: "text-[12.5px] text-ink-muted mt-1 leading-relaxed", children: description })
58
+ ] }),
59
+ onDismiss && /* @__PURE__ */ jsx(CloseButton, { size: "sm", label: "Dismiss", onClick: onDismiss })
60
+ ]
61
+ }
62
+ )
63
+ }
64
+ );
65
+ }
66
+ export {
67
+ Alert
68
+ };
69
+ //# sourceMappingURL=Alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Alert.tsx"],"sourcesContent":["import React from \"react\";\nimport { CloseButton } from \"./CloseButton\";\n\ntype AlertVariant = \"default\" | \"success\" | \"warning\" | \"error\";\n\ninterface AlertProps {\n variant?: AlertVariant;\n title: string;\n description?: string;\n onDismiss?: () => void;\n className?: string;\n}\n\n/* Tiles share Toast's lit-from-above gradients, so an alert and the toast\n it came from look like the same object. */\nconst variantConfig: Record<AlertVariant, {\n tile: string;\n icon: React.ReactNode;\n}> = {\n default: {\n tile: \"var(--face-inverse)\",\n icon: (\n <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.75\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path strokeLinecap=\"round\" d=\"M12 8v4m0 4h.01\" />\n </svg>\n ),\n },\n success: {\n tile: \"var(--tile-success)\",\n icon: (\n <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ),\n },\n warning: {\n tile: \"var(--tile-warning)\",\n icon: (\n <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.75\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z\" />\n </svg>\n ),\n },\n error: {\n tile: \"var(--tile-error)\",\n icon: (\n <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n ),\n },\n};\n\nexport function Alert({ variant = \"default\", title, description, onDismiss, className = \"\" }: AlertProps) {\n const { tile, icon } = variantConfig[variant];\n\n return (\n <div\n // Errors and warnings interrupt; everything else waits its turn\n role={variant === \"error\" || variant === \"warning\" ? \"alert\" : \"status\"}\n className={[\"rounded-[18px] p-1.5\", className].join(\" \")}\n style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--panel-shadow)\" }}\n >\n <div\n className=\"bg-panel-face rounded-[13px] pl-3 pr-2 py-3 flex items-start gap-3\"\n style={{ boxShadow: \"var(--panel-face-shadow)\" }}\n >\n <div\n aria-hidden\n className={[\n \"shrink-0 w-7 h-7 rounded-[8px] flex items-center justify-center\",\n // the default tile is the inverse face; status tiles stay white-on-colour\n tile === \"var(--face-inverse)\" ? \"text-ink-inverse\" : \"text-white\",\n ].join(\" \")}\n style={{\n background: tile,\n boxShadow: \"var(--tile-status-shadow)\",\n }}\n >\n {icon}\n </div>\n\n <div className=\"flex-1 min-w-0 pt-[5px]\">\n <p className=\"text-[13px] font-semibold text-ink leading-tight tracking-[-0.01em]\">{title}</p>\n {description && (\n <p className=\"text-[12.5px] text-ink-muted mt-1 leading-relaxed\">{description}</p>\n )}\n </div>\n\n {onDismiss && <CloseButton size=\"sm\" label=\"Dismiss\" onClick={onDismiss} />}\n </div>\n </div>\n );\n}\n"],"mappings":";AACA,SAAS,mBAAmB;AAqBtB,SACE,KADF;AAPN,IAAM,gBAGD;AAAA,EACH,SAAS;AAAA,IACP,MAAM;AAAA,IACN,MACE,qBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,MAAK,QAAO,QAAO,gBAAe,aAAY,QAAO,SAAQ,aACvF;AAAA,0BAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,MAC/B,oBAAC,UAAK,eAAc,SAAQ,GAAE,mBAAkB;AAAA,OAClD;AAAA,EAEJ;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,MACE,oBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,SAAQ,aACpF,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,kBAAiB,GACxE;AAAA,EAEJ;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,MACE,oBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,MAAK,QAAO,QAAO,gBAAe,aAAY,QAAO,SAAQ,aACvF,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,sGAAqG,GAC5J;AAAA,EAEJ;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MACE,oBAAC,SAAI,OAAM,MAAK,QAAO,MAAK,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,SAAQ,aACpF,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,wBAAuB,GAC9E;AAAA,EAEJ;AACF;AAEO,SAAS,MAAM,EAAE,UAAU,WAAW,OAAO,aAAa,WAAW,YAAY,GAAG,GAAe;AACxG,QAAM,EAAE,MAAM,KAAK,IAAI,cAAc,OAAO;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MAEC,MAAM,YAAY,WAAW,YAAY,YAAY,UAAU;AAAA,MAC/D,WAAW,CAAC,wBAAwB,SAAS,EAAE,KAAK,GAAG;AAAA,MACvD,OAAO,EAAE,YAAY,sBAAsB,WAAW,sBAAsB;AAAA,MAE5E;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,2BAA2B;AAAA,UAE/C;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,eAAW;AAAA,gBACX,WAAW;AAAA,kBACP;AAAA;AAAA,kBAEA,SAAS,wBAAwB,qBAAqB;AAAA,gBACxD,EAAE,KAAK,GAAG;AAAA,gBACZ,OAAO;AAAA,kBACL,YAAY;AAAA,kBACZ,WAAW;AAAA,gBACb;AAAA,gBAEC;AAAA;AAAA,YACH;AAAA,YAEA,qBAAC,SAAI,WAAU,2BACb;AAAA,kCAAC,OAAE,WAAU,uEAAuE,iBAAM;AAAA,cACzF,eACC,oBAAC,OAAE,WAAU,qDAAqD,uBAAY;AAAA,eAElF;AAAA,YAEC,aAAa,oBAAC,eAAY,MAAK,MAAK,OAAM,WAAU,SAAS,WAAW;AAAA;AAAA;AAAA,MAC3E;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,12 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface AuthorCardProps {
4
+ name: string;
5
+ bio?: string;
6
+ avatarSrc?: string;
7
+ onFollow?: () => void;
8
+ className?: string;
9
+ }
10
+ declare function AuthorCard({ name, bio, avatarSrc, onFollow, className, }: AuthorCardProps): react_jsx_runtime.JSX.Element;
11
+
12
+ export { AuthorCard };
@@ -0,0 +1,57 @@
1
+ // ../../components/ui/AuthorCard.tsx
2
+ import { Avatar } from "./Avatar.js";
3
+ import { Button } from "./Button.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ function AuthorCard({
6
+ name,
7
+ bio,
8
+ avatarSrc,
9
+ onFollow,
10
+ className = ""
11
+ }) {
12
+ return /* @__PURE__ */ jsx(
13
+ "div",
14
+ {
15
+ className: [
16
+ "rim relative rounded-[22px] p-1.5",
17
+ className
18
+ ].filter(Boolean).join(" "),
19
+ style: { background: "var(--panel-bezel)", boxShadow: "var(--card-shadow)" },
20
+ children: /* @__PURE__ */ jsxs("div", { className: "relative bg-panel-face rounded-[17px] overflow-hidden", style: { boxShadow: "var(--panel-face-shadow)" }, children: [
21
+ /* @__PURE__ */ jsx(
22
+ "div",
23
+ {
24
+ "aria-hidden": true,
25
+ className: "h-20 relative overflow-hidden",
26
+ style: {
27
+ background: "var(--cover)",
28
+ boxShadow: "var(--cover-shadow)"
29
+ }
30
+ }
31
+ ),
32
+ /* @__PURE__ */ jsxs("div", { className: "px-6 pb-6", children: [
33
+ /* @__PURE__ */ jsx("div", { className: "-mt-8 mb-4", children: /* @__PURE__ */ jsx(
34
+ Avatar,
35
+ {
36
+ src: avatarSrc,
37
+ name,
38
+ size: "lg",
39
+ className: "ring-[3.5px] ring-panel-face shadow-[var(--avatar-lift)]"
40
+ }
41
+ ) }),
42
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
43
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
44
+ /* @__PURE__ */ jsx("p", { className: "text-[15px] font-semibold text-ink leading-snug truncate tracking-[-0.01em]", children: name }),
45
+ bio && /* @__PURE__ */ jsx("p", { className: "text-[13px] text-ink-muted leading-relaxed mt-1 line-clamp-2", children: bio })
46
+ ] }),
47
+ onFollow && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: onFollow, className: "shrink-0", children: "follow" })
48
+ ] })
49
+ ] })
50
+ ] })
51
+ }
52
+ );
53
+ }
54
+ export {
55
+ AuthorCard
56
+ };
57
+ //# sourceMappingURL=AuthorCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/AuthorCard.tsx"],"sourcesContent":["import React from \"react\";\nimport { Avatar } from \"./Avatar\";\nimport { Button } from \"./Button\";\n\ninterface AuthorCardProps {\n name: string;\n bio?: string;\n avatarSrc?: string;\n onFollow?: () => void;\n className?: string;\n}\n\nexport function AuthorCard({\n name,\n bio,\n avatarSrc,\n onFollow,\n className = \"\",\n}: AuthorCardProps) {\n return (\n <div\n className={[\n \"rim relative rounded-[22px] p-1.5\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ background: \"var(--panel-bezel)\", boxShadow: \"var(--card-shadow)\" }}\n >\n <div className=\"relative bg-panel-face rounded-[17px] overflow-hidden\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n {/* Cover: a sunken well, so the avatar visibly sits on top of it */}\n <div\n aria-hidden\n className=\"h-20 relative overflow-hidden\"\n style={{\n background:\n \"var(--cover)\",\n boxShadow: \"var(--cover-shadow)\",\n }}\n />\n <div className=\"px-6 pb-6\">\n <div className=\"-mt-8 mb-4\">\n <Avatar\n src={avatarSrc}\n name={name}\n size=\"lg\"\n className=\"ring-[3.5px] ring-panel-face shadow-[var(--avatar-lift)]\"\n />\n </div>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-[15px] font-semibold text-ink leading-snug truncate tracking-[-0.01em]\">{name}</p>\n {bio && (\n <p className=\"text-[13px] text-ink-muted leading-relaxed mt-1 line-clamp-2\">{bio}</p>\n )}\n </div>\n {onFollow && (\n <Button variant=\"primary\" size=\"sm\" onClick={onFollow} className=\"shrink-0\">\n follow\n </Button>\n )}\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";AACA,SAAS,cAAc;AACvB,SAAS,cAAc;AA6Bf,cAmBI,YAnBJ;AAnBD,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACX,OAAO,EAAE,YAAY,sBAAsB,WAAW,qBAAqB;AAAA,MAE3E,+BAAC,SAAI,WAAU,yDAAwD,OAAO,EAAE,WAAW,2BAA2B,GAEpH;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,YACV,OAAO;AAAA,cACL,YACE;AAAA,cACF,WAAW;AAAA,YACb;AAAA;AAAA,QACF;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAI,WAAU,cACb;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL;AAAA,cACA,MAAK;AAAA,cACL,WAAU;AAAA;AAAA,UACZ,GACF;AAAA,UACA,qBAAC,SAAI,WAAU,0CACb;AAAA,iCAAC,SAAI,WAAU,kBACb;AAAA,kCAAC,OAAE,WAAU,+EAA+E,gBAAK;AAAA,cAChG,OACC,oBAAC,OAAE,WAAU,gEAAgE,eAAI;AAAA,eAErF;AAAA,YACC,YACC,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,UAAU,WAAU,YAAW,oBAE5E;AAAA,aAEJ;AAAA,WACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,21 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ type AvatarSize = "sm" | "md" | "lg";
5
+ interface AvatarProps {
6
+ src?: string;
7
+ alt?: string;
8
+ name?: string;
9
+ size?: AvatarSize;
10
+ className?: string;
11
+ }
12
+ declare function Avatar({ src, alt, name, size, className, }: AvatarProps): react_jsx_runtime.JSX.Element;
13
+ interface AvatarGroupProps {
14
+ children: React.ReactNode;
15
+ max?: number;
16
+ size?: AvatarSize;
17
+ className?: string;
18
+ }
19
+ declare function AvatarGroup({ children, max, size, className, }: AvatarGroupProps): react_jsx_runtime.JSX.Element;
20
+
21
+ export { Avatar, AvatarGroup };
package/dist/Avatar.js ADDED
@@ -0,0 +1,96 @@
1
+ // ../../components/ui/Avatar.tsx
2
+ import React from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var sizeStyles = {
5
+ sm: "w-7 h-7 text-[10px]",
6
+ md: "w-9 h-9 text-[12px]",
7
+ lg: "w-12 h-12 text-[14px]"
8
+ };
9
+ function getInitials(name) {
10
+ return name.split(" ").map((w) => w[0]).slice(0, 2).join("").toLowerCase();
11
+ }
12
+ function Avatar({
13
+ src,
14
+ alt = "",
15
+ name,
16
+ size = "md",
17
+ className = ""
18
+ }) {
19
+ const initials = name ? getInitials(name) : "?";
20
+ return /* @__PURE__ */ jsxs(
21
+ "div",
22
+ {
23
+ className: [
24
+ "relative shrink-0 rounded-full overflow-hidden flex items-center justify-center font-semibold text-ink-body select-none",
25
+ sizeStyles[size],
26
+ className
27
+ ].filter(Boolean).join(" "),
28
+ style: { background: "var(--face)" },
29
+ children: [
30
+ src ? (
31
+ // eslint-disable-next-line @next/next/no-img-element
32
+ /* @__PURE__ */ jsx("img", { src, alt: alt || name || "", loading: "lazy", className: "w-full h-full object-cover" })
33
+ ) : /* @__PURE__ */ jsx("span", { role: "img", "aria-label": name ?? "Unknown", children: initials }),
34
+ /* @__PURE__ */ jsx(
35
+ "span",
36
+ {
37
+ "aria-hidden": true,
38
+ className: "pointer-events-none absolute inset-0 rounded-full",
39
+ style: {
40
+ boxShadow: "var(--avatar-rim)"
41
+ }
42
+ }
43
+ )
44
+ ]
45
+ }
46
+ );
47
+ }
48
+ var overlapStyles = {
49
+ sm: "-ml-2",
50
+ md: "-ml-2.5",
51
+ lg: "-ml-3"
52
+ };
53
+ function AvatarGroup({
54
+ children,
55
+ max = 5,
56
+ size = "md",
57
+ className = ""
58
+ }) {
59
+ const items = React.Children.toArray(children);
60
+ const visible = items.slice(0, max);
61
+ const overflow = items.length - max;
62
+ return /* @__PURE__ */ jsxs("div", { className: ["flex items-center", className].join(" "), children: [
63
+ visible.map((child, i) => /* @__PURE__ */ jsx(
64
+ "div",
65
+ {
66
+ className: [
67
+ "ring-2 ring-panel-face rounded-full",
68
+ i > 0 ? overlapStyles[size] : ""
69
+ ].join(" "),
70
+ children: child
71
+ },
72
+ i
73
+ )),
74
+ overflow > 0 && /* @__PURE__ */ jsxs(
75
+ "div",
76
+ {
77
+ className: [
78
+ "ring-2 ring-panel-face rounded-full flex items-center justify-center font-semibold text-ink-body select-none",
79
+ overlapStyles[size],
80
+ sizeStyles[size]
81
+ ].join(" "),
82
+ style: { background: "var(--well)", boxShadow: "var(--well-shadow)" },
83
+ "aria-label": `${overflow} more`,
84
+ children: [
85
+ "+",
86
+ overflow
87
+ ]
88
+ }
89
+ )
90
+ ] });
91
+ }
92
+ export {
93
+ Avatar,
94
+ AvatarGroup
95
+ };
96
+ //# sourceMappingURL=Avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Avatar.tsx"],"sourcesContent":["import React from \"react\";\n\ntype AvatarSize = \"sm\" | \"md\" | \"lg\";\n\ninterface AvatarProps {\n src?: string;\n alt?: string;\n name?: string;\n size?: AvatarSize;\n className?: string;\n}\n\nconst sizeStyles: Record<AvatarSize, string> = {\n sm: \"w-7 h-7 text-[10px]\",\n md: \"w-9 h-9 text-[12px]\",\n lg: \"w-12 h-12 text-[14px]\",\n};\n\nfunction getInitials(name: string): string {\n return name\n .split(\" \")\n .map((w) => w[0])\n .slice(0, 2)\n .join(\"\")\n .toLowerCase();\n}\n\nexport function Avatar({\n src,\n alt = \"\",\n name,\n size = \"md\",\n className = \"\",\n}: AvatarProps) {\n const initials = name ? getInitials(name) : \"?\";\n\n return (\n <div\n className={[\n \"relative shrink-0 rounded-full overflow-hidden flex items-center justify-center font-semibold text-ink-body select-none\",\n sizeStyles[size],\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ background: \"var(--face)\" }}\n >\n {src ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt || name || \"\"} loading=\"lazy\" className=\"w-full h-full object-cover\" />\n ) : (\n <span role=\"img\" aria-label={name ?? \"Unknown\"}>{initials}</span>\n )}\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 rounded-full\"\n style={{\n boxShadow: \"var(--avatar-rim)\",\n }}\n />\n </div>\n );\n}\n\ninterface AvatarGroupProps {\n children: React.ReactNode;\n max?: number;\n size?: AvatarSize;\n className?: string;\n}\n\nconst overlapStyles: Record<AvatarSize, string> = {\n sm: \"-ml-2\",\n md: \"-ml-2.5\",\n lg: \"-ml-3\",\n};\n\nexport function AvatarGroup({\n children,\n max = 5,\n size = \"md\",\n className = \"\",\n}: AvatarGroupProps) {\n const items = React.Children.toArray(children);\n const visible = items.slice(0, max);\n const overflow = items.length - max;\n\n return (\n <div className={[\"flex items-center\", className].join(\" \")}>\n {visible.map((child, i) => (\n <div\n key={i}\n className={[\n \"ring-2 ring-panel-face rounded-full\",\n i > 0 ? overlapStyles[size] : \"\",\n ].join(\" \")}\n >\n {child}\n </div>\n ))}\n {overflow > 0 && (\n <div\n className={[\n \"ring-2 ring-panel-face rounded-full flex items-center justify-center font-semibold text-ink-body select-none\",\n overlapStyles[size],\n sizeStyles[size],\n ].join(\" \")}\n style={{ background: \"var(--well)\", boxShadow: \"var(--well-shadow)\" }}\n aria-label={`${overflow} more`}\n >\n +{overflow}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";AAAA,OAAO,WAAW;AAqCd,SAYI,KAZJ;AAzBJ,IAAM,aAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,SAAS,YAAY,MAAsB;AACzC,SAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,EACP,YAAY;AACjB;AAEO,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AACd,GAAgB;AACd,QAAM,WAAW,OAAO,YAAY,IAAI,IAAI;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,WAAW,IAAI;AAAA,QACf;AAAA,MACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACX,OAAO,EAAE,YAAY,cAAc;AAAA,MAElC;AAAA;AAAA;AAAA,UAEC,oBAAC,SAAI,KAAU,KAAK,OAAO,QAAQ,IAAI,SAAQ,QAAO,WAAU,8BAA6B;AAAA,YAE7F,oBAAC,UAAK,MAAK,OAAM,cAAY,QAAQ,WAAY,oBAAS;AAAA,QAE5D;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,YACV,OAAO;AAAA,cACL,WAAW;AAAA,YACb;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;AASA,IAAM,gBAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,EACN,OAAO;AAAA,EACP,YAAY;AACd,GAAqB;AACnB,QAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ;AAC7C,QAAM,UAAU,MAAM,MAAM,GAAG,GAAG;AAClC,QAAM,WAAW,MAAM,SAAS;AAEhC,SACE,qBAAC,SAAI,WAAW,CAAC,qBAAqB,SAAS,EAAE,KAAK,GAAG,GACtD;AAAA,YAAQ,IAAI,CAAC,OAAO,MACnB;AAAA,MAAC;AAAA;AAAA,QAEC,WAAW;AAAA,UACT;AAAA,UACA,IAAI,IAAI,cAAc,IAAI,IAAI;AAAA,QAChC,EAAE,KAAK,GAAG;AAAA,QAET;AAAA;AAAA,MANI;AAAA,IAOP,CACD;AAAA,IACA,WAAW,KACV;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,cAAc,IAAI;AAAA,UAClB,WAAW,IAAI;AAAA,QACjB,EAAE,KAAK,GAAG;AAAA,QACV,OAAO,EAAE,YAAY,eAAe,WAAW,qBAAqB;AAAA,QACpE,cAAY,GAAG,QAAQ;AAAA,QACxB;AAAA;AAAA,UACG;AAAA;AAAA;AAAA,IACJ;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,12 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ type BadgeVariant = "default" | "muted" | "accent";
5
+ interface BadgeProps {
6
+ variant?: BadgeVariant;
7
+ children: React.ReactNode;
8
+ className?: string;
9
+ }
10
+ declare function Badge({ variant, children, className, }: BadgeProps): react_jsx_runtime.JSX.Element;
11
+
12
+ export { Badge };