@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,75 @@
1
+ /**
2
+ * The engine-free adapter.
3
+ *
4
+ * No Tailwind, no UnoCSS, no build step in the app at all: nebula ships a
5
+ * prebuilt `nebula.css` holding exactly the utilities its own components
6
+ * reference, and the app imports it.
7
+ *
8
+ * **Where the compilation actually happens.** nebula's own release build runs
9
+ * Tailwind over `src/**` and freezes the result (`scripts/build-css.ts`).
10
+ * Tailwind is therefore a devDependency *of nebula* and never reaches the
11
+ * app's dependency tree — which is the whole point. The set of utilities is
12
+ * knowable ahead of time because the components are nebula's, and they do not
13
+ * change between one app's install and another's.
14
+ *
15
+ * **The limitation, stated plainly.** The frozen stylesheet covers the
16
+ * components as nebula wrote them. The registry copies those sources into the
17
+ * app for the user to edit, and the moment an edit introduces a utility nebula
18
+ * never used — `bg-emerald-500`, `grid-cols-7` — nothing emits it, and the
19
+ * class silently does nothing.
20
+ *
21
+ * So this adapter suits an app that takes the components as they are and
22
+ * writes its own hand-rolled CSS around them. An app that intends to retune
23
+ * components through their class strings wants `tailwind` or `unocss`, where
24
+ * the scanner sees the edits.
25
+ *
26
+ * Lifting that limitation means compiling the app's copied sources too — a
27
+ * utility scanner and a CSS emitter over the finite surface nebula uses. That
28
+ * is a build-time, string-heavy, run-on-every-build job, which is the profile
29
+ * where moving it into `ream-cli` (Rust) pays; it is the one part of nebula
30
+ * where Rust would earn its place.
31
+ */
32
+
33
+ import type { ResolvedNebulaConfig } from "../config.js";
34
+ import type { GeneratedFile, StyleAdapter } from "./types.js";
35
+
36
+ function stylesheet(): string {
37
+ return `/*
38
+ * nebula, engine-free.
39
+ *
40
+ * \`nebula.css\` is prebuilt and shipped with the package — no scanner runs in
41
+ * this project, so nothing here needs a build step.
42
+ *
43
+ * It covers nebula's components as published. A utility you add to a copied
44
+ * component will not be emitted; write the rule below by hand, or switch the
45
+ * adapter in \`config/nebula.ts\` to \`tailwind\` or \`unocss\`.
46
+ */
47
+ @import "@c9up/nebula/nebula.css";
48
+
49
+ /* Your own styles go here. */
50
+ `;
51
+ }
52
+
53
+ export const cssAdapter: StyleAdapter = {
54
+ name: "css",
55
+ summary:
56
+ "No engine and no build step — nebula's prebuilt stylesheet, imported as-is.",
57
+ packages: [],
58
+
59
+ files(config: ResolvedNebulaConfig): readonly GeneratedFile[] {
60
+ return [
61
+ { path: config.paths.css, contents: stylesheet(), skipIfExists: true },
62
+ ];
63
+ },
64
+
65
+ /**
66
+ * Nothing to compile.
67
+ *
68
+ * `null` rather than a copy command: the app serves the stylesheet straight
69
+ * from the package, so there is no compile step for `ream build` to run and
70
+ * none should be registered.
71
+ */
72
+ commands(): null {
73
+ return null;
74
+ },
75
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The three style adapters.
3
+ *
4
+ * All three consume the same utility class names, which is what lets one set
5
+ * of components serve all of them. Switching is a one-word change in
6
+ * `config/nebula.ts` followed by `ream nebula:init`.
7
+ */
8
+
9
+ import type { AdapterName } from "../config.js";
10
+ import { cssAdapter } from "./css.js";
11
+ import { tailwindAdapter } from "./tailwind.js";
12
+ import type { StyleAdapter } from "./types.js";
13
+ import { unocssAdapter } from "./unocss.js";
14
+
15
+ export { cssAdapter } from "./css.js";
16
+ export { tailwindAdapter } from "./tailwind.js";
17
+ export type {
18
+ AdapterCommand,
19
+ GeneratedFile,
20
+ StyleAdapter,
21
+ } from "./types.js";
22
+ export { unocssAdapter } from "./unocss.js";
23
+
24
+ const adapters: Readonly<Record<AdapterName, StyleAdapter>> = {
25
+ tailwind: tailwindAdapter,
26
+ unocss: unocssAdapter,
27
+ css: cssAdapter,
28
+ };
29
+
30
+ export function adapterFor(name: AdapterName): StyleAdapter {
31
+ return adapters[name];
32
+ }
33
+
34
+ export function adapterNames(): readonly AdapterName[] {
35
+ return Object.keys(adapters).filter(isAdapterName);
36
+ }
37
+
38
+ export function isAdapterName(value: string): value is AdapterName {
39
+ return value === "tailwind" || value === "unocss" || value === "css";
40
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Tailwind CSS v4 adapter.
3
+ *
4
+ * The default, and the one shadcn itself targets — so the class strings in
5
+ * nebula's components are exactly the ones upstream uses, and a shadcn example
6
+ * pasted into a nebula project renders the same.
7
+ *
8
+ * v4 is configured in CSS rather than a JS config file, which is why this
9
+ * emits a stylesheet and no `tailwind.config.js`. Three directives do the
10
+ * work:
11
+ *
12
+ * - `@source` points the scanner at the copied components. Without it Tailwind
13
+ * never sees `bg-primary` and emits none of it — the single most common way
14
+ * a v4 setup produces an unstyled page.
15
+ * - `@custom-variant dark` makes `dark:` respond to a `.dark` class rather
16
+ * than the system preference, so a theme toggle can override the OS.
17
+ * - `@theme inline` maps nebula's plain custom properties onto Tailwind's
18
+ * colour namespace, which is what turns `--primary` into a `bg-primary`
19
+ * utility.
20
+ *
21
+ * The build runs through `@tailwindcss/cli`, matching the aurora apps already
22
+ * in this workspace.
23
+ */
24
+
25
+ import type { ResolvedNebulaConfig } from "../config.js";
26
+ import type { GeneratedFile, StyleAdapter } from "./types.js";
27
+
28
+ function stylesheet(config: ResolvedNebulaConfig): string {
29
+ return `@import "tailwindcss";
30
+ @import "@c9up/nebula/theme.css";
31
+
32
+ /* Scan the component tree so the utilities they use are emitted. */
33
+ @source "${relativeFromCss(config)}";
34
+
35
+ /* Class-based dark mode: \`dark:\` applies under any \`.dark\` ancestor. */
36
+ @custom-variant dark (&:is(.dark *));
37
+
38
+ /*
39
+ * Map nebula's tokens onto Tailwind's namespaces.
40
+ *
41
+ * \`inline\` matters: without it Tailwind copies the *values* at build time and
42
+ * the \`.dark\` overrides never reach the utilities, so dark mode changes the
43
+ * custom properties and nothing else.
44
+ */
45
+ @theme inline {
46
+ --color-background: var(--background);
47
+ --color-foreground: var(--foreground);
48
+ --color-card: var(--card);
49
+ --color-card-foreground: var(--card-foreground);
50
+ --color-popover: var(--popover);
51
+ --color-popover-foreground: var(--popover-foreground);
52
+ --color-primary: var(--primary);
53
+ --color-primary-foreground: var(--primary-foreground);
54
+ --color-secondary: var(--secondary);
55
+ --color-secondary-foreground: var(--secondary-foreground);
56
+ --color-muted: var(--muted);
57
+ --color-muted-foreground: var(--muted-foreground);
58
+ --color-accent: var(--accent);
59
+ --color-accent-foreground: var(--accent-foreground);
60
+ --color-destructive: var(--destructive);
61
+ --color-destructive-foreground: var(--destructive-foreground);
62
+ --color-border: var(--border);
63
+ --color-input: var(--input);
64
+ --color-ring: var(--ring);
65
+ --color-chart-1: var(--chart-1);
66
+ --color-chart-2: var(--chart-2);
67
+ --color-chart-3: var(--chart-3);
68
+ --color-chart-4: var(--chart-4);
69
+ --color-chart-5: var(--chart-5);
70
+ --color-sidebar: var(--sidebar);
71
+ --color-sidebar-foreground: var(--sidebar-foreground);
72
+ --color-sidebar-primary: var(--sidebar-primary);
73
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
74
+ --color-sidebar-accent: var(--sidebar-accent);
75
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
76
+ --color-sidebar-border: var(--sidebar-border);
77
+ --color-sidebar-ring: var(--sidebar-ring);
78
+
79
+ --radius-sm: calc(var(--radius) - 4px);
80
+ --radius-md: calc(var(--radius) - 2px);
81
+ --radius-lg: var(--radius);
82
+ --radius-xl: calc(var(--radius) + 4px);
83
+ }
84
+ `;
85
+ }
86
+
87
+ /**
88
+ * `@source` is resolved relative to the stylesheet, not the project root.
89
+ *
90
+ * The two are rarely the same — `resources/css/app.css` scanning
91
+ * `resources/pages` needs `../pages` — and getting it wrong fails silently:
92
+ * Tailwind finds no files, emits no utilities, and the page renders unstyled
93
+ * with no error anywhere.
94
+ */
95
+ function relativeFromCss(config: ResolvedNebulaConfig): string {
96
+ const cssDirectory = config.paths.css.split("/").slice(0, -1);
97
+ const target = config.paths.components.split("/");
98
+
99
+ let shared = 0;
100
+ while (
101
+ shared < cssDirectory.length &&
102
+ shared < target.length &&
103
+ cssDirectory[shared] === target[shared]
104
+ ) {
105
+ shared += 1;
106
+ }
107
+
108
+ const up = new Array(cssDirectory.length - shared).fill("..");
109
+ return [...up, ...target.slice(shared)].join("/");
110
+ }
111
+
112
+ export const tailwindAdapter: StyleAdapter = {
113
+ name: "tailwind",
114
+ summary: "Tailwind CSS v4, configured in CSS. What shadcn/ui itself targets.",
115
+ packages: ["tailwindcss", "@tailwindcss/cli"],
116
+
117
+ files(config: ResolvedNebulaConfig): readonly GeneratedFile[] {
118
+ return [
119
+ {
120
+ path: config.paths.css,
121
+ contents: stylesheet(config),
122
+ skipIfExists: true,
123
+ },
124
+ ];
125
+ },
126
+
127
+ commands(config) {
128
+ const base = ["-i", config.paths.css, "-o", config.paths.output];
129
+ return {
130
+ build: { command: "tailwindcss", args: base },
131
+ dev: { command: "tailwindcss", args: [...base, "--watch"] },
132
+ };
133
+ },
134
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The style-adapter contract.
3
+ *
4
+ * An adapter answers three questions and nothing else:
5
+ *
6
+ * 1. What does the app need to install? nebula never installs it — the app
7
+ * owns its dependencies, and a UI library quietly adding a build tool to
8
+ * someone's `package.json` is how a dependency tree gets away from them.
9
+ * 2. What files should exist? The stylesheet entry, and any engine config.
10
+ * 3. What command compiles them? Handed to ream's `assets` config, which
11
+ * `ream dev` and `ream build` already run — the same hook AdonisJS uses for
12
+ * its bundler.
13
+ *
14
+ * Deliberately not in the contract: anything that would change how a component
15
+ * is written. All three adapters consume the same utility class names, which
16
+ * is what keeps one set of fifty-odd components serving all of them. An engine
17
+ * with a different authoring model — Panda's recipes, StyleX's compile-time
18
+ * API — cannot be added behind this interface, and pretending otherwise would
19
+ * mean a second version of every component.
20
+ */
21
+
22
+ import type { ResolvedNebulaConfig } from "../config.js";
23
+
24
+ export interface GeneratedFile {
25
+ /** Path relative to the project root. */
26
+ path: string;
27
+ contents: string;
28
+ /**
29
+ * Leave an existing file alone. True for anything the user is expected to
30
+ * edit — their stylesheet is theirs once it exists.
31
+ */
32
+ skipIfExists: boolean;
33
+ }
34
+
35
+ export interface AdapterCommand {
36
+ command: string;
37
+ args: readonly string[];
38
+ }
39
+
40
+ export interface StyleAdapter {
41
+ readonly name: string;
42
+ /** One-line summary, printed by `nebula init`. */
43
+ readonly summary: string;
44
+ /** Packages the app must add itself. Empty for an engine-free adapter. */
45
+ readonly packages: readonly string[];
46
+ /** Stubs to write. */
47
+ files(config: ResolvedNebulaConfig): readonly GeneratedFile[];
48
+ /** Build and watch commands, or `null` when there is nothing to compile. */
49
+ commands(config: ResolvedNebulaConfig): {
50
+ build: AdapterCommand;
51
+ dev: AdapterCommand;
52
+ } | null;
53
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * UnoCSS adapter.
3
+ *
4
+ * `presetWind4` implements Tailwind v4's utility syntax, so nebula's component
5
+ * sources are unchanged between this adapter and the Tailwind one — the whole
6
+ * reason both can be offered without a second set of components.
7
+ *
8
+ * What differs is the config format. UnoCSS is configured in JavaScript rather
9
+ * than CSS, so the theme mapping that `@theme inline` expresses for Tailwind
10
+ * is a `theme` object here, and the token definitions come in through
11
+ * `preflights` since UnoCSS has no `@import` of its own.
12
+ *
13
+ * The tokens still come from `theme.css`. One file remains the source of truth
14
+ * for the palette; only the plumbing around it changes.
15
+ */
16
+
17
+ import type { ResolvedNebulaConfig } from "../config.js";
18
+ import type { GeneratedFile, StyleAdapter } from "./types.js";
19
+
20
+ const CONFIG_PATH = "uno.config.ts";
21
+
22
+ function unoConfig(config: ResolvedNebulaConfig): string {
23
+ return `import { defineConfig, presetWind4 } from 'unocss'
24
+ import { readFileSync } from 'node:fs'
25
+ import { createRequire } from 'node:module'
26
+
27
+ const require = createRequire(import.meta.url)
28
+
29
+ /*
30
+ * nebula's tokens, read from the package rather than copied.
31
+ *
32
+ * UnoCSS has no \`@import\`, so the custom properties are injected as a
33
+ * preflight. Reading the file keeps one source of truth for the palette —
34
+ * copying it here would drift the moment nebula retunes a colour.
35
+ */
36
+ const tokens = readFileSync(require.resolve('@c9up/nebula/theme.css'), 'utf8')
37
+
38
+ /**
39
+ * Map a token to a colour the utilities can reach.
40
+ *
41
+ * The indirection through \`var()\` is what makes dark mode work: the utility
42
+ * resolves the variable at paint time, so a \`.dark\` ancestor redefining it
43
+ * changes the rendered colour without a second stylesheet.
44
+ */
45
+ const color = (name: string) => \`var(--\${name})\`
46
+
47
+ export default defineConfig({
48
+ presets: [presetWind4()],
49
+ content: {
50
+ filesystem: ['${config.paths.components}/**/*.{ts,js}'],
51
+ },
52
+ preflights: [{ getCSS: () => tokens }],
53
+ // \`dark:\` follows a \`.dark\` class, so a theme toggle can override the OS.
54
+ darkMode: 'class',
55
+ theme: {
56
+ colors: {
57
+ background: color('background'),
58
+ foreground: color('foreground'),
59
+ card: { DEFAULT: color('card'), foreground: color('card-foreground') },
60
+ popover: { DEFAULT: color('popover'), foreground: color('popover-foreground') },
61
+ primary: { DEFAULT: color('primary'), foreground: color('primary-foreground') },
62
+ secondary: { DEFAULT: color('secondary'), foreground: color('secondary-foreground') },
63
+ muted: { DEFAULT: color('muted'), foreground: color('muted-foreground') },
64
+ accent: { DEFAULT: color('accent'), foreground: color('accent-foreground') },
65
+ destructive: { DEFAULT: color('destructive'), foreground: color('destructive-foreground') },
66
+ border: color('border'),
67
+ input: color('input'),
68
+ ring: color('ring'),
69
+ chart: {
70
+ 1: color('chart-1'),
71
+ 2: color('chart-2'),
72
+ 3: color('chart-3'),
73
+ 4: color('chart-4'),
74
+ 5: color('chart-5'),
75
+ },
76
+ sidebar: {
77
+ DEFAULT: color('sidebar'),
78
+ foreground: color('sidebar-foreground'),
79
+ primary: color('sidebar-primary'),
80
+ accent: color('sidebar-accent'),
81
+ border: color('sidebar-border'),
82
+ ring: color('sidebar-ring'),
83
+ },
84
+ },
85
+ radius: {
86
+ sm: 'calc(var(--radius) - 4px)',
87
+ md: 'calc(var(--radius) - 2px)',
88
+ lg: 'var(--radius)',
89
+ xl: 'calc(var(--radius) + 4px)',
90
+ },
91
+ },
92
+ })
93
+ `;
94
+ }
95
+
96
+ export const unocssAdapter: StyleAdapter = {
97
+ name: "unocss",
98
+ summary:
99
+ "UnoCSS with presetWind4 — same class syntax, no PostCSS, faster builds.",
100
+ packages: ["unocss", "@unocss/cli"],
101
+
102
+ files(config: ResolvedNebulaConfig): readonly GeneratedFile[] {
103
+ return [
104
+ { path: CONFIG_PATH, contents: unoConfig(config), skipIfExists: true },
105
+ ];
106
+ },
107
+
108
+ commands(config) {
109
+ const patterns = `${config.paths.components}/**/*.{ts,js}`;
110
+ const base = [patterns, "-o", config.paths.output];
111
+ return {
112
+ build: { command: "unocss", args: base },
113
+ dev: { command: "unocss", args: [...base, "--watch"] },
114
+ };
115
+ },
116
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AspectRatio — hold a box at a fixed width-to-height ratio.
3
+ *
4
+ * Radix's version predates the CSS `aspect-ratio` property and reproduces it
5
+ * with a padding-top percentage trick. That is no longer needed anywhere
6
+ * nebula runs, so this is the property, set inline because the value is
7
+ * arbitrary and cannot be a utility class.
8
+ */
9
+
10
+ import { component, html } from "@c9up/aurora";
11
+ import { type Slot, slot } from "../lib/children.js";
12
+ import { cn } from "../lib/cn.js";
13
+ import { type Reactive, read, readOr } from "../lib/props.js";
14
+
15
+ export interface AspectRatioProps {
16
+ children?: Slot;
17
+ /** Width divided by height. `16 / 9` for widescreen, `1` for a square. */
18
+ ratio?: Reactive<number>;
19
+ class?: Reactive<string>;
20
+ }
21
+
22
+ export const AspectRatio = component<AspectRatioProps>((props) => {
23
+ return html`<div
24
+ data-slot="aspect-ratio"
25
+ style="${() => `aspect-ratio: ${readOr(props.ratio, 1)}`}"
26
+ class="${() => cn("relative w-full", read(props.class))}"
27
+ >${slot(props.children)}</div>`;
28
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Avatar — a user's picture, with a fallback when there is none.
3
+ *
4
+ * shadcn splits this into `Avatar` / `AvatarImage` / `AvatarFallback` because
5
+ * Radix needs three components to track the image's load state across them.
6
+ * nebula tracks it in one signal inside one component, so the API is a `src`
7
+ * and a `fallback`.
8
+ *
9
+ * Three states, not two. An image that has not loaded *yet* must not show the
10
+ * fallback — swapping initials in and a photo over them a moment later is the
11
+ * flicker this component exists to avoid. So the fallback appears only once
12
+ * the image has actually failed, or when there was no `src` to begin with.
13
+ */
14
+
15
+ import { component, html, signal } from "@c9up/aurora";
16
+ import { type Slot, slot } from "../lib/children.js";
17
+ import { cn } from "../lib/cn.js";
18
+ import { type Reactive, read, readOr } from "../lib/props.js";
19
+
20
+ type LoadState = "pending" | "loaded" | "failed";
21
+
22
+ export interface AvatarProps {
23
+ src?: Reactive<string | undefined>;
24
+ alt?: Reactive<string>;
25
+ /** Shown when there is no image, or it failed. Usually initials. */
26
+ fallback?: Slot;
27
+ class?: Reactive<string>;
28
+ }
29
+
30
+ export const Avatar = component<AvatarProps>((props) => {
31
+ const state = signal<LoadState>("pending");
32
+
33
+ const hasSrc = (): boolean => {
34
+ const src = read(props.src);
35
+ return src !== undefined && src !== "";
36
+ };
37
+
38
+ const showFallback = (): boolean => !hasSrc() || state() === "failed";
39
+
40
+ return html`<span
41
+ data-slot="avatar"
42
+ class="${() =>
43
+ cn(
44
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
45
+ read(props.class),
46
+ )}"
47
+ >
48
+ ${() =>
49
+ hasSrc() && state() !== "failed"
50
+ ? html`<img
51
+ data-slot="avatar-image"
52
+ src="${() => read(props.src)}"
53
+ alt="${() => readOr(props.alt, "")}"
54
+ class="aspect-square size-full object-cover"
55
+ @load="${() => state("loaded")}"
56
+ @error="${() => state("failed")}"
57
+ />`
58
+ : null}
59
+ ${() =>
60
+ showFallback()
61
+ ? html`<span
62
+ data-slot="avatar-fallback"
63
+ class="bg-muted flex size-full items-center justify-center rounded-full text-xs font-medium"
64
+ >${slot(props.fallback)}</span>`
65
+ : null}
66
+ </span>`;
67
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Badge — a small status or count marker.
3
+ *
4
+ * Exported variants as well as the component, for the same reason as Button:
5
+ * a badge is often a link to the thing it counts, and an anchor needs the
6
+ * classes without the element.
7
+ */
8
+
9
+ import { component, html } from "@c9up/aurora";
10
+ import { type Slot, slot } from "../lib/children.js";
11
+ import { cva, type VariantProps } from "../lib/cva.js";
12
+ import { type Reactive, read } from "../lib/props.js";
13
+
14
+ export const badgeVariants = cva(
15
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
16
+ {
17
+ variants: {
18
+ variant: {
19
+ default:
20
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
21
+ secondary:
22
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
23
+ destructive:
24
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20",
25
+ outline:
26
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
27
+ },
28
+ },
29
+ defaultVariants: { variant: "default" },
30
+ },
31
+ );
32
+
33
+ export type BadgeVariants = VariantProps<typeof badgeVariants>;
34
+
35
+ export interface BadgeProps {
36
+ children?: Slot;
37
+ variant?: Reactive<BadgeVariants["variant"]>;
38
+ class?: Reactive<string>;
39
+ }
40
+
41
+ export const Badge = component<BadgeProps>((props) => {
42
+ return html`<span
43
+ data-slot="badge"
44
+ class="${() => badgeVariants({ variant: read(props.variant), class: read(props.class) })}"
45
+ >${slot(props.children)}</span>`;
46
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Button — the reference component for the whole library.
3
+ *
4
+ * Every other nebula component follows the shape set here, so it is worth
5
+ * reading once:
6
+ *
7
+ * - Variants come from `cva`, exported separately as `buttonVariants` so a
8
+ * link or a menu item can borrow the button's look without being a button.
9
+ * shadcn does the same, and half its examples depend on it.
10
+ * - Every prop that can change is `Reactive<T>` and is bound through
11
+ * `accessor()`. Aurora does not re-render; a value read once at setup is
12
+ * frozen, so `disabled: form.pending` would be a bug that only shows up
13
+ * after the first submit.
14
+ * - `data-slot` mirrors shadcn v4, which stamps one on every part. It gives
15
+ * host CSS and tests a stable hook that does not depend on the utility
16
+ * classes, which are the part a user is expected to edit.
17
+ *
18
+ * No `asChild`. React's Slot pattern clones an element and merges props into
19
+ * it; Aurora templates are compiled markup with no element to clone. Where
20
+ * shadcn would write `<Button asChild><a/></Button>`, nebula exports the
21
+ * variants and you put them on the anchor yourself — which is what the Slot
22
+ * was doing anyway, with a runtime in between.
23
+ */
24
+
25
+ import { component, html } from "@c9up/aurora";
26
+ import { type Slot, slot } from "../lib/children.js";
27
+ import { cva, type VariantProps } from "../lib/cva.js";
28
+ import { accessor, type Reactive, read } from "../lib/props.js";
29
+
30
+ export const buttonVariants = cva(
31
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
32
+ {
33
+ variants: {
34
+ variant: {
35
+ default:
36
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
37
+ destructive:
38
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20",
39
+ outline:
40
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
41
+ secondary:
42
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
43
+ ghost: "hover:bg-accent hover:text-accent-foreground",
44
+ link: "text-primary underline-offset-4 hover:underline",
45
+ },
46
+ size: {
47
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
48
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
49
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
50
+ icon: "size-9",
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ variant: "default",
55
+ size: "default",
56
+ },
57
+ },
58
+ );
59
+
60
+ export type ButtonVariants = VariantProps<typeof buttonVariants>;
61
+
62
+ export interface ButtonProps {
63
+ children?: Slot;
64
+ variant?: Reactive<ButtonVariants["variant"]>;
65
+ size?: Reactive<ButtonVariants["size"]>;
66
+ /** Defaults to `"button"` — never `"submit"`, which is HTML's default and
67
+ * the cause of most accidental form submissions. */
68
+ type?: Reactive<"button" | "submit" | "reset">;
69
+ disabled?: Reactive<boolean>;
70
+ class?: Reactive<string>;
71
+ id?: string;
72
+ /** Sets `aria-label`. Required in practice for `size: "icon"`. */
73
+ label?: Reactive<string | undefined>;
74
+ onClick?: (event: MouseEvent) => void;
75
+ }
76
+
77
+ export const Button = component<ButtonProps>((props) => {
78
+ // An accessor, not a string: the renderer wraps it in an effect, so the
79
+ // class list tracks whichever of these props were given as signals.
80
+ const classes = (): string =>
81
+ buttonVariants({
82
+ variant: read(props.variant),
83
+ size: read(props.size),
84
+ class: read(props.class),
85
+ });
86
+
87
+ return html`<button
88
+ data-slot="button"
89
+ id="${props.id}"
90
+ type="${accessor(props.type, "button")}"
91
+ class="${classes}"
92
+ aria-label="${accessor(props.label, undefined)}"
93
+ ?disabled="${accessor(props.disabled, false)}"
94
+ @click="${props.onClick}"
95
+ >${slot(props.children)}</button>`;
96
+ });