@c9up/nebula 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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -0
  3. package/dist/adapters/css.d.ts +33 -0
  4. package/dist/adapters/css.js +67 -0
  5. package/dist/adapters/index.d.ts +16 -0
  6. package/dist/adapters/index.js +27 -0
  7. package/dist/adapters/tailwind.d.ts +25 -0
  8. package/dist/adapters/tailwind.js +122 -0
  9. package/dist/adapters/types.d.ts +49 -0
  10. package/dist/adapters/types.js +21 -0
  11. package/dist/adapters/unocss.d.ts +17 -0
  12. package/dist/adapters/unocss.js +107 -0
  13. package/dist/atoms/AspectRatio.d.ts +17 -0
  14. package/dist/atoms/AspectRatio.js +19 -0
  15. package/dist/atoms/Avatar.d.ts +23 -0
  16. package/dist/atoms/Avatar.js +46 -0
  17. package/dist/atoms/Badge.d.ts +25 -0
  18. package/dist/atoms/Badge.js +28 -0
  19. package/dist/atoms/Button.d.ts +58 -0
  20. package/dist/atoms/Button.js +67 -0
  21. package/dist/atoms/Checkbox.d.ts +41 -0
  22. package/dist/atoms/Checkbox.js +47 -0
  23. package/dist/atoms/Input.d.ts +36 -0
  24. package/dist/atoms/Input.js +50 -0
  25. package/dist/atoms/Kbd.d.ts +14 -0
  26. package/dist/atoms/Kbd.js +17 -0
  27. package/dist/atoms/Label.d.ts +24 -0
  28. package/dist/atoms/Label.js +26 -0
  29. package/dist/atoms/Marker.d.ts +42 -0
  30. package/dist/atoms/Marker.js +55 -0
  31. package/dist/atoms/NativeSelect.d.ts +43 -0
  32. package/dist/atoms/NativeSelect.js +81 -0
  33. package/dist/atoms/Progress.d.ts +21 -0
  34. package/dist/atoms/Progress.js +48 -0
  35. package/dist/atoms/ScrollArea.d.ts +28 -0
  36. package/dist/atoms/ScrollArea.js +37 -0
  37. package/dist/atoms/Separator.d.ts +16 -0
  38. package/dist/atoms/Separator.js +21 -0
  39. package/dist/atoms/Skeleton.d.ts +13 -0
  40. package/dist/atoms/Skeleton.js +18 -0
  41. package/dist/atoms/Slider.d.ts +30 -0
  42. package/dist/atoms/Slider.js +40 -0
  43. package/dist/atoms/Spinner.d.ts +17 -0
  44. package/dist/atoms/Spinner.js +20 -0
  45. package/dist/atoms/Switch.d.ts +24 -0
  46. package/dist/atoms/Switch.js +41 -0
  47. package/dist/atoms/Textarea.d.ts +28 -0
  48. package/dist/atoms/Textarea.js +36 -0
  49. package/dist/atoms/Toggle.d.ts +38 -0
  50. package/dist/atoms/Toggle.js +45 -0
  51. package/dist/atoms/index.d.ts +32 -0
  52. package/dist/atoms/index.js +32 -0
  53. package/dist/cli/add.d.ts +44 -0
  54. package/dist/cli/add.js +99 -0
  55. package/dist/cli/index.d.ts +10 -0
  56. package/dist/cli/index.js +10 -0
  57. package/dist/cli/registry.d.ts +31 -0
  58. package/dist/cli/registry.js +83 -0
  59. package/dist/config.d.ts +81 -0
  60. package/dist/config.js +43 -0
  61. package/dist/configure.d.ts +34 -0
  62. package/dist/configure.js +85 -0
  63. package/dist/index.d.ts +41 -0
  64. package/dist/index.js +41 -0
  65. package/dist/lib/children.d.ts +28 -0
  66. package/dist/lib/children.js +27 -0
  67. package/dist/lib/cn.d.ts +13 -0
  68. package/dist/lib/cn.js +13 -0
  69. package/dist/lib/cva.d.ts +83 -0
  70. package/dist/lib/cva.js +178 -0
  71. package/dist/lib/icons.d.ts +44 -0
  72. package/dist/lib/icons.js +79 -0
  73. package/dist/lib/id.d.ts +31 -0
  74. package/dist/lib/id.js +41 -0
  75. package/dist/lib/index.d.ts +12 -0
  76. package/dist/lib/index.js +12 -0
  77. package/dist/lib/motion.d.ts +31 -0
  78. package/dist/lib/motion.js +39 -0
  79. package/dist/lib/props.d.ts +48 -0
  80. package/dist/lib/props.js +59 -0
  81. package/dist/lib/styled.d.ts +31 -0
  82. package/dist/lib/styled.js +39 -0
  83. package/dist/molecules/Accordion.d.ts +35 -0
  84. package/dist/molecules/Accordion.js +117 -0
  85. package/dist/molecules/Alert.d.ts +30 -0
  86. package/dist/molecules/Alert.js +35 -0
  87. package/dist/molecules/Attachment.d.ts +39 -0
  88. package/dist/molecules/Attachment.js +103 -0
  89. package/dist/molecules/Breadcrumb.d.ts +29 -0
  90. package/dist/molecules/Breadcrumb.js +73 -0
  91. package/dist/molecules/Bubble.d.ts +56 -0
  92. package/dist/molecules/Bubble.js +92 -0
  93. package/dist/molecules/ButtonGroup.d.ts +23 -0
  94. package/dist/molecules/ButtonGroup.js +30 -0
  95. package/dist/molecules/Card.d.ts +17 -0
  96. package/dist/molecules/Card.js +17 -0
  97. package/dist/molecules/Collapsible.d.ts +40 -0
  98. package/dist/molecules/Collapsible.js +71 -0
  99. package/dist/molecules/Empty.d.ts +16 -0
  100. package/dist/molecules/Empty.js +16 -0
  101. package/dist/molecules/Field.d.ts +54 -0
  102. package/dist/molecules/Field.js +82 -0
  103. package/dist/molecules/InputGroup.d.ts +33 -0
  104. package/dist/molecules/InputGroup.js +35 -0
  105. package/dist/molecules/InputOTP.d.ts +33 -0
  106. package/dist/molecules/InputOTP.js +140 -0
  107. package/dist/molecules/Item.d.ts +41 -0
  108. package/dist/molecules/Item.js +48 -0
  109. package/dist/molecules/Message.d.ts +43 -0
  110. package/dist/molecules/Message.js +82 -0
  111. package/dist/molecules/Pagination.d.ts +41 -0
  112. package/dist/molecules/Pagination.js +144 -0
  113. package/dist/molecules/RadioGroup.d.ts +32 -0
  114. package/dist/molecules/RadioGroup.js +65 -0
  115. package/dist/molecules/Resizable.d.ts +37 -0
  116. package/dist/molecules/Resizable.js +136 -0
  117. package/dist/molecules/Table.d.ts +37 -0
  118. package/dist/molecules/Table.js +78 -0
  119. package/dist/molecules/Tabs.d.ts +37 -0
  120. package/dist/molecules/Tabs.js +109 -0
  121. package/dist/molecules/ToggleGroup.d.ts +37 -0
  122. package/dist/molecules/ToggleGroup.js +68 -0
  123. package/dist/molecules/Typography.d.ts +26 -0
  124. package/dist/molecules/Typography.js +67 -0
  125. package/dist/molecules/index.d.ts +37 -0
  126. package/dist/molecules/index.js +37 -0
  127. package/dist/organisms/AlertDialog.d.ts +40 -0
  128. package/dist/organisms/AlertDialog.js +104 -0
  129. package/dist/organisms/Calendar.d.ts +96 -0
  130. package/dist/organisms/Calendar.js +353 -0
  131. package/dist/organisms/Carousel.d.ts +29 -0
  132. package/dist/organisms/Carousel.js +128 -0
  133. package/dist/organisms/Chart.d.ts +55 -0
  134. package/dist/organisms/Chart.js +189 -0
  135. package/dist/organisms/Combobox.d.ts +41 -0
  136. package/dist/organisms/Combobox.js +102 -0
  137. package/dist/organisms/Command.d.ts +46 -0
  138. package/dist/organisms/Command.js +171 -0
  139. package/dist/organisms/CommandDialog.d.ts +42 -0
  140. package/dist/organisms/CommandDialog.js +116 -0
  141. package/dist/organisms/ContextMenu.d.ts +29 -0
  142. package/dist/organisms/ContextMenu.js +102 -0
  143. package/dist/organisms/DataTable.d.ts +50 -0
  144. package/dist/organisms/DataTable.js +257 -0
  145. package/dist/organisms/DatePicker.d.ts +34 -0
  146. package/dist/organisms/DatePicker.js +115 -0
  147. package/dist/organisms/DateRangePicker.d.ts +34 -0
  148. package/dist/organisms/DateRangePicker.js +122 -0
  149. package/dist/organisms/Dialog.d.ts +50 -0
  150. package/dist/organisms/Dialog.js +116 -0
  151. package/dist/organisms/Drawer.d.ts +40 -0
  152. package/dist/organisms/Drawer.js +131 -0
  153. package/dist/organisms/DropdownMenu.d.ts +30 -0
  154. package/dist/organisms/DropdownMenu.js +91 -0
  155. package/dist/organisms/Form.d.ts +114 -0
  156. package/dist/organisms/Form.js +151 -0
  157. package/dist/organisms/HoverCard.d.ts +30 -0
  158. package/dist/organisms/HoverCard.js +68 -0
  159. package/dist/organisms/Menubar.d.ts +28 -0
  160. package/dist/organisms/Menubar.js +147 -0
  161. package/dist/organisms/MessageScroller.d.ts +36 -0
  162. package/dist/organisms/MessageScroller.js +124 -0
  163. package/dist/organisms/NavigationMenu.d.ts +41 -0
  164. package/dist/organisms/NavigationMenu.js +139 -0
  165. package/dist/organisms/Popover.d.ts +33 -0
  166. package/dist/organisms/Popover.js +58 -0
  167. package/dist/organisms/Questionnaire.d.ts +74 -0
  168. package/dist/organisms/Questionnaire.js +235 -0
  169. package/dist/organisms/Select.d.ts +47 -0
  170. package/dist/organisms/Select.js +250 -0
  171. package/dist/organisms/Sheet.d.ts +34 -0
  172. package/dist/organisms/Sheet.js +107 -0
  173. package/dist/organisms/Sidebar.d.ts +100 -0
  174. package/dist/organisms/Sidebar.js +228 -0
  175. package/dist/organisms/Toaster.d.ts +67 -0
  176. package/dist/organisms/Toaster.js +189 -0
  177. package/dist/organisms/Tooltip.d.ts +39 -0
  178. package/dist/organisms/Tooltip.js +94 -0
  179. package/dist/organisms/index.d.ts +40 -0
  180. package/dist/organisms/index.js +40 -0
  181. package/dist/primitives/controllable.d.ts +38 -0
  182. package/dist/primitives/controllable.js +43 -0
  183. package/dist/primitives/dismissable.d.ts +53 -0
  184. package/dist/primitives/dismissable.js +158 -0
  185. package/dist/primitives/floating.d.ts +116 -0
  186. package/dist/primitives/floating.js +289 -0
  187. package/dist/primitives/floatingSurface.d.ts +56 -0
  188. package/dist/primitives/floatingSurface.js +142 -0
  189. package/dist/primitives/focusTrap.d.ts +47 -0
  190. package/dist/primitives/focusTrap.js +101 -0
  191. package/dist/primitives/focusable.d.ts +53 -0
  192. package/dist/primitives/focusable.js +109 -0
  193. package/dist/primitives/index.d.ts +27 -0
  194. package/dist/primitives/index.js +27 -0
  195. package/dist/primitives/menu.d.ts +102 -0
  196. package/dist/primitives/menu.js +356 -0
  197. package/dist/primitives/modalSurface.d.ts +54 -0
  198. package/dist/primitives/modalSurface.js +146 -0
  199. package/dist/primitives/portal.d.ts +34 -0
  200. package/dist/primitives/portal.js +42 -0
  201. package/dist/primitives/presence.d.ts +45 -0
  202. package/dist/primitives/presence.js +152 -0
  203. package/dist/primitives/rovingFocus.d.ts +47 -0
  204. package/dist/primitives/rovingFocus.js +176 -0
  205. package/dist/primitives/scrollLock.d.ts +21 -0
  206. package/dist/primitives/scrollLock.js +75 -0
  207. package/dist/primitives/typeahead.d.ts +39 -0
  208. package/dist/primitives/typeahead.js +123 -0
  209. package/dist/templates/AppShell.d.ts +30 -0
  210. package/dist/templates/AppShell.js +42 -0
  211. package/dist/templates/AuthLayout.d.ts +23 -0
  212. package/dist/templates/AuthLayout.js +42 -0
  213. package/dist/templates/SettingsLayout.d.ts +27 -0
  214. package/dist/templates/SettingsLayout.js +42 -0
  215. package/dist/templates/index.d.ts +16 -0
  216. package/dist/templates/index.js +16 -0
  217. package/nebula.css +2 -0
  218. package/package.json +92 -0
  219. package/registry.json +1107 -0
  220. package/src/adapters/css.ts +75 -0
  221. package/src/adapters/index.ts +40 -0
  222. package/src/adapters/tailwind.ts +134 -0
  223. package/src/adapters/types.ts +53 -0
  224. package/src/adapters/unocss.ts +116 -0
  225. package/src/atoms/AspectRatio.ts +28 -0
  226. package/src/atoms/Avatar.ts +67 -0
  227. package/src/atoms/Badge.ts +46 -0
  228. package/src/atoms/Button.ts +96 -0
  229. package/src/atoms/Checkbox.ts +76 -0
  230. package/src/atoms/Input.ts +76 -0
  231. package/src/atoms/Kbd.ts +28 -0
  232. package/src/atoms/Label.ts +38 -0
  233. package/src/atoms/Marker.ts +79 -0
  234. package/src/atoms/NativeSelect.ts +115 -0
  235. package/src/atoms/Progress.ts +64 -0
  236. package/src/atoms/ScrollArea.ts +53 -0
  237. package/src/atoms/Separator.ts +36 -0
  238. package/src/atoms/Skeleton.ts +24 -0
  239. package/src/atoms/Slider.ts +65 -0
  240. package/src/atoms/Spinner.ts +28 -0
  241. package/src/atoms/Switch.ts +56 -0
  242. package/src/atoms/Textarea.ts +58 -0
  243. package/src/atoms/Toggle.ts +67 -0
  244. package/src/atoms/index.ts +62 -0
  245. package/src/cli/add.ts +126 -0
  246. package/src/cli/index.ts +22 -0
  247. package/src/cli/registry.ts +116 -0
  248. package/src/config.ts +102 -0
  249. package/src/configure.ts +127 -0
  250. package/src/index.ts +53 -0
  251. package/src/lib/children.ts +43 -0
  252. package/src/lib/cn.ts +14 -0
  253. package/src/lib/cva.ts +249 -0
  254. package/src/lib/icons.ts +138 -0
  255. package/src/lib/id.ts +44 -0
  256. package/src/lib/index.ts +21 -0
  257. package/src/lib/motion.ts +48 -0
  258. package/src/lib/props.ts +83 -0
  259. package/src/lib/styled.ts +60 -0
  260. package/src/molecules/Accordion.ts +161 -0
  261. package/src/molecules/Alert.ts +58 -0
  262. package/src/molecules/Attachment.ts +132 -0
  263. package/src/molecules/Breadcrumb.ts +107 -0
  264. package/src/molecules/Bubble.ts +134 -0
  265. package/src/molecules/ButtonGroup.ts +55 -0
  266. package/src/molecules/Card.ts +42 -0
  267. package/src/molecules/Collapsible.ts +89 -0
  268. package/src/molecules/Empty.ts +43 -0
  269. package/src/molecules/Field.ts +131 -0
  270. package/src/molecules/InputGroup.ts +62 -0
  271. package/src/molecules/InputOTP.ts +166 -0
  272. package/src/molecules/Item.ts +89 -0
  273. package/src/molecules/Message.ts +127 -0
  274. package/src/molecules/Pagination.ts +197 -0
  275. package/src/molecules/RadioGroup.ts +104 -0
  276. package/src/molecules/Resizable.ts +166 -0
  277. package/src/molecules/Table.ts +118 -0
  278. package/src/molecules/Tabs.ts +148 -0
  279. package/src/molecules/ToggleGroup.ts +109 -0
  280. package/src/molecules/Typography.ts +94 -0
  281. package/src/molecules/index.ts +144 -0
  282. package/src/organisms/AlertDialog.ts +133 -0
  283. package/src/organisms/Calendar.ts +470 -0
  284. package/src/organisms/Carousel.ts +161 -0
  285. package/src/organisms/Chart.ts +257 -0
  286. package/src/organisms/Combobox.ts +140 -0
  287. package/src/organisms/Command.ts +211 -0
  288. package/src/organisms/CommandDialog.ts +149 -0
  289. package/src/organisms/ContextMenu.ts +125 -0
  290. package/src/organisms/DataTable.ts +328 -0
  291. package/src/organisms/DatePicker.ts +141 -0
  292. package/src/organisms/DateRangePicker.ts +149 -0
  293. package/src/organisms/Dialog.ts +162 -0
  294. package/src/organisms/Drawer.ts +167 -0
  295. package/src/organisms/DropdownMenu.ts +112 -0
  296. package/src/organisms/Form.ts +217 -0
  297. package/src/organisms/HoverCard.ts +92 -0
  298. package/src/organisms/Menubar.ts +167 -0
  299. package/src/organisms/MessageScroller.ts +145 -0
  300. package/src/organisms/NavigationMenu.ts +184 -0
  301. package/src/organisms/Popover.ts +84 -0
  302. package/src/organisms/Questionnaire.ts +322 -0
  303. package/src/organisms/Select.ts +286 -0
  304. package/src/organisms/Sheet.ts +144 -0
  305. package/src/organisms/Sidebar.ts +336 -0
  306. package/src/organisms/Toaster.ts +246 -0
  307. package/src/organisms/Tooltip.ts +124 -0
  308. package/src/organisms/index.ts +134 -0
  309. package/src/primitives/controllable.ts +64 -0
  310. package/src/primitives/dismissable.ts +195 -0
  311. package/src/primitives/floating.ts +453 -0
  312. package/src/primitives/floatingSurface.ts +209 -0
  313. package/src/primitives/focusTrap.ts +132 -0
  314. package/src/primitives/focusable.ts +108 -0
  315. package/src/primitives/index.ts +88 -0
  316. package/src/primitives/menu.ts +491 -0
  317. package/src/primitives/modalSurface.ts +199 -0
  318. package/src/primitives/portal.ts +61 -0
  319. package/src/primitives/presence.ts +185 -0
  320. package/src/primitives/rovingFocus.ts +212 -0
  321. package/src/primitives/scrollLock.ts +79 -0
  322. package/src/primitives/typeahead.ts +156 -0
  323. package/src/templates/AppShell.ts +56 -0
  324. package/src/templates/AuthLayout.ts +66 -0
  325. package/src/templates/SettingsLayout.ts +63 -0
  326. package/src/templates/index.ts +21 -0
  327. package/theme.css +151 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Pagination — page links for a long list.
