@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 +174 -15
- package/dist/base.css +116 -0
- package/dist/styles.css +2159 -0
- package/dist/switch.d.ts +8 -0
- package/dist/switch.js +44 -0
- package/dist/textarea.d.ts +5 -0
- package/dist/textarea.js +25 -0
- package/dist/theme.css +221 -0
- package/dist/tokens.css +414 -0
- package/package.json +18 -5
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
|
|
50
|
+
## Styling
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
tokens
|
|
52
|
-
|
|
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
|
-
|
|
56
|
+
Pick **one** of the two paths. Loading both means two copies of every utility.
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
+
}
|