@ekanos/ui 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,8 @@ One subpath per component, mirroring the `@kit/ui` names:
21
21
  | `@ekanos/ui/select` | `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, … |
22
22
  | `@ekanos/ui/form` | `Form`, `FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormDescription`, `FormMessage`, `useFormField` |
23
23
  | `@ekanos/ui/input` | `Input` |
24
+ | `@ekanos/ui/textarea` | `Textarea` |
25
+ | `@ekanos/ui/switch` | `Switch` |
24
26
  | `@ekanos/ui/dropdown-menu` | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, … |
25
27
  | `@ekanos/ui/label` | `Label` |
26
28
  | `@ekanos/ui/skeleton` | `Skeleton` |
@@ -45,23 +47,157 @@ implementations here are original, and `pack:test` fails the build if any
45
47
  `packages/ui/src/makerkit` source is ever inlined into `dist/`. Inside the
46
48
  monorepo, first-party code keeps using `@kit/ui` as before.
47
49
 
48
- ## Styling: this package ships NO CSS
50
+ ## Styling
49
51
 
50
- Components emit Tailwind class strings referencing Fusion's **semantic design
51
- tokens** (`bg-primary`, `text-muted-foreground`, `border-border`, …). They
52
- render correctly only inside a host that owns that CSS — the Fusion app or the
53
- integration dev harness. Nothing here reads or requires the host's
54
- `theme-tokens.ts` runtime; the contract is purely "the host page loads
55
- Fusion's stylesheet".
52
+ Every component emits Tailwind class strings bound to Fusion's semantic design
53
+ tokens (`bg-primary`, `text-muted-foreground`, `border-border`, `shadow-card`,
54
+ …), so something has to define those. This package ships that CSS.
56
55
 
57
- Two host-provided assets to know about:
56
+ Pick **one** of the two paths. Loading both means two copies of every utility.
58
57
 
59
- - **Tailwind tokens**: the host defines the semantic token variables and
60
- utilities. Without them the components render unstyled HTML.
61
- - **Icons are Font Awesome**: `Icon` renders `<i class="fa-solid fa-house">`
62
- styled by the Font Awesome Pro web-font CSS the host loads. Legacy PascalCase
63
- Lucide names are translated to FA equivalents via a bundled map; there is no
64
- `lucide-react` at runtime.
58
+ ### You do NOT use Tailwind import the compiled stylesheet
59
+
60
+ ```ts
61
+ import '@ekanos/ui/styles.css';
62
+ ```
63
+
64
+ Self-contained: Tailwind's Preflight, the design tokens, light and dark, and
65
+ every utility class the components in this package emit — all pre-compiled.
66
+ Nothing else to install, no build step, no config. Works in Next.js, Vite, CRA,
67
+ a plain `<link rel="stylesheet">`, anything.
68
+
69
+ ### You DO use Tailwind v4 — import the preset
70
+
71
+ ```css
72
+ @import 'tailwindcss';
73
+ @import '@ekanos/ui/theme.css';
74
+ ```
75
+
76
+ That registers the token map (`--color-primary` → `--primary`, the `--radius-*`
77
+ scale, `shadow-card`), the token values for both themes, the base rules, the
78
+ `dark:` variant, and an `@source` pointing at this package's bundles so your
79
+ Tailwind build generates the components' utilities itself. You then get the
80
+ Fusion tokens in **your own** class strings too.
81
+
82
+ Also published, if you want the pieces separately: `@ekanos/ui/tokens.css`
83
+ (just the custom properties, no Tailwind at-rules) and `@ekanos/ui/base.css`
84
+ (the base rules).
85
+
86
+ ### What `styles.css` does and does not contain
87
+
88
+ It contains the utilities **this package's components** emit — derived by
89
+ compiling every class string found in the built bundles. It is not a copy of
90
+ Tailwind.
91
+
92
+ So `<Button>` and `<Card>` render correctly, but a Tailwind class *you* write in
93
+ *your* component (`p-8`, `grid-cols-3`, `h-5 w-5`) has no rule in it and will do
94
+ nothing. That is inherent: a pre-compiled stylesheet cannot know class names
95
+ that don't exist yet. Two options, both fine:
96
+
97
+ - style your own markup with plain CSS or inline styles, or
98
+ - take the Tailwind path above, which is how Fusion itself works.
99
+
100
+ `styles.css` includes Tailwind's Preflight, which is a global reset — it will
101
+ normalise margins, headings and form controls across your whole page. If that
102
+ is unwelcome, use the Tailwind path and control layering yourself.
103
+
104
+ ### Page background and dark mode
105
+
106
+ The stylesheet will not repaint your page uninvited. To adopt Fusion's page
107
+ surface — which is what makes dark mode look right rather than dark cards
108
+ floating on a white page — add the opt-in class:
109
+
110
+ ```html
111
+ <body class="ekanos-root">
112
+ ```
113
+
114
+ Dark mode resolves in three states, in this order:
115
+
116
+ | State | Result |
117
+ | ----------------------------------------- | ---------------------------- |
118
+ | nothing set | light |
119
+ | `@media (prefers-color-scheme: dark)` | dark |
120
+ | `.dark` or `[data-theme='dark']` ancestor | dark, beating the media query |
121
+ | `.light` or `[data-theme='light']` on `:root` | light, beating the media query |
122
+
123
+ So a partner app with no theme toggle at all still follows the OS, and one with
124
+ a toggle (next-themes, or your own) works by setting the class. Fusion itself is
125
+ class-only; the media query is added here for apps that have no switcher.
126
+
127
+ ### Tokens: exactly the ones the components use
128
+
129
+ `tokens.css` is not a copy of Fusion's `shadcn-ui.css`. It carries the 25
130
+ semantic colour tokens the published components actually reference, plus their
131
+ paired foregrounds, `--radius`, and the one elevation level any of them uses
132
+ (`--el-card`, behind `shadow-card`). Fusion's chart, sidebar and assistant
133
+ tokens, and nine of its ten elevation levels, are deliberately absent.
134
+
135
+ The build enforces this in both directions: it re-compiles the same class
136
+ strings against Fusion's **full** token map and fails if a component has started
137
+ using a token this package doesn't ship (which would otherwise emit no CSS at
138
+ all, silently). See `scripts/build-css.mjs`.
139
+
140
+ ### Fonts
141
+
142
+ Fusion pairs **Figtree** (body) with **Bricolage Grotesque** (headings) and
143
+ loads them via `next/font`. This package only names them in `--font-sans` /
144
+ `--font-heading`, followed by a system fallback stack — no webfont is bundled or
145
+ fetched. For exact typographic parity, load them yourself:
146
+
147
+ ```html
148
+ <link
149
+ rel="stylesheet"
150
+ href="https://fonts.googleapis.com/css2?family=Figtree:wght@300..700&family=Bricolage+Grotesque:wght@200..800&display=swap"
151
+ />
152
+ ```
153
+
154
+ Without them, everything still renders — in your platform's UI font.
155
+
156
+ ### Icons: you must supply Font Awesome
157
+
158
+ `Icon` renders Font Awesome markup (`<i class="fa-solid fa-house">`) and this
159
+ package ships **no icon font**, because Fusion uses **Font Awesome Pro** (7.2.0,
160
+ © Fonticons, Inc.) under a commercial licence. Redistributing it inside an MIT
161
+ package would hand reuse rights we do not hold to everyone who installs this;
162
+ `pack:test` fails the build if any font data ever reaches `dist/`.
163
+
164
+ **If you hold a Font Awesome Pro licence**, load your own Pro CSS (or kit) and
165
+ every icon renders exactly as it does in Fusion.
166
+
167
+ **If you do not**, use Font Awesome Free — but read this, because the fit is
168
+ imperfect:
169
+
170
+ ```sh
171
+ npm install @fortawesome/fontawesome-free
172
+ ```
173
+
174
+ ```ts
175
+ import '@fortawesome/fontawesome-free/css/all.min.css';
176
+ ```
177
+
178
+ Free ships `fa-solid`, `fa-regular` and `fa-brands`. It has **no `fa-light` or
179
+ `fa-duotone` family**, and it is missing many Pro glyphs — and `fa-light` is
180
+ exactly what `@kit/ui`'s resolver falls back to for an icon name it doesn't
181
+ recognise. Force everything into a family Free actually has:
182
+
183
+ ```tsx
184
+ import { IconStyleOverrideProvider } from '@ekanos/ui/icon';
185
+
186
+ <IconStyleOverrideProvider value="solid">{children}</IconStyleOverrideProvider>;
187
+ ```
188
+
189
+ Any glyph Free still doesn't have falls back to a question mark rather than
190
+ vanishing (`--ekanos-icon-fa-fallback`, default `'\f059'`).
191
+
192
+ **If you load no Font Awesome at all**, icons do not disappear silently: this
193
+ package's CSS draws a placeholder box (`□`) in their place, so "I forgot the
194
+ icon font" is visibly different from "my code is broken". Change or disable it
195
+ with `--ekanos-icon-fallback`.
196
+
197
+ Note that FA glyphs are sized by `font-size`, not box dimensions, and `Icon`
198
+ derives that from the first `h-*` / `w-*` / `size-*` token in `className` in JS
199
+ — so `<Icon className="h-5 w-5" />` is the right *visual* size even on the
200
+ no-Tailwind path where those utilities have no CSS rule.
65
201
 
66
202
  ## Runtime notes
67
203
 
@@ -110,14 +246,37 @@ runtime is a declared dependency of this package.
110
246
  ## Building / verifying
111
247
 
112
248
  ```bash