3
+ *
4
+ * The interesting part is `pageWindow`, which decides which page numbers to
5
+ * show. Rendering all of them breaks at a few hundred pages; the usual fix is
6
+ * a fixed window around the current page, which then jitters in width as you
7
+ * approach either end — the control visibly reflows while you click through
8
+ * it. `pageWindow` keeps the count constant instead, so the row stays the same
9
+ * size from page 1 to page 500.
10
+ *
11
+ * `aria-current="page"` marks the current page. Without it the only signal is
12
+ * the highlight, which a screen-reader user does not get.
13
+ */
14
+ import { component, html } from "@c9up/aurora";
15
+ import { buttonVariants } from "../atoms/Button.js";
16
+ import { cn } from "../lib/cn.js";
17
+ import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon, } from "../lib/icons.js";
18
+ import { read } from "../lib/props.js";
19
+ /**
20
+ * Which page numbers to show, at a fixed total width.
21
+ *
22
+ * Always the first page, the last page, a run around the current one, and one
23
+ * or two gaps — adding up to exactly `size` slots wherever the current page
24
+ * sits. The constant width is the whole reason this is computed rather than a
25
+ * range being sliced: a window that simply narrows near the ends makes the
26
+ * control reflow as you click through it, and the buttons move under the
27
+ * pointer.
28
+ *
29
+ * Near an end there is only one gap, so the run is one slot wider to make up
30
+ * for it. That is the case a naive implementation gets wrong.
31
+ */
32
+ export function pageWindow(page, pageCount, size = 7) {
33
+ if (pageCount <= size)
34
+ return range(1, pageCount);
35
+ // Below five slots there is no room for first, last, a gap and a run, so
36
+ // the shape this function promises cannot be built.
37
+ const slots = Math.max(size, 5);
38
+ const current = Math.min(Math.max(page, 1), pageCount);
39
+ // Near an end the run absorbs the first (or last) page and there is a
40
+ // single gap, so it holds `slots - 2` entries. In the middle the first and
41
+ // last pages are separate and there are two gaps, leaving `slots - 4`.
42
+ const runNearEnd = slots - 2;
43
+ const runInMiddle = slots - 4;
44
+ if (current <= runNearEnd - 1) {
45
+ return [...range(1, runNearEnd), null, pageCount];
46
+ }
47
+ if (current >= pageCount - runNearEnd + 2) {
48
+ return [1, null, ...range(pageCount - runNearEnd + 1, pageCount)];
49
+ }
50
+ const before = Math.floor((runInMiddle - 1) / 2);
51
+ const start = current - before;
52
+ return [1, null, ...range(start, start + runInMiddle - 1), null, pageCount];
53
+ }
54
+ function range(from, to) {
55
+ const out = [];
56
+ for (let value = from; value <= to; value += 1)
57
+ out.push(value);
58
+ return out;
59
+ }
60
+ export const Pagination = component((props) => {
61
+ const page = () => read(props.page) ?? 1;
62
+ const pageCount = () => Math.max(1, read(props.pageCount) ?? 1);
63
+ const go = (target) => (event) => {
64
+ if (props.onPageChange === undefined)
65
+ return;
66
+ // Only intercept when the caller wants callback-driven paging. With an
67
+ // `href` and no handler the link must navigate normally.
68
+ event.preventDefault();
69
+ props.onPageChange(Math.min(Math.max(target, 1), pageCount()));
70
+ };
71
+ return html `<nav
72
+ data-slot="pagination"
73
+ role="navigation"
74
+ aria-label="pagination"
75
+ class="${() => cn("mx-auto flex w-full justify-center", read(props.class))}"
76
+ >
77
+ <ul data-slot="pagination-content" class="flex flex-row items-center gap-1">
78
+ <li>
79
+ ${link({
80
+ label: html `${ChevronLeftIcon({ class: "size-4" })}<span class="hidden sm:block">Previous</span>`,
81
+ ariaLabel: "Go to previous page",
82
+ href: props.href?.(page() - 1),
83
+ disabled: () => page() <= 1,
84
+ onClick: go(page() - 1),
85
+ size: "default",
86
+ extra: "gap-1 px-2.5 sm:pl-2.5",
87
+ })}
88
+ </li>
89
+ ${() => pageWindow(page(), pageCount(), props.window ?? 7).map((entry, index) => entry === null
90
+ ? html `<li aria-hidden="true">
91
+ <span class="flex size-9 items-center justify-center">
92
+ ${MoreHorizontalIcon({ class: "size-4" })}
93
+ <span class="sr-only">More pages</span>
94
+ </span>
95
+ </li>`
96
+ : html `<li>
97
+ ${link({
98
+ label: String(entry),
99
+ ariaLabel: `Go to page ${entry}`,
100
+ href: props.href?.(entry),
101
+ current: entry === page(),
102
+ onClick: go(entry),
103
+ size: "icon",
104
+ key: index,
105
+ })}
106
+ </li>`)}
107
+ <li>
108
+ ${link({
109
+ label: html `<span class="hidden sm:block">Next</span>${ChevronRightIcon({ class: "size-4" })}`,
110
+ ariaLabel: "Go to next page",
111
+ href: props.href?.(page() + 1),
112
+ disabled: () => page() >= pageCount(),
113
+ onClick: go(page() + 1),
114
+ size: "default",
115
+ extra: "gap-1 px-2.5 sm:pr-2.5",
116
+ })}
117
+ </li>
118
+ </ul>
119
+ </nav>`;
120
+ });
121
+ /**
122
+ * One page link.
123
+ *
124
+ * Always an `<a>`, even without an `href`: pagination is navigation, and the
125
+ * role is what a screen reader uses to offer "list of links" here. The
126
+ * disabled ends carry `aria-disabled` plus `pointer-events-none` rather than
127
+ * being removed, so the control keeps its width at both ends of the range.
128
+ */
129
+ function link(spec) {
130
+ const disabled = () => spec.disabled?.() === true;
131
+ return html `<a
132
+ data-slot="pagination-link"
133
+ href="${spec.href}"
134
+ aria-label="${spec.ariaLabel}"
135
+ aria-current="${spec.current === true ? "page" : undefined}"
136
+ aria-disabled="${() => (disabled() ? "true" : undefined)}"
137
+ tabindex="${() => (disabled() ? -1 : 0)}"
138
+ class="${() => cn(buttonVariants({
139
+ variant: spec.current === true ? "outline" : "ghost",
140
+ size: spec.size,
141
+ }), spec.extra, disabled() ? "pointer-events-none opacity-50" : "")}"
142
+ @click="${spec.onClick}"
143
+ >${spec.label}</a>`;
144
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * RadioGroup — pick exactly one option.
3
+ *
4
+ * Native `<input type="radio">` throughout. The browser already implements the
5
+ * radio group pattern on a shared `name`: arrows move and select, only one can
6
+ * be checked, and the group is one tab stop. Radix reimplements all of that
7
+ * because it needs a stylable element; `appearance-none` gets nebula the same
8
+ * styling freedom while the behaviour stays the browser's.
9
+ *
10
+ * The indicator dot is a sibling shown by `peer-checked:`, so selecting a
11
+ * radio runs no JavaScript at all.
12
+ */
13
+ import type { Child } from "../lib/children.js";
14
+ import { type Reactive } from "../lib/props.js";
15
+ export interface RadioOption {
16
+ value: string;
17
+ label: Child;
18
+ description?: Child;
19
+ disabled?: boolean;
20
+ }
21
+ export interface RadioGroupProps {
22
+ /** Shared `name`, which is what makes the browser treat these as one group. */
23
+ name: string;
24
+ options: readonly RadioOption[];
25
+ value?: Reactive<string | undefined>;
26
+ defaultValue?: string;
27
+ disabled?: Reactive<boolean>;
28
+ orientation?: "vertical" | "horizontal";
29
+ class?: Reactive<string>;
30
+ onValueChange?: (value: string) => void;
31
+ }
32
+ export declare const RadioGroup: (props?: RadioGroupProps | undefined) => import("@c9up/aurora").TemplateResult;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * RadioGroup — pick exactly one option.
3
+ *
4
+ * Native `<input type="radio">` throughout. The browser already implements the
5
+ * radio group pattern on a shared `name`: arrows move and select, only one can
6
+ * be checked, and the group is one tab stop. Radix reimplements all of that
7
+ * because it needs a stylable element; `appearance-none` gets nebula the same
8
+ * styling freedom while the behaviour stays the browser's.
9
+ *
10
+ * The indicator dot is a sibling shown by `peer-checked:`, so selecting a
11
+ * radio runs no JavaScript at all.
12
+ */
13
+ import { component, html } from "@c9up/aurora";
14
+ import { cn } from "../lib/cn.js";
15
+ import { uid } from "../lib/id.js";
16
+ import { read, readOr } from "../lib/props.js";
17
+ const radioClasses = "peer size-4 shrink-0 appearance-none rounded-full border border-input bg-transparent shadow-xs outline-none transition-shadow dark:bg-input/30 checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50";
18
+ export const RadioGroup = component((props) => {
19
+ const selected = () => {
20
+ const controlled = read(props.value);
21
+ return controlled === undefined ? props.defaultValue : controlled;
22
+ };
23
+ return html `<div
24
+ role="radiogroup"
25
+ data-slot="radio-group"
26
+ aria-orientation="${props.orientation ?? "vertical"}"
27
+ class="${() => cn("grid gap-3", props.orientation === "horizontal" ? "grid-flow-col auto-cols-max" : "", read(props.class))}"
28
+ >
29
+ ${props.options.map((option) => renderOption(option, props, selected))}
30
+ </div>`;
31
+ });
32
+ function renderOption(option, props, selected) {
33
+ const id = uid("radio");
34
+ const descriptionId = option.description === undefined ? undefined : uid("radio-description");
35
+ return html `<div class="flex items-start gap-3">
36
+ <span class="relative flex size-4 shrink-0 items-center justify-center">
37
+ <input
38
+ type="radio"
39
+ data-slot="radio-group-item"
40
+ id="${id}"
41
+ name="${props.name}"
42
+ value="${option.value}"
43
+ aria-describedby="${descriptionId}"
44
+ ?disabled="${() => option.disabled === true || readOr(props.disabled, false)}"
45
+ .checked="${() => selected() === option.value}"
46
+ class="${radioClasses}"
47
+ @change="${() => props.onValueChange?.(option.value)}"
48
+ />
49
+ <span
50
+ class="bg-primary pointer-events-none absolute size-2 rounded-full opacity-0 transition-opacity peer-checked:opacity-100"
51
+ ></span>
52
+ </span>
53
+ <div class="grid gap-1 leading-none">
54
+ <label
55
+ for="${id}"
56
+ class="text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
57
+ >${option.label}</label>
58
+ ${option.description === undefined
59
+ ? null
60
+ : html `<p id="${descriptionId}" class="text-muted-foreground text-sm">
61
+ ${option.description}
62
+ </p>`}
63
+ </div>
64
+ </div>`;
65
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resizable — two panes with a draggable divider.
3
+ *
4
+ * `react-resizable-panels`, which shadcn wraps, supports arbitrarily nested
5
+ * groups with persisted layouts and collapse-to-zero. nebula ships the case
6
+ * that covers almost every use of it: two panes, one handle. Nesting two of
7
+ * these gets a three-pane layout, which is where the demand stops.
8
+ *
9
+ * The split is one number — the first pane's percentage — driving both panes
10
+ * through `flex-basis`. One source of truth means the two can never disagree,
11
+ * which is the failure mode of tracking a size per pane.
12
+ *
13
+ * Pointer capture on the handle is what keeps the drag alive when the pointer
14
+ * outruns it, which it will: the handle is a few pixels wide and the pointer
15
+ * moves faster than layout. Without capture the drag drops the moment the
16
+ * cursor leaves.
17
+ *
18
+ * The handle is also a real `separator` with `aria-valuenow`, and arrow keys
19
+ * move it. A resizable layout reachable only by dragging is unusable by
20
+ * keyboard, and this is the whole of what it takes to fix that.
21
+ */
22
+ import { type Slot } from "../lib/children.js";
23
+ import { type Reactive } from "../lib/props.js";
24
+ export interface ResizableProps {
25
+ first?: Slot;
26
+ second?: Slot;
27
+ direction?: "horizontal" | "vertical";
28
+ /** First pane's share at first render, in percent. Default `50`. */
29
+ defaultSize?: number;
30
+ minSize?: number;
31
+ maxSize?: number;
32
+ /** Show the grip dots on the handle. */
33
+ withHandle?: boolean;
34
+ class?: Reactive<string>;
35
+ onResize?: (percent: number) => void;
36
+ }
37
+ export declare const Resizable: (props?: ResizableProps | undefined) => import("@c9up/aurora").TemplateResult;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Resizable — two panes with a draggable divider.
3
+ *
4
+ * `react-resizable-panels`, which shadcn wraps, supports arbitrarily nested
5
+ * groups with persisted layouts and collapse-to-zero. nebula ships the case
6
+ * that covers almost every use of it: two panes, one handle. Nesting two of
7
+ * these gets a three-pane layout, which is where the demand stops.
8
+ *
9
+ * The split is one number — the first pane's percentage — driving both panes
10
+ * through `flex-basis`. One source of truth means the two can never disagree,
11
+ * which is the failure mode of tracking a size per pane.
12
+ *
13
+ * Pointer capture on the handle is what keeps the drag alive when the pointer
14
+ * outruns it, which it will: the handle is a few pixels wide and the pointer
15
+ * moves faster than layout. Without capture the drag drops the moment the
16
+ * cursor leaves.
17
+ *
18
+ * The handle is also a real `separator` with `aria-valuenow`, and arrow keys
19
+ * move it. A resizable layout reachable only by dragging is unusable by
20
+ * keyboard, and this is the whole of what it takes to fix that.
21
+ */
22
+ import { component, html, signal } from "@c9up/aurora";
23
+ import { slot } from "../lib/children.js";
24
+ import { cn } from "../lib/cn.js";
25
+ import { GripVerticalIcon } from "../lib/icons.js";
26
+ import { uid } from "../lib/id.js";
27
+ import { read } from "../lib/props.js";
28
+ const KEYBOARD_STEP = 5;
29
+ export const Resizable = component((props) => {
30
+ const horizontal = props.direction !== "vertical";
31
+ const min = props.minSize ?? 10;
32
+ const max = props.maxSize ?? 90;
33
+ const size = signal(clamp(props.defaultSize ?? 50, min, max));
34
+ const rootId = uid("resizable");
35
+ function setSize(percent) {
36
+ const next = clamp(percent, min, max);
37
+ size(next);
38
+ props.onResize?.(next);
39
+ }
40
+ /**
41
+ * Convert a pointer position into a percentage of the group.
42
+ *
43
+ * Measured against the group's own rect on every move rather than a rect
44
+ * cached at drag start: the window can be resized mid-drag, and a stale rect
45
+ * makes the divider drift away from the pointer.
46
+ */
47
+ function percentAt(event) {
48
+ const root = document.getElementById(rootId);
49
+ if (root === null)
50
+ return null;
51
+ const rect = root.getBoundingClientRect();
52
+ const span = horizontal ? rect.width : rect.height;
53
+ if (span === 0)
54
+ return null;
55
+ const offset = horizontal
56
+ ? event.clientX - rect.left
57
+ : event.clientY - rect.top;
58
+ return (offset / span) * 100;
59
+ }
60
+ function onPointerDown(event) {
61
+ const target = event.currentTarget;
62
+ if (!(target instanceof HTMLElement))
63
+ return;
64
+ event.preventDefault();
65
+ target.setPointerCapture(event.pointerId);
66
+ const onMove = (move) => {
67
+ const percent = percentAt(move);
68
+ if (percent !== null)
69
+ setSize(percent);
70
+ };
71
+ const onUp = () => {
72
+ target.releasePointerCapture(event.pointerId);
73
+ target.removeEventListener("pointermove", onMove);
74
+ target.removeEventListener("pointerup", onUp);
75
+ target.removeEventListener("pointercancel", onUp);
76
+ };
77
+ target.addEventListener("pointermove", onMove);
78
+ target.addEventListener("pointerup", onUp);
79
+ target.addEventListener("pointercancel", onUp);
80
+ }
81
+ function onKeyDown(event) {
82
+ const back = horizontal ? "ArrowLeft" : "ArrowUp";
83
+ const forward = horizontal ? "ArrowRight" : "ArrowDown";
84
+ if (event.key === back) {
85
+ event.preventDefault();
86
+ setSize(size() - KEYBOARD_STEP);
87
+ }
88
+ else if (event.key === forward) {
89
+ event.preventDefault();
90
+ setSize(size() + KEYBOARD_STEP);
91
+ }
92
+ else if (event.key === "Home") {
93
+ event.preventDefault();
94
+ setSize(min);
95
+ }
96
+ else if (event.key === "End") {
97
+ event.preventDefault();
98
+ setSize(max);
99
+ }
100
+ }
101
+ return html `<div
102
+ data-slot="resizable-group"
103
+ id="${rootId}"
104
+ data-direction="${horizontal ? "horizontal" : "vertical"}"
105
+ class="${() => cn("flex h-full w-full", horizontal ? "flex-row" : "flex-col", read(props.class))}"
106
+ >
107
+ <div
108
+ data-slot="resizable-panel"
109
+ class="overflow-hidden"
110
+ style="${() => `flex: 0 0 ${size()}%`}"
111
+ >${slot(props.first)}</div>
112
+ <div
113
+ data-slot="resizable-handle"
114
+ role="separator"
115
+ tabindex="0"
116
+ aria-orientation="${horizontal ? "vertical" : "horizontal"}"
117
+ aria-valuemin="${min}"
118
+ aria-valuemax="${max}"
119
+ aria-valuenow="${() => Math.round(size())}"
120
+ aria-label="Resize panes"
121
+ class="${cn("bg-border relative flex shrink-0 items-center justify-center outline-none", "focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1", horizontal ? "w-px cursor-col-resize" : "h-px cursor-row-resize")}"
122
+ @pointerdown="${onPointerDown}"
123
+ @keydown="${onKeyDown}"
124
+ >
125
+ ${props.withHandle === true
126
+ ? html `<span
127
+ class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border"
128
+ >${GripVerticalIcon({ class: "size-2.5" })}</span>`
129
+ : null}
130
+ </div>
131
+ <div data-slot="resizable-panel" class="flex-1 overflow-hidden">${slot(props.second)}</div>
132
+ </div>`;
133
+ });
134
+ function clamp(value, min, max) {
135
+ return Math.min(Math.max(value, min), max);
136
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Table — the styled primitives for tabular data.
3
+ *
4
+ * Real table elements, not a grid of divs. A `<table>` gives screen readers
5
+ * row and column context for free ("row 3, Amount, 42"), and reproducing that
6
+ * over divs means hand-maintaining `role="grid"`, `aria-rowindex` and
7
+ * `aria-colindex` on every cell. The container's `overflow-x-auto` handles
8
+ * narrow viewports without touching the semantics.
9
+ *
10
+ * These are the presentation layer only. `DataTable` in `organisms/` adds
11
+ * sorting, selection and pagination on top of them.
12
+ */
13
+ import { type Reactive } from "../lib/props.js";
14
+ import type { StyledProps } from "../lib/styled.js";
15
+ export type TableProps = StyledProps;
16
+ export interface TableCellProps extends StyledProps {
17
+ colspan?: number;
18
+ rowspan?: number;
19
+ /** `col` for a column header, `row` for a row header. */
20
+ scope?: "col" | "row";
21
+ /** Reflected as `aria-sort` — set it on the column currently sorted. */
22
+ sort?: Reactive<"ascending" | "descending" | "none" | undefined>;
23
+ onClick?: (event: MouseEvent) => void;
24
+ }
25
+ export declare const Table: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
26
+ export declare const TableHeader: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
27
+ export declare const TableBody: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
28
+ export declare const TableFooter: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
29
+ export interface TableRowProps extends StyledProps {
30
+ /** Reflected as `data-state="selected"`, which the row styling keys off. */
31
+ selected?: Reactive<boolean>;
32
+ onClick?: (event: MouseEvent) => void;
33
+ }
34
+ export declare const TableRow: (props?: TableRowProps | undefined) => import("@c9up/aurora").TemplateResult;
35
+ export declare const TableHead: (props?: TableCellProps | undefined) => import("@c9up/aurora").TemplateResult;
36
+ export declare const TableCell: (props?: TableCellProps | undefined) => import("@c9up/aurora").TemplateResult;
37
+ export declare const TableCaption: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Table — the styled primitives for tabular data.
3
+ *
4
+ * Real table elements, not a grid of divs. A `<table>` gives screen readers
5
+ * row and column context for free ("row 3, Amount, 42"), and reproducing that
6
+ * over divs means hand-maintaining `role="grid"`, `aria-rowindex` and
7
+ * `aria-colindex` on every cell. The container's `overflow-x-auto` handles
8
+ * narrow viewports without touching the semantics.
9
+ *
10
+ * These are the presentation layer only. `DataTable` in `organisms/` adds
11
+ * sorting, selection and pagination on top of them.
12
+ */
13
+ import { component, html } from "@c9up/aurora";
14
+ import { slot } from "../lib/children.js";
15
+ import { cn } from "../lib/cn.js";
16
+ import { read } from "../lib/props.js";
17
+ export const Table = component((props) => {
18
+ return html `<div
19
+ data-slot="table-container"
20
+ class="relative w-full overflow-x-auto"
21
+ >
22
+ <table
23
+ data-slot="table"
24
+ class="${() => cn("w-full caption-bottom text-sm", read(props.class))}"
25
+ >${slot(props.children)}</table>
26
+ </div>`;
27
+ });
28
+ export const TableHeader = component((props) => {
29
+ return html `<thead
30
+ data-slot="table-header"
31
+ class="${() => cn("[&_tr]:border-b", read(props.class))}"
32
+ >${slot(props.children)}</thead>`;
33
+ });
34
+ export const TableBody = component((props) => {
35
+ return html `<tbody
36
+ data-slot="table-body"
37
+ class="${() => cn("[&_tr:last-child]:border-0", read(props.class))}"
38
+ >${slot(props.children)}</tbody>`;
39
+ });
40
+ export const TableFooter = component((props) => {
41
+ return html `<tfoot
42
+ data-slot="table-footer"
43
+ class="${() => cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", read(props.class))}"
44
+ >${slot(props.children)}</tfoot>`;
45
+ });
46
+ export const TableRow = component((props) => {
47
+ return html `<tr
48
+ data-slot="table-row"
49
+ data-state="${() => (read(props.selected) === true ? "selected" : undefined)}"
50
+ class="${() => cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", read(props.class))}"
51
+ @click="${props.onClick}"
52
+ >${slot(props.children)}</tr>`;
53
+ });
54
+ export const TableHead = component((props) => {
55
+ return html `<th
56
+ data-slot="table-head"
57
+ scope="${props.scope ?? "col"}"
58
+ colspan="${props.colspan}"
59
+ rowspan="${props.rowspan}"
60
+ aria-sort="${() => read(props.sort)}"
61
+ class="${() => cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", read(props.class))}"
62
+ @click="${props.onClick}"
63
+ >${slot(props.children)}</th>`;
64
+ });
65
+ export const TableCell = component((props) => {
66
+ return html `<td
67
+ data-slot="table-cell"
68
+ colspan="${props.colspan}"
69
+ rowspan="${props.rowspan}"
70
+ class="${() => cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", read(props.class))}"
71
+ >${slot(props.children)}</td>`;
72
+ });
73
+ export const TableCaption = component((props) => {
74
+ return html `<caption
75
+ data-slot="table-caption"
76
+ class="${() => cn("text-muted-foreground mt-4 text-sm", read(props.class))}"
77
+ >${slot(props.children)}</caption>`;
78
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tabs — one panel visible at a time.
3
+ *
4
+ * The WAI-ARIA tabs pattern in full: the tab list is a single tab stop with
5
+ * arrows moving between tabs, each tab points at its panel with
6
+ * `aria-controls`, and each panel points back with `aria-labelledby`.
7
+ *
8
+ * Activation is automatic — moving to a tab selects it — which is the right
9
+ * default when panels are already rendered and switching costs nothing. The
10
+ * `activateOnFocus: false` escape hatch exists for panels that fetch on
11
+ * display, where arrowing past three tabs would fire three requests.
12
+ *
13
+ * Hidden panels stay mounted so a form or a scroll position inside one
14
+ * survives a trip to another tab. `hidden` keeps them out of the layout and
15
+ * the accessibility tree, which is the behaviour `display: none` would give
16
+ * without also removing them from the DOM.
17
+ */
18
+ import type { Child, Slot } from "../lib/children.js";
19
+ import { type Reactive } from "../lib/props.js";
20
+ export interface TabItem {
21
+ value: string;
22
+ label: Child;
23
+ content: Slot;
24
+ disabled?: boolean;
25
+ }
26
+ export interface TabsProps {
27
+ items: readonly TabItem[];
28
+ /** Selected at first render. Defaults to the first enabled tab. */
29
+ defaultValue?: string;
30
+ orientation?: "horizontal" | "vertical";
31
+ /** Selecting follows focus. Default `true`. */
32
+ activateOnFocus?: boolean;
33
+ onValueChange?: (value: string) => void;
34
+ class?: Reactive<string>;
35
+ listClass?: Reactive<string>;
36
+ }
37
+ export declare const Tabs: (props?: TabsProps | undefined) => import("@c9up/aurora").TemplateResult;