@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.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/adapters/css.d.ts +33 -0
- package/dist/adapters/css.js +67 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.js +27 -0
- package/dist/adapters/tailwind.d.ts +25 -0
- package/dist/adapters/tailwind.js +122 -0
- package/dist/adapters/types.d.ts +49 -0
- package/dist/adapters/types.js +21 -0
- package/dist/adapters/unocss.d.ts +17 -0
- package/dist/adapters/unocss.js +107 -0
- package/dist/atoms/AspectRatio.d.ts +17 -0
- package/dist/atoms/AspectRatio.js +19 -0
- package/dist/atoms/Avatar.d.ts +23 -0
- package/dist/atoms/Avatar.js +46 -0
- package/dist/atoms/Badge.d.ts +25 -0
- package/dist/atoms/Badge.js +28 -0
- package/dist/atoms/Button.d.ts +58 -0
- package/dist/atoms/Button.js +67 -0
- package/dist/atoms/Checkbox.d.ts +41 -0
- package/dist/atoms/Checkbox.js +47 -0
- package/dist/atoms/Input.d.ts +36 -0
- package/dist/atoms/Input.js +50 -0
- package/dist/atoms/Kbd.d.ts +14 -0
- package/dist/atoms/Kbd.js +17 -0
- package/dist/atoms/Label.d.ts +24 -0
- package/dist/atoms/Label.js +26 -0
- package/dist/atoms/Marker.d.ts +42 -0
- package/dist/atoms/Marker.js +55 -0
- package/dist/atoms/NativeSelect.d.ts +43 -0
- package/dist/atoms/NativeSelect.js +81 -0
- package/dist/atoms/Progress.d.ts +21 -0
- package/dist/atoms/Progress.js +48 -0
- package/dist/atoms/ScrollArea.d.ts +28 -0
- package/dist/atoms/ScrollArea.js +37 -0
- package/dist/atoms/Separator.d.ts +16 -0
- package/dist/atoms/Separator.js +21 -0
- package/dist/atoms/Skeleton.d.ts +13 -0
- package/dist/atoms/Skeleton.js +18 -0
- package/dist/atoms/Slider.d.ts +30 -0
- package/dist/atoms/Slider.js +40 -0
- package/dist/atoms/Spinner.d.ts +17 -0
- package/dist/atoms/Spinner.js +20 -0
- package/dist/atoms/Switch.d.ts +24 -0
- package/dist/atoms/Switch.js +41 -0
- package/dist/atoms/Textarea.d.ts +28 -0
- package/dist/atoms/Textarea.js +36 -0
- package/dist/atoms/Toggle.d.ts +38 -0
- package/dist/atoms/Toggle.js +45 -0
- package/dist/atoms/index.d.ts +32 -0
- package/dist/atoms/index.js +32 -0
- package/dist/cli/add.d.ts +44 -0
- package/dist/cli/add.js +99 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/registry.d.ts +31 -0
- package/dist/cli/registry.js +83 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +43 -0
- package/dist/configure.d.ts +34 -0
- package/dist/configure.js +85 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/lib/children.d.ts +28 -0
- package/dist/lib/children.js +27 -0
- package/dist/lib/cn.d.ts +13 -0
- package/dist/lib/cn.js +13 -0
- package/dist/lib/cva.d.ts +83 -0
- package/dist/lib/cva.js +178 -0
- package/dist/lib/icons.d.ts +44 -0
- package/dist/lib/icons.js +79 -0
- package/dist/lib/id.d.ts +31 -0
- package/dist/lib/id.js +41 -0
- package/dist/lib/index.d.ts +12 -0
- package/dist/lib/index.js +12 -0
- package/dist/lib/motion.d.ts +31 -0
- package/dist/lib/motion.js +39 -0
- package/dist/lib/props.d.ts +48 -0
- package/dist/lib/props.js +59 -0
- package/dist/lib/styled.d.ts +31 -0
- package/dist/lib/styled.js +39 -0
- package/dist/molecules/Accordion.d.ts +35 -0
- package/dist/molecules/Accordion.js +117 -0
- package/dist/molecules/Alert.d.ts +30 -0
- package/dist/molecules/Alert.js +35 -0
- package/dist/molecules/Attachment.d.ts +39 -0
- package/dist/molecules/Attachment.js +103 -0
- package/dist/molecules/Breadcrumb.d.ts +29 -0
- package/dist/molecules/Breadcrumb.js +73 -0
- package/dist/molecules/Bubble.d.ts +56 -0
- package/dist/molecules/Bubble.js +92 -0
- package/dist/molecules/ButtonGroup.d.ts +23 -0
- package/dist/molecules/ButtonGroup.js +30 -0
- package/dist/molecules/Card.d.ts +17 -0
- package/dist/molecules/Card.js +17 -0
- package/dist/molecules/Collapsible.d.ts +40 -0
- package/dist/molecules/Collapsible.js +71 -0
- package/dist/molecules/Empty.d.ts +16 -0
- package/dist/molecules/Empty.js +16 -0
- package/dist/molecules/Field.d.ts +54 -0
- package/dist/molecules/Field.js +82 -0
- package/dist/molecules/InputGroup.d.ts +33 -0
- package/dist/molecules/InputGroup.js +35 -0
- package/dist/molecules/InputOTP.d.ts +33 -0
- package/dist/molecules/InputOTP.js +140 -0
- package/dist/molecules/Item.d.ts +41 -0
- package/dist/molecules/Item.js +48 -0
- package/dist/molecules/Message.d.ts +43 -0
- package/dist/molecules/Message.js +82 -0
- package/dist/molecules/Pagination.d.ts +41 -0
- package/dist/molecules/Pagination.js +144 -0
- package/dist/molecules/RadioGroup.d.ts +32 -0
- package/dist/molecules/RadioGroup.js +65 -0
- package/dist/molecules/Resizable.d.ts +37 -0
- package/dist/molecules/Resizable.js +136 -0
- package/dist/molecules/Table.d.ts +37 -0
- package/dist/molecules/Table.js +78 -0
- package/dist/molecules/Tabs.d.ts +37 -0
- package/dist/molecules/Tabs.js +109 -0
- package/dist/molecules/ToggleGroup.d.ts +37 -0
- package/dist/molecules/ToggleGroup.js +68 -0
- package/dist/molecules/Typography.d.ts +26 -0
- package/dist/molecules/Typography.js +67 -0
- package/dist/molecules/index.d.ts +37 -0
- package/dist/molecules/index.js +37 -0
- package/dist/organisms/AlertDialog.d.ts +40 -0
- package/dist/organisms/AlertDialog.js +104 -0
- package/dist/organisms/Calendar.d.ts +96 -0
- package/dist/organisms/Calendar.js +353 -0
- package/dist/organisms/Carousel.d.ts +29 -0
- package/dist/organisms/Carousel.js +128 -0
- package/dist/organisms/Chart.d.ts +55 -0
- package/dist/organisms/Chart.js +189 -0
- package/dist/organisms/Combobox.d.ts +41 -0
- package/dist/organisms/Combobox.js +102 -0
- package/dist/organisms/Command.d.ts +46 -0
- package/dist/organisms/Command.js +171 -0
- package/dist/organisms/CommandDialog.d.ts +42 -0
- package/dist/organisms/CommandDialog.js +116 -0
- package/dist/organisms/ContextMenu.d.ts +29 -0
- package/dist/organisms/ContextMenu.js +102 -0
- package/dist/organisms/DataTable.d.ts +50 -0
- package/dist/organisms/DataTable.js +257 -0
- package/dist/organisms/DatePicker.d.ts +34 -0
- package/dist/organisms/DatePicker.js +115 -0
- package/dist/organisms/DateRangePicker.d.ts +34 -0
- package/dist/organisms/DateRangePicker.js +122 -0
- package/dist/organisms/Dialog.d.ts +50 -0
- package/dist/organisms/Dialog.js +116 -0
- package/dist/organisms/Drawer.d.ts +40 -0
- package/dist/organisms/Drawer.js +131 -0
- package/dist/organisms/DropdownMenu.d.ts +30 -0
- package/dist/organisms/DropdownMenu.js +91 -0
- package/dist/organisms/Form.d.ts +114 -0
- package/dist/organisms/Form.js +151 -0
- package/dist/organisms/HoverCard.d.ts +30 -0
- package/dist/organisms/HoverCard.js +68 -0
- package/dist/organisms/Menubar.d.ts +28 -0
- package/dist/organisms/Menubar.js +147 -0
- package/dist/organisms/MessageScroller.d.ts +36 -0
- package/dist/organisms/MessageScroller.js +124 -0
- package/dist/organisms/NavigationMenu.d.ts +41 -0
- package/dist/organisms/NavigationMenu.js +139 -0
- package/dist/organisms/Popover.d.ts +33 -0
- package/dist/organisms/Popover.js +58 -0
- package/dist/organisms/Questionnaire.d.ts +74 -0
- package/dist/organisms/Questionnaire.js +235 -0
- package/dist/organisms/Select.d.ts +47 -0
- package/dist/organisms/Select.js +250 -0
- package/dist/organisms/Sheet.d.ts +34 -0
- package/dist/organisms/Sheet.js +107 -0
- package/dist/organisms/Sidebar.d.ts +100 -0
- package/dist/organisms/Sidebar.js +228 -0
- package/dist/organisms/Toaster.d.ts +67 -0
- package/dist/organisms/Toaster.js +189 -0
- package/dist/organisms/Tooltip.d.ts +39 -0
- package/dist/organisms/Tooltip.js +94 -0
- package/dist/organisms/index.d.ts +40 -0
- package/dist/organisms/index.js +40 -0
- package/dist/primitives/controllable.d.ts +38 -0
- package/dist/primitives/controllable.js +43 -0
- package/dist/primitives/dismissable.d.ts +53 -0
- package/dist/primitives/dismissable.js +158 -0
- package/dist/primitives/floating.d.ts +116 -0
- package/dist/primitives/floating.js +289 -0
- package/dist/primitives/floatingSurface.d.ts +56 -0
- package/dist/primitives/floatingSurface.js +142 -0
- package/dist/primitives/focusTrap.d.ts +47 -0
- package/dist/primitives/focusTrap.js +101 -0
- package/dist/primitives/focusable.d.ts +53 -0
- package/dist/primitives/focusable.js +109 -0
- package/dist/primitives/index.d.ts +27 -0
- package/dist/primitives/index.js +27 -0
- package/dist/primitives/menu.d.ts +102 -0
- package/dist/primitives/menu.js +356 -0
- package/dist/primitives/modalSurface.d.ts +54 -0
- package/dist/primitives/modalSurface.js +146 -0
- package/dist/primitives/portal.d.ts +34 -0
- package/dist/primitives/portal.js +42 -0
- package/dist/primitives/presence.d.ts +45 -0
- package/dist/primitives/presence.js +152 -0
- package/dist/primitives/rovingFocus.d.ts +47 -0
- package/dist/primitives/rovingFocus.js +176 -0
- package/dist/primitives/scrollLock.d.ts +21 -0
- package/dist/primitives/scrollLock.js +75 -0
- package/dist/primitives/typeahead.d.ts +39 -0
- package/dist/primitives/typeahead.js +123 -0
- package/dist/templates/AppShell.d.ts +30 -0
- package/dist/templates/AppShell.js +42 -0
- package/dist/templates/AuthLayout.d.ts +23 -0
- package/dist/templates/AuthLayout.js +42 -0
- package/dist/templates/SettingsLayout.d.ts +27 -0
- package/dist/templates/SettingsLayout.js +42 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.js +16 -0
- package/nebula.css +2 -0
- package/package.json +92 -0
- package/registry.json +1107 -0
- package/src/adapters/css.ts +75 -0
- package/src/adapters/index.ts +40 -0
- package/src/adapters/tailwind.ts +134 -0
- package/src/adapters/types.ts +53 -0
- package/src/adapters/unocss.ts +116 -0
- package/src/atoms/AspectRatio.ts +28 -0
- package/src/atoms/Avatar.ts +67 -0
- package/src/atoms/Badge.ts +46 -0
- package/src/atoms/Button.ts +96 -0
- package/src/atoms/Checkbox.ts +76 -0
- package/src/atoms/Input.ts +76 -0
- package/src/atoms/Kbd.ts +28 -0
- package/src/atoms/Label.ts +38 -0
- package/src/atoms/Marker.ts +79 -0
- package/src/atoms/NativeSelect.ts +115 -0
- package/src/atoms/Progress.ts +64 -0
- package/src/atoms/ScrollArea.ts +53 -0
- package/src/atoms/Separator.ts +36 -0
- package/src/atoms/Skeleton.ts +24 -0
- package/src/atoms/Slider.ts +65 -0
- package/src/atoms/Spinner.ts +28 -0
- package/src/atoms/Switch.ts +56 -0
- package/src/atoms/Textarea.ts +58 -0
- package/src/atoms/Toggle.ts +67 -0
- package/src/atoms/index.ts +62 -0
- package/src/cli/add.ts +126 -0
- package/src/cli/index.ts +22 -0
- package/src/cli/registry.ts +116 -0
- package/src/config.ts +102 -0
- package/src/configure.ts +127 -0
- package/src/index.ts +53 -0
- package/src/lib/children.ts +43 -0
- package/src/lib/cn.ts +14 -0
- package/src/lib/cva.ts +249 -0
- package/src/lib/icons.ts +138 -0
- package/src/lib/id.ts +44 -0
- package/src/lib/index.ts +21 -0
- package/src/lib/motion.ts +48 -0
- package/src/lib/props.ts +83 -0
- package/src/lib/styled.ts +60 -0
- package/src/molecules/Accordion.ts +161 -0
- package/src/molecules/Alert.ts +58 -0
- package/src/molecules/Attachment.ts +132 -0
- package/src/molecules/Breadcrumb.ts +107 -0
- package/src/molecules/Bubble.ts +134 -0
- package/src/molecules/ButtonGroup.ts +55 -0
- package/src/molecules/Card.ts +42 -0
- package/src/molecules/Collapsible.ts +89 -0
- package/src/molecules/Empty.ts +43 -0
- package/src/molecules/Field.ts +131 -0
- package/src/molecules/InputGroup.ts +62 -0
- package/src/molecules/InputOTP.ts +166 -0
- package/src/molecules/Item.ts +89 -0
- package/src/molecules/Message.ts +127 -0
- package/src/molecules/Pagination.ts +197 -0
- package/src/molecules/RadioGroup.ts +104 -0
- package/src/molecules/Resizable.ts +166 -0
- package/src/molecules/Table.ts +118 -0
- package/src/molecules/Tabs.ts +148 -0
- package/src/molecules/ToggleGroup.ts +109 -0
- package/src/molecules/Typography.ts +94 -0
- package/src/molecules/index.ts +144 -0
- package/src/organisms/AlertDialog.ts +133 -0
- package/src/organisms/Calendar.ts +470 -0
- package/src/organisms/Carousel.ts +161 -0
- package/src/organisms/Chart.ts +257 -0
- package/src/organisms/Combobox.ts +140 -0
- package/src/organisms/Command.ts +211 -0
- package/src/organisms/CommandDialog.ts +149 -0
- package/src/organisms/ContextMenu.ts +125 -0
- package/src/organisms/DataTable.ts +328 -0
- package/src/organisms/DatePicker.ts +141 -0
- package/src/organisms/DateRangePicker.ts +149 -0
- package/src/organisms/Dialog.ts +162 -0
- package/src/organisms/Drawer.ts +167 -0
- package/src/organisms/DropdownMenu.ts +112 -0
- package/src/organisms/Form.ts +217 -0
- package/src/organisms/HoverCard.ts +92 -0
- package/src/organisms/Menubar.ts +167 -0
- package/src/organisms/MessageScroller.ts +145 -0
- package/src/organisms/NavigationMenu.ts +184 -0
- package/src/organisms/Popover.ts +84 -0
- package/src/organisms/Questionnaire.ts +322 -0
- package/src/organisms/Select.ts +286 -0
- package/src/organisms/Sheet.ts +144 -0
- package/src/organisms/Sidebar.ts +336 -0
- package/src/organisms/Toaster.ts +246 -0
- package/src/organisms/Tooltip.ts +124 -0
- package/src/organisms/index.ts +134 -0
- package/src/primitives/controllable.ts +64 -0
- package/src/primitives/dismissable.ts +195 -0
- package/src/primitives/floating.ts +453 -0
- package/src/primitives/floatingSurface.ts +209 -0
- package/src/primitives/focusTrap.ts +132 -0
- package/src/primitives/focusable.ts +108 -0
- package/src/primitives/index.ts +88 -0
- package/src/primitives/menu.ts +491 -0
- package/src/primitives/modalSurface.ts +199 -0
- package/src/primitives/portal.ts +61 -0
- package/src/primitives/presence.ts +185 -0
- package/src/primitives/rovingFocus.ts +212 -0
- package/src/primitives/scrollLock.ts +79 -0
- package/src/primitives/typeahead.ts +156 -0
- package/src/templates/AppShell.ts +56 -0
- package/src/templates/AuthLayout.ts +66 -0
- package/src/templates/SettingsLayout.ts +63 -0
- package/src/templates/index.ts +21 -0
- package/theme.css +151 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C9up
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# @c9up/nebula
|
|
2
|
+
|
|
3
|
+
> shadcn/ui, ported to Aurora, organised as atomic design. Zero runtime dependencies, copy-the-source registry, swappable CSS engine.
|
|
4
|
+
|
|
5
|
+
Part of **[Ream](https://github.com/C9up/ream)** — a Rust-powered, AdonisJS-compatible Node.js framework. Independent, publishable package.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
shadcn/ui is React. [Aurora](https://github.com/C9up/aurora) is a tagged-template DOM runtime with signals and no build step. nebula is the shadcn component set — the same markup, the same Tailwind classes, the same behaviour — written for Aurora.
|
|
10
|
+
|
|
11
|
+
Sixty-nine components across four atomic layers, plus the headless behaviour layer Radix would otherwise provide. Every component in shadcn's registry has a counterpart; several are deliberately narrower, and the [parity section](#parity-with-shadcn) says exactly which.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ream add @c9up/nebula --adapter tailwind
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The same one-step install as every other ream package: it installs the package and dispatches to nebula's `configure()` hook, which writes `config/nebula.ts` and the stylesheet for your chosen engine, then prints the packages to install and the build command to register. It installs nothing itself and edits no `package.json` of yours.
|
|
20
|
+
|
|
21
|
+
Changing the adapter later is `ream configure @c9up/nebula --adapter unocss`.
|
|
22
|
+
|
|
23
|
+
## Two ways to use it
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// Import it — quickest to try
|
|
27
|
+
import { Button, Card, CardHeader } from '@c9up/nebula'
|
|
28
|
+
|
|
29
|
+
// Or take the source — what the library is really for
|
|
30
|
+
// $ ream nebula:add button card
|
|
31
|
+
import { Button } from '#pages/atoms/Button.js'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`ream nebula:add` copies the component's source into your project and hands it over. No version, no upgrade path, no wrapper to fight when a design needs one class changed. That is shadcn's premise and nebula keeps it.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
ream nebula:list # everything in the registry
|
|
38
|
+
ream nebula:list --layer organisms
|
|
39
|
+
ream nebula:add dialog data-table # copies both, plus what they depend on
|
|
40
|
+
ream nebula:add button --force # overwrite your edited copy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Subcommands on the ream binary, not a CLI of nebula's own — the same place
|
|
44
|
+
`ream nova:vapid:generate` lives. The registry logic stays in this package; the
|
|
45
|
+
binary dispatches to it.
|
|
46
|
+
|
|
47
|
+
Copies mirror the package's own layout, so `atoms/Button` finds `../lib/cva.js` for the same reason it does inside nebula. **No import is ever rewritten** — that is where a copy-the-source CLI usually accumulates its edge cases.
|
|
48
|
+
|
|
49
|
+
**JavaScript by default.** An Aurora app serves `resources/pages` to the browser unbuilt — that zero-build-step promise is the framework's premise — so TypeScript dropped into that tree does not run. `ream nebula:add` copies the compiled output instead: valid ESM, `.js` specifiers already correct, and every doc comment intact, so what you own is still readable source. The language is inferred from what your components directory already holds; `--ts` and `--js` override it.
|
|
50
|
+
|
|
51
|
+
## Zero runtime dependencies
|
|
52
|
+
|
|
53
|
+
shadcn stands on Radix, `clsx`, `tailwind-merge`, `class-variance-authority`, `lucide-react`, `@floating-ui/dom`, `cmdk`, `sonner`, `recharts`, `@tanstack/react-table` and `react-day-picker`. None are React-agnostic, and this workspace had already decided `cn` was worth writing by hand rather than installing two packages. So:
|
|
54
|
+
|
|
55
|
+
| shadcn dependency | nebula |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `clsx` + `tailwind-merge` | `cn` from `@c9up/aurora` — already written from scratch there |
|
|
58
|
+
| `class-variance-authority` | `lib/cva.ts` — reimplemented, and it runs its result through `cn` |
|
|
59
|
+
| Radix UI | `primitives/` — focus trap, dismissable layers, roving focus, type-ahead, presence, portals |
|
|
60
|
+
| `@floating-ui/dom` | `primitives/floating.ts` — offset, flip, shift, arrow, available height |
|
|
61
|
+
| `lucide-react` | `lib/icons.ts` — the eighteen glyphs the set needs, inlined |
|
|
62
|
+
| `cmdk` | `organisms/Command.ts` |
|
|
63
|
+
| `sonner` | `organisms/Toaster.ts` |
|
|
64
|
+
| `react-day-picker` + `date-fns` | `organisms/Calendar.ts` — `Date` and `Intl` |
|
|
65
|
+
| `@tanstack/react-table` | `organisms/DataTable.ts` — sort, filter, page, select |
|
|
66
|
+
| `recharts` | `organisms/Chart.ts` — line, area and bar, as inline SVG |
|
|
67
|
+
| `react-hook-form` | `form()` from `@c9up/aurora`, bound by `organisms/Form.ts` |
|
|
68
|
+
| `tw-animate-css` | four keyframes in `theme.css` |
|
|
69
|
+
|
|
70
|
+
Several of those are narrower than what they replace — see [parity with shadcn](#parity-with-shadcn) for the full list rather than a reassuring summary.
|
|
71
|
+
|
|
72
|
+
## Parity with shadcn
|
|
73
|
+
|
|
74
|
+
Checked against shadcn's published component list, not from memory. Every one of its components has a counterpart here — including the conversational set (`Bubble`, `Message`, `MessageScroller`, `Attachment`, `Marker`), `NativeSelect` and `Questionnaire` — and the ~40 simple ones are faithful down to the class strings, the variants and the ARIA attributes.
|
|
75
|
+
|
|
76
|
+
Two of shadcn's entries have no direct counterpart on purpose. `DirectionProvider` is React context; Aurora has none, and the direction belongs on `<html dir>` — what it was really buying is [RTL support](#right-to-left), which is handled in the placement engine instead. `Form` has been folded into `Field` upstream; nebula ships both, with `Form` binding Aurora's own form controller. The components shadcn builds by wrapping a third-party library are reimplementations, and they are narrower. Stated plainly, because "complete port" would not be true:
|
|
77
|
+
|
|
78
|
+
| Component | shadcn | nebula |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| Chart | Recharts, in full | line, area and bar over one categorical axis |
|
|
81
|
+
| DataTable | TanStack Table (column grouping, virtualisation, pinning, faceted filters, server-side) | sort, filter, page, select — in memory |
|
|
82
|
+
| Sidebar | ~15 parts | the parts that are not re-skinned atoms — see below |
|
|
83
|
+
| Carousel | embla (loop, autoplay, N slides per view) | scroll-snap, one slide per view, no loop or autoplay |
|
|
84
|
+
| Toaster | sonner (promise toasts, arbitrary JSX, multiple positions) | four variants, action, pause on hover |
|
|
85
|
+
| Resizable | arbitrary nesting, persisted layouts, collapse-to-zero | two panes, one handle |
|
|
86
|
+
| Combobox | single, multi-select and creatable recipes | single-select |
|
|
87
|
+
| ScrollArea | scrollbars redrawn by Radix | native scrollbars, styled |
|
|
88
|
+
| Calendar | react-day-picker, every selection mode | single date and range; no multi-month, no multi-select |
|
|
89
|
+
| Questionnaire | branching logic, validation schemas | linear steps; single, multiple, freeform, skippable |
|
|
90
|
+
| Bubble / Message | rich composition slots | the parts nebula's own layout needs |
|
|
91
|
+
|
|
92
|
+
Two API-wide differences, both consequences of the runtime rather than choices about scope: there is no `asChild` (a compiled template has no element to clone), and compound components take data rather than children (Aurora has no React context).
|
|
93
|
+
|
|
94
|
+
**The Sidebar deserves its own note**, because porting it part-for-part would have fought the atomic taxonomy rather than following it. `SidebarInput`, `SidebarSeparator` and `SidebarMenuSkeleton` are the existing `Input`, `Separator` and `Skeleton` atoms with a prefix — redeclaring them would break the composition rule the whole library is organised on. `SidebarProvider` is React context, and nebula's sidebar owns its own shared signal instead. `SidebarInset` is the content column beside the rail, which is `AppShell`, a template. What was genuinely missing and has been added: `SidebarMenuSub`, `SidebarMenuSubItem`, `SidebarMenuAction`, a badge slot, and tooltips when the rail is collapsed.
|
|
95
|
+
|
|
96
|
+
## Right to left
|
|
97
|
+
|
|
98
|
+
The placement engine mirrors itself. Placements are written physically —
|
|
99
|
+
`"right-start"` for a submenu — because that is what reads clearly at the call
|
|
100
|
+
site, and `resolvePosition` flips them when the anchor computes to
|
|
101
|
+
`direction: rtl`. `autoPosition` reads that off the anchor on every update, so
|
|
102
|
+
no component passes a flag and a language switcher flipped mid-session moves
|
|
103
|
+
open surfaces with it.
|
|
104
|
+
|
|
105
|
+
The mirror is not symmetric, which is the part worth knowing: for `left`/`right`
|
|
106
|
+
the *side* swaps and the alignment is untouched; for `top`/`bottom` the side
|
|
107
|
+
stays and the *alignment* swaps. Mirroring both halves of `bottom-start` would
|
|
108
|
+
land it back where it started.
|
|
109
|
+
|
|
110
|
+
Components use logical properties (`ms-*`, `me-*`, `start-*`, `end-*`) wherever
|
|
111
|
+
a side is meant relative to the text, so a `Bubble` aligned to the end sits
|
|
112
|
+
right in English and left in Arabic. Where a side is a genuine layout choice —
|
|
113
|
+
which edge a `Sheet` enters from, which side a `Sidebar` occupies — it stays
|
|
114
|
+
physical, because that is what the caller means.
|
|
115
|
+
|
|
116
|
+
## Choose your CSS engine
|
|
117
|
+
|
|
118
|
+
nebula declares **no CSS dependency at all**, not even a peer one. You install the engine you want; `config/nebula.ts` names it; nebula generates the matching stubs and build command. Same arrangement AdonisJS uses for its asset bundler.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// config/nebula.ts
|
|
122
|
+
import { defineConfig } from '@c9up/nebula'
|
|
123
|
+
|
|
124
|
+
export default defineConfig({
|
|
125
|
+
adapter: 'tailwind', // 'tailwind' | 'unocss' | 'css'
|
|
126
|
+
paths: {
|
|
127
|
+
components: 'resources/pages',
|
|
128
|
+
css: 'resources/css/app.css',
|
|
129
|
+
output: 'public/app.css',
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Adapter | What it does | You install |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `tailwind` | Tailwind v4, configured in CSS. What shadcn itself targets. | `tailwindcss @tailwindcss/cli` |
|
|
137
|
+
| `unocss` | `presetWind4` — same class syntax, no PostCSS, faster. | `unocss @unocss/cli` |
|
|
138
|
+
| `css` | Nothing. nebula ships a prebuilt stylesheet. | — |
|
|
139
|
+
|
|
140
|
+
All three consume the same class names, which is what lets one set of components serve all of them. Switching is a one-word change plus `ream configure @c9up/nebula`.
|
|
141
|
+
|
|
142
|
+
**The `css` adapter's limit, stated plainly.** `nebula.css` is compiled at nebula's release time and covers the components as published. Edit a copied component to add a utility nebula never used and nothing emits it — the class silently does nothing. Use it when you take the components as they are; use `tailwind` or `unocss` when you intend to retune them.
|
|
143
|
+
|
|
144
|
+
An engine with a different authoring model — Panda's recipes, StyleX — cannot go behind this interface. It would need a second version of every component.
|
|
145
|
+
|
|
146
|
+
## Atomic design
|
|
147
|
+
|
|
148
|
+
shadcn is a flat `ui/` directory. nebula sorts the same components into layers, and the layer is a property of the component: `nebula add button` knows Button is an atom.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
resources/pages/
|
|
152
|
+
├── lib/ cn, cva, icons, ids, reactive props
|
|
153
|
+
├── primitives/ the headless layer — focus, dismissal, placement, presence
|
|
154
|
+
├── atoms/ one element, composing nothing from nebula
|
|
155
|
+
├── molecules/ assembles atoms, or owns state across several elements
|
|
156
|
+
├── organisms/ portals, traps focus, floats, or coordinates molecules
|
|
157
|
+
└── templates/ page skeletons
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The rule is composition, not complexity. Slider is an atom though it is interactive, because it is one input. Card is a molecule though it is trivial, because it assembles parts.
|
|
161
|
+
|
|
162
|
+
<details>
|
|
163
|
+
<summary><strong>All 69 components</strong></summary>
|
|
164
|
+
|
|
165
|
+
**atoms (19)** — AspectRatio, Avatar, Badge, Button, Checkbox, Input, Kbd, Label, Marker, NativeSelect, Progress, ScrollArea, Separator, Skeleton, Slider, Spinner, Switch, Textarea, Toggle
|
|
166
|
+
|
|
167
|
+
**molecules (21)** — Accordion, Alert, Attachment, Breadcrumb, Bubble, ButtonGroup, Card, Collapsible, Empty, Field, InputGroup, InputOTP, Item, Message, Pagination, RadioGroup, Resizable, Table, Tabs, ToggleGroup, Typography
|
|
168
|
+
|
|
169
|
+
**organisms (26)** — AlertDialog, Calendar, Carousel, Chart, Combobox, Command, CommandDialog, ContextMenu, DataTable, DatePicker, DateRangePicker, Dialog, Drawer, DropdownMenu, Form, HoverCard, Menubar, MessageScroller, NavigationMenu, Popover, Questionnaire, Select, Sheet, Sidebar, Toaster, Tooltip
|
|
170
|
+
|
|
171
|
+
**templates (3)** — AppShell, AuthLayout, SettingsLayout
|
|
172
|
+
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
## The API difference
|
|
176
|
+
|
|
177
|
+
shadcn composes through React context:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<Tabs defaultValue="account">
|
|
181
|
+
<TabsList><TabsTrigger value="account">Account</TabsTrigger></TabsList>
|
|
182
|
+
<TabsContent value="account">…</TabsContent>
|
|
183
|
+
</Tabs>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Aurora has no context, and the workarounds — a factory returning bound parts, a handle threaded through props — are more machinery for less clarity. So compound components take data:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
Tabs({
|
|
190
|
+
defaultValue: 'account',
|
|
191
|
+
items: [
|
|
192
|
+
{ value: 'account', label: 'Account', content: html`…` },
|
|
193
|
+
{ value: 'password', label: 'Password', content: html`…` },
|
|
194
|
+
],
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The rendered markup is unchanged, so shadcn's CSS and its examples still read across. Free-form containers take named slots instead:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
Dialog({
|
|
202
|
+
trigger: 'Edit profile',
|
|
203
|
+
title: 'Edit profile',
|
|
204
|
+
description: "Make changes here. Click save when you're done.",
|
|
205
|
+
children: [TextField({ bind: bind(profile, 'name'), label: 'Name' })],
|
|
206
|
+
footer: SubmitButton({ form: profile, label: 'Save' }),
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
There is no `asChild`. React's Slot clones an element and merges props into it; Aurora templates are compiled markup with nothing to clone. Where shadcn writes `<Button asChild><a/></Button>`, nebula exports the variants:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
html`<a href="/docs" class="${buttonVariants({ variant: 'outline' })}">Docs</a>`
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Reactive props
|
|
217
|
+
|
|
218
|
+
Aurora never re-renders. Any prop that can change is `Reactive<T>` — pass a constant when it never moves, an accessor when it does:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
Button({ disabled: true }) // static
|
|
222
|
+
Button({ disabled: () => form.submitting() }) // live
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A value read once at setup is frozen for the lifetime of the node, so `disabled: form.submitting()` is a bug that only shows after the first submit.
|
|
226
|
+
|
|
227
|
+
## Accessibility
|
|
228
|
+
|
|
229
|
+
The headless layer is most of this package, and it is where shadcn's behaviour actually lives. What is implemented, rather than approximated:
|
|
230
|
+
|
|
231
|
+
- **Focus trap** — Tab wraps, focus returns to the trigger, and a `focusin` handler catches focus arriving by any other route.
|
|
232
|
+
- **Modal** — the page behind is `aria-hidden`, not merely unreachable by Tab. Trapping keyboard focus does nothing for a reader navigating by landmark.
|
|
233
|
+
- **Dismissable layers** — one stack. Escape reaches the topmost layer that accepts it; a pointer outside closes layers above the one it landed in and no further.
|
|
234
|
+
- **Roving focus** — a menu, tab list or toolbar is one tab stop.
|
|
235
|
+
- **Type-ahead** — accumulating buffer, and a repeated letter cycles.
|
|
236
|
+
- **Charts** — the same data is emitted as a visually hidden `<table>`. No ARIA makes an SVG readable.
|
|
237
|
+
- **Live regions** — `polite` for toasts, `assertive` for errors, mounted empty before anything arrives.
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pnpm test # 391 unit tests
|
|
243
|
+
pnpm typecheck
|
|
244
|
+
pnpm lint
|
|
245
|
+
pnpm registry # regenerate registry.json from the source tree
|
|
246
|
+
pnpm css # freeze nebula.css for the `css` adapter
|
|
247
|
+
pnpm build
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`registry.json` is derived from the imports rather than maintained by hand, and a test asserts that every file an item ships actually resolves — the failure it guards is otherwise silent, showing up in a user's build rather than here.
|
|
251
|
+
|
|
252
|
+
Coverage sits around 83% of statements. The shape matters more than the number: every component is mounted and unmounted by `render-smoke.test.ts`, the shared surfaces and the headless primitives are tested directly, and the overlays are opened rather than only rendered closed. What the suite cannot reach is pointer-drag — happy-dom has no `setPointerCapture`, so Drawer's swipe-to-dismiss and Resizable's drag are covered by their keyboard paths only.
|
|
253
|
+
|
|
254
|
+
## Licence
|
|
255
|
+
|
|
256
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
import type { StyleAdapter } from "./types.js";
|
|
33
|
+
export declare const cssAdapter: StyleAdapter;
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
function stylesheet() {
|
|
33
|
+
return `/*
|
|
34
|
+
* nebula, engine-free.
|
|
35
|
+
*
|
|
36
|
+
* \`nebula.css\` is prebuilt and shipped with the package — no scanner runs in
|
|
37
|
+
* this project, so nothing here needs a build step.
|
|
38
|
+
*
|
|
39
|
+
* It covers nebula's components as published. A utility you add to a copied
|
|
40
|
+
* component will not be emitted; write the rule below by hand, or switch the
|
|
41
|
+
* adapter in \`config/nebula.ts\` to \`tailwind\` or \`unocss\`.
|
|
42
|
+
*/
|
|
43
|
+
@import "@c9up/nebula/nebula.css";
|
|
44
|
+
|
|
45
|
+
/* Your own styles go here. */
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
export const cssAdapter = {
|
|
49
|
+
name: "css",
|
|
50
|
+
summary: "No engine and no build step — nebula's prebuilt stylesheet, imported as-is.",
|
|
51
|
+
packages: [],
|
|
52
|
+
files(config) {
|
|
53
|
+
return [
|
|
54
|
+
{ path: config.paths.css, contents: stylesheet(), skipIfExists: true },
|
|
55
|
+
];
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Nothing to compile.
|
|
59
|
+
*
|
|
60
|
+
* `null` rather than a copy command: the app serves the stylesheet straight
|
|
61
|
+
* from the package, so there is no compile step for `ream build` to run and
|
|
62
|
+
* none should be registered.
|
|
63
|
+
*/
|
|
64
|
+
commands() {
|
|
65
|
+
return null;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
import type { AdapterName } from "../config.js";
|
|
9
|
+
import type { StyleAdapter } from "./types.js";
|
|
10
|
+
export { cssAdapter } from "./css.js";
|
|
11
|
+
export { tailwindAdapter } from "./tailwind.js";
|
|
12
|
+
export type { AdapterCommand, GeneratedFile, StyleAdapter, } from "./types.js";
|
|
13
|
+
export { unocssAdapter } from "./unocss.js";
|
|
14
|
+
export declare function adapterFor(name: AdapterName): StyleAdapter;
|
|
15
|
+
export declare function adapterNames(): readonly AdapterName[];
|
|
16
|
+
export declare function isAdapterName(value: string): value is AdapterName;
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
import { cssAdapter } from "./css.js";
|
|
9
|
+
import { tailwindAdapter } from "./tailwind.js";
|
|
10
|
+
import { unocssAdapter } from "./unocss.js";
|
|
11
|
+
export { cssAdapter } from "./css.js";
|
|
12
|
+
export { tailwindAdapter } from "./tailwind.js";
|
|
13
|
+
export { unocssAdapter } from "./unocss.js";
|
|
14
|
+
const adapters = {
|
|
15
|
+
tailwind: tailwindAdapter,
|
|
16
|
+
unocss: unocssAdapter,
|
|
17
|
+
css: cssAdapter,
|
|
18
|
+
};
|
|
19
|
+
export function adapterFor(name) {
|
|
20
|
+
return adapters[name];
|
|
21
|
+
}
|
|
22
|
+
export function adapterNames() {
|
|
23
|
+
return Object.keys(adapters).filter(isAdapterName);
|
|
24
|
+
}
|
|
25
|
+
export function isAdapterName(value) {
|
|
26
|
+
return value === "tailwind" || value === "unocss" || value === "css";
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
import type { StyleAdapter } from "./types.js";
|
|
25
|
+
export declare const tailwindAdapter: StyleAdapter;
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
function stylesheet(config) {
|
|
25
|
+
return `@import "tailwindcss";
|
|
26
|
+
@import "@c9up/nebula/theme.css";
|
|
27
|
+
|
|
28
|
+
/* Scan the component tree so the utilities they use are emitted. */
|
|
29
|
+
@source "${relativeFromCss(config)}";
|
|
30
|
+
|
|
31
|
+
/* Class-based dark mode: \`dark:\` applies under any \`.dark\` ancestor. */
|
|
32
|
+
@custom-variant dark (&:is(.dark *));
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Map nebula's tokens onto Tailwind's namespaces.
|
|
36
|
+
*
|
|
37
|
+
* \`inline\` matters: without it Tailwind copies the *values* at build time and
|
|
38
|
+
* the \`.dark\` overrides never reach the utilities, so dark mode changes the
|
|
39
|
+
* custom properties and nothing else.
|
|
40
|
+
*/
|
|
41
|
+
@theme inline {
|
|
42
|
+
--color-background: var(--background);
|
|
43
|
+
--color-foreground: var(--foreground);
|
|
44
|
+
--color-card: var(--card);
|
|
45
|
+
--color-card-foreground: var(--card-foreground);
|
|
46
|
+
--color-popover: var(--popover);
|
|
47
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
48
|
+
--color-primary: var(--primary);
|
|
49
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
50
|
+
--color-secondary: var(--secondary);
|
|
51
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
52
|
+
--color-muted: var(--muted);
|
|
53
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
54
|
+
--color-accent: var(--accent);
|
|
55
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
56
|
+
--color-destructive: var(--destructive);
|
|
57
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
58
|
+
--color-border: var(--border);
|
|
59
|
+
--color-input: var(--input);
|
|
60
|
+
--color-ring: var(--ring);
|
|
61
|
+
--color-chart-1: var(--chart-1);
|
|
62
|
+
--color-chart-2: var(--chart-2);
|
|
63
|
+
--color-chart-3: var(--chart-3);
|
|
64
|
+
--color-chart-4: var(--chart-4);
|
|
65
|
+
--color-chart-5: var(--chart-5);
|
|
66
|
+
--color-sidebar: var(--sidebar);
|
|
67
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
68
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
69
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
70
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
71
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
72
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
73
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
74
|
+
|
|
75
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
76
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
77
|
+
--radius-lg: var(--radius);
|
|
78
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `@source` is resolved relative to the stylesheet, not the project root.
|
|
84
|
+
*
|
|
85
|
+
* The two are rarely the same — `resources/css/app.css` scanning
|
|
86
|
+
* `resources/pages` needs `../pages` — and getting it wrong fails silently:
|
|
87
|
+
* Tailwind finds no files, emits no utilities, and the page renders unstyled
|
|
88
|
+
* with no error anywhere.
|
|
89
|
+
*/
|
|
90
|
+
function relativeFromCss(config) {
|
|
91
|
+
const cssDirectory = config.paths.css.split("/").slice(0, -1);
|
|
92
|
+
const target = config.paths.components.split("/");
|
|
93
|
+
let shared = 0;
|
|
94
|
+
while (shared < cssDirectory.length &&
|
|
95
|
+
shared < target.length &&
|
|
96
|
+
cssDirectory[shared] === target[shared]) {
|
|
97
|
+
shared += 1;
|
|
98
|
+
}
|
|
99
|
+
const up = new Array(cssDirectory.length - shared).fill("..");
|
|
100
|
+
return [...up, ...target.slice(shared)].join("/");
|
|
101
|
+
}
|
|
102
|
+
export const tailwindAdapter = {
|
|
103
|
+
name: "tailwind",
|
|
104
|
+
summary: "Tailwind CSS v4, configured in CSS. What shadcn/ui itself targets.",
|
|
105
|
+
packages: ["tailwindcss", "@tailwindcss/cli"],
|
|
106
|
+
files(config) {
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
path: config.paths.css,
|
|
110
|
+
contents: stylesheet(config),
|
|
111
|
+
skipIfExists: true,
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
commands(config) {
|
|
116
|
+
const base = ["-i", config.paths.css, "-o", config.paths.output];
|
|
117
|
+
return {
|
|
118
|
+
build: { command: "tailwindcss", args: base },
|
|
119
|
+
dev: { command: "tailwindcss", args: [...base, "--watch"] },
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
import type { ResolvedNebulaConfig } from "../config.js";
|
|
22
|
+
export interface GeneratedFile {
|
|
23
|
+
/** Path relative to the project root. */
|
|
24
|
+
path: string;
|
|
25
|
+
contents: string;
|
|
26
|
+
/**
|
|
27
|
+
* Leave an existing file alone. True for anything the user is expected to
|
|
28
|
+
* edit — their stylesheet is theirs once it exists.
|
|
29
|
+
*/
|
|
30
|
+
skipIfExists: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface AdapterCommand {
|
|
33
|
+
command: string;
|
|
34
|
+
args: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export interface StyleAdapter {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
/** One-line summary, printed by `nebula init`. */
|
|
39
|
+
readonly summary: string;
|
|
40
|
+
/** Packages the app must add itself. Empty for an engine-free adapter. */
|
|
41
|
+
readonly packages: readonly string[];
|
|
42
|
+
/** Stubs to write. */
|
|
43
|
+
files(config: ResolvedNebulaConfig): readonly GeneratedFile[];
|
|
44
|
+
/** Build and watch commands, or `null` when there is nothing to compile. */
|
|
45
|
+
commands(config: ResolvedNebulaConfig): {
|
|
46
|
+
build: AdapterCommand;
|
|
47
|
+
dev: AdapterCommand;
|
|
48
|
+
} | null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import type { StyleAdapter } from "./types.js";
|
|
17
|
+
export declare const unocssAdapter: StyleAdapter;
|