113
- pnpm --filter @ekanos/ui build # tsup → dist/ (ESM + .d.ts per subpath)
249
+ pnpm --filter @ekanos/ui build # tsup → dist/, then scripts/build-css.mjs the stylesheets
114
250
  pnpm --filter @ekanos/ui pack:test # clean-room publish gate (see scripts/pack-test.mjs)
115
251
  ```
116
252
 
253
+ The build runs in two stages, in this order for a reason: `tsup` bundles the
254
+ components, then `scripts/build-css.mjs` scans the *built* `dist/*.js` for class
255
+ strings and compiles exactly those through Tailwind. Deriving the utility set
256
+ from the build output rather than a hand-kept list is what makes it impossible
257
+ to add a component whose classes have no CSS.
258
+
259
+ That script carries three fatal gates:
260
+
261
+ - **dangling** — every `var(--x)` the token map reaches for must be defined.
262
+ - **coverage** — the same class strings are re-compiled against Fusion's full
263
+ token map, and the build fails if a component has begun using a token this
264
+ package doesn't ship. Without this, such a utility would just silently emit
265
+ nothing.
266
+ - **lockstep** — `tokens.css` spells its dark values out twice (a media query
267
+ and an explicit selector, because CSS can't share a block between them); both
268
+ must stay in agreement.
269
+
117
270
  `pack:test` packs the tarball, asserts dist-only contents, fails on any
118
271
  `@kit/` leak, asserts `'use client'` preservation, then installs the tarball
119
272
  into a temp project outside the workspace and typechecks
120
273
  (`skipLibCheck: false`) + esbuild-bundles a consumer importing every subpath.
121
274
 
275
+ It also gates the stylesheets: all four must ship, clear a size floor, still
276
+ contain known utilities and both theme states, resolve through esbuild as
277
+ `@ekanos/ui/styles.css`, and carry **no** icon-font data — `@font-face`, a
278
+ webfont reference or a glyph table in `dist/` fails the build, for the same
279
+ licensing reason the Makerkit lineage check exists.
280
+
122
281
  `prepack` runs the build, so a publish can never ship a stale or missing
123
282
  `dist/`.
package/dist/base.css ADDED
@@ -0,0 +1,116 @@
1
+ /*
2
+ * @ekanos/ui — base rules the components depend on
3
+ * ------------------------------------------------------------------
4
+ * Plain CSS. Everything here is something a component's class strings assume
5
+ * about the page rather than state themselves. Kept deliberately short: a
6
+ * library stylesheet has no business restyling a host's page.
7
+ */
8
+
9
+ /*
10
+ * Default border colour.
11
+ *
12
+ * Tailwind's Preflight resets every element to `border: 0 solid`, which leaves
13
+ * `border-color` at `currentColor`. Several components then set only a border
14
+ * *width* and expect the theme colour: `Button variant="outline"` and
15
+ * `SelectTrigger` use a bare `border`, `Badge` uses `border border-transparent`.
16
+ * Without this rule those borders come out the colour of the label text.
17
+ *
18
+ * This is the same rule Fusion's own `@layer base` carries
19
+ * (apps/web/styles/globals.css).
20
+ */
21
+ *,
22
+ ::after,
23
+ ::before,
24
+ ::backdrop,
25
+ ::file-selector-button {
26
+ border-color: var(--border, currentColor);
27
+ }
28
+
29
+ /*
30
+ * Missing-icon placeholders.
31
+ *
32
+ * `@ekanos/ui/icon` renders Font Awesome markup — `<i class="fa-solid fa-house">`
33
+ * — and this package ships NO icon font. Fusion uses Font Awesome **Pro**,
34
+ * which is commercially licensed and cannot be redistributed inside an MIT
35
+ * package. See the README for what to load instead.
36
+ *
37
+ * That leaves two ways for an icon to come out blank, and both are silent, so
38
+ * there are two placeholders. Neither can change the rendering of a correctly
39
+ * licensed Pro host.
40
+ */
41
+
42
+ /*
43
+ * 1. No Font Awesome CSS at all. An `<i>` then has no content and no box, so
44
+ * icons render as *literally nothing* and a partner cannot tell "I forgot
45
+ * the icon font" apart from "my code is broken". This draws a placeholder
46
+ * box instead.
47
+ *
48
+ * `:where()` keeps the specificity at (0,0,1) — below Font Awesome's own
49
+ * `:is(…)::before` at (0,1,1) — so FA wins `content` outright the instant
50
+ * any Font Awesome stylesheet is present.
51
+ *
52
+ * BUT specificity only settles properties BOTH rules declare, and `content`
53
+ * is the ONLY one FA sets on `::before`. FA declares `font-family` and
54
+ * `font-weight` on the ELEMENT (`.fa-solid { font-family: var(--_fa-family) }`),
55
+ * never on the pseudo-element. So declaring them here left them uncontested
56
+ * at `::before` level — a declared value beats an inherited one regardless
57
+ * of specificity — and FA's correct codepoint got painted in system-ui,
58
+ * which has no glyph in the Private Use Area. The result was Chromium's
59
+ * missing-glyph hex box on every icon in any app that loads both this
60
+ * stylesheet and a real Font Awesome build.
61
+ *
62
+ * So this rule must NOT set the font. Inheriting is correct in both cases:
63
+ * with FA present the element carries the FA family and the glyph resolves;
64
+ * with FA absent the element inherits the body font, which does have U+25A1.
65
+ *
66
+ * Override the glyph with `--ekanos-icon-fallback` (any CSS <string>), or
67
+ * switch it off with `--ekanos-icon-fallback: ''`.
68
+ */
69
+ :where(i[class*='fa-']) {
70
+ display: inline-block;
71
+ font-style: normal;
72
+ line-height: 1;
73
+ text-align: center;
74
+ }
75
+
76
+ :where(i[class*='fa-'])::before {
77
+ content: var(--ekanos-icon-fallback, '\25a1');
78
+ font-style: normal;
79
+ }
80
+
81
+ /*
82
+ * 2. Font Awesome Free IS loaded, but the requested glyph is Pro-only. Free
83
+ * covers `fa-solid` / `fa-regular` / `fa-brands`; it has no `fa-light` or
84
+ * `fa-duotone` families and is missing many Pro glyphs — including
85
+ * `fa-light fa-shapes`, which is what this package's icon resolver falls
86
+ * back to for an unmapped name. Font Awesome drives its glyphs off a per-icon
87
+ * `--fa` custom property (`.fa-house { --fa: '\f015' }`) and renders
88
+ * `content: var(--fa)`, so a glyph Free doesn't define leaves `--fa` unset
89
+ * and `content` invalid — nothing renders, again silently.
90
+ *
91
+ * Giving `--fa` a zero-specificity default fixes that: any real Font Awesome
92
+ * icon class is (0,1,0) and beats it, so known glyphs are untouched, while
93
+ * an unknown one falls through to a question mark (`\f059`,
94
+ * `fa-circle-question`, which Free does ship). Matches the fallback the
95
+ * Fusion dev harness uses against Font Awesome Free.
96
+ */
97
+ :where(i[class*='fa-']) {
98
+ --fa: var(--ekanos-icon-fa-fallback, '\f059');
99
+ }
100
+
101
+ /*
102
+ * Opt-in page shell.
103
+ *
104
+ * Fusion's app sets `bg-background text-foreground` on `<body>`; this package
105
+ * will not do that to a host page uninvited. Put `class="ekanos-root"` on
106
+ * `<body>` (or on any wrapper around Ekanos UI) to adopt Fusion's page surface
107
+ * — which is what makes dark mode look right, rather than dark cards floating
108
+ * on a white page.
109
+ */
110
+ .ekanos-root {
111
+ background-color: var(--background);
112
+ color: var(--foreground);
113
+ font-family: var(--font-sans);
114
+ -webkit-font-smoothing: antialiased;
115
+ -moz-osx-font-smoothing: grayscale;
116
+ }