@lovett/ui 0.0.9 → 0.0.10
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/dist/index.d.ts +191 -156
- package/dist/index.js +227 -28
- package/dist/index.js.map +1 -1
- package/dist/styles.css +28 -0
- package/dist/tokens.css +114 -8
- package/package.json +1 -1
- package/src/__tests__/modal.test.tsx +180 -6
- package/src/code-block.tsx +15 -26
- package/src/icons/brand.tsx +81 -2
- package/src/index.ts +5 -0
- package/src/modal.tsx +165 -8
- package/src/page-shell.tsx +91 -2
- package/src/segmented-pill.tsx +33 -10
- package/src/styles.css +28 -0
- package/src/theme-v2.css +7 -245
- package/src/tokens.css +114 -8
- package/src/react-syntax-highlighter-prism.d.ts +0 -34
- package/src/v2/README.md +0 -208
- package/src/v2/__demo__/showcase.tsx +0 -1045
- package/src/v2/action.tsx +0 -91
- package/src/v2/callout.tsx +0 -76
- package/src/v2/document-section.tsx +0 -82
- package/src/v2/document-shell.tsx +0 -0
- package/src/v2/field-row.tsx +0 -113
- package/src/v2/icons.tsx +0 -165
- package/src/v2/index.ts +0 -147
- package/src/v2/layout.tsx +0 -293
- package/src/v2/progress-track.tsx +0 -89
- package/src/v2/stat-tile.tsx +0 -129
- package/src/v2/states.tsx +0 -271
- package/src/v2/status-pill.tsx +0 -74
- package/src/v2/theme.css +0 -1861
- package/src/v2/timeline.tsx +0 -81
- package/src/v2/tokens.ts +0 -228
package/src/v2/README.md
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# `@lovett/ui` v2
|
|
2
|
-
|
|
3
|
-
The document layer. Built for dense, text-heavy surfaces — briefs, checklists,
|
|
4
|
-
dossiers, report pages — where the job is to make a lot of information legible
|
|
5
|
-
rather than to entertain anyone.
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
import '@lovett/ui/src/v2/theme.css'
|
|
9
|
-
import { DocumentShell, DocumentSection, FieldRow } from '@lovett/ui/v2'
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
v2 is **additive**. Every token is namespaced `--lv-*`, every class `.lv-*`.
|
|
13
|
-
`theme.css` loads alongside v1's `tokens.css` and `styles.css` without changing
|
|
14
|
-
a single v1 pixel, so a page migrates one section at a time. Nothing in v1 was
|
|
15
|
-
touched.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## The surface ladder
|
|
20
|
-
|
|
21
|
-
This is the spine. Everything else is detail.
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
page → shell → section → card → [inset]
|
|
25
|
-
─────────────── brighter ──────────────► ◄─ recessed
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
| Tier | Token | What sits here |
|
|
29
|
-
| --- | --- | --- |
|
|
30
|
-
| page | `--lv-surface-page` | the viewport floor |
|
|
31
|
-
| shell | `--lv-surface-shell` | `DocumentShell` — the document's own field |
|
|
32
|
-
| section | `--lv-surface-section` | `Section` — a titled region |
|
|
33
|
-
| card | `--lv-surface-card` | `StatTile`, a tray |
|
|
34
|
-
| inset | `--lv-surface-inset` | `Panel` — **the recessed panel** |
|
|
35
|
-
|
|
36
|
-
Each tier is one notch brighter than the one behind it. `inset` deliberately
|
|
37
|
-
goes the other way, because a recessed panel *inside* a card is the single most
|
|
38
|
-
premium move in the system and recession is the only thing that makes it read
|
|
39
|
-
as recessed. Use `Panel` for the thing inside a card the reader actually came
|
|
40
|
-
for: the extracted quote, the computed figure, the diff, the raw payload.
|
|
41
|
-
|
|
42
|
-
**Depth is shadow plus a top-edge inner highlight, never a border.** A 1px
|
|
43
|
-
hairline used for elevation reads as a form field. Hairlines here are strictly
|
|
44
|
-
structural — dividers, table rules, field outlines, the `Timeline` rail.
|
|
45
|
-
|
|
46
|
-
Elevation runs `--lv-elev-0` → `-4` for lifting and `--lv-elev-inset` for
|
|
47
|
-
digging. In dark mode the layered soft shadows collapse to a single ring plus
|
|
48
|
-
one drop, because stacked low-alpha shadows are invisible on a dark surface.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## The colour formula
|
|
53
|
-
|
|
54
|
-
Authored in OKLCH. Ramps are **constant-hue**; chroma at each step is a
|
|
55
|
-
percentage of that hue's sRGB maximum *at that lightness*, so nothing clips and
|
|
56
|
-
no hue accidentally outranks another.
|
|
57
|
-
|
|
58
|
-
**Neutral** — hue 255, chroma capped at 0.023. Navy-tinted, not black. A pure
|
|
59
|
-
grey ramp goes dead next to a warm accent; this one stays alive without ever
|
|
60
|
-
reading as "blue".
|
|
61
|
-
|
|
62
|
-
**Accent** — hue 27.5, the house red. `--lv-accent` is the AA-safe action step
|
|
63
|
-
(L 0.554); `--lv-accent-mark` is the brighter step used for dots, rails, and
|
|
64
|
-
fills that carry no text on them.
|
|
65
|
-
|
|
66
|
-
> **The accent earns its impact from scarcity.** Exactly one filled accent
|
|
67
|
-
> surface per view. `Action variant="primary"` is that one thing; everything
|
|
68
|
-
> else is `secondary`, `ghost`, or `danger`. Three red buttons on a screen means
|
|
69
|
-
> there is no primary action on that screen.
|
|
70
|
-
|
|
71
|
-
**Semantic hues, held far apart:**
|
|
72
|
-
|
|
73
|
-
| Role | Hue | Marker shape |
|
|
74
|
-
| --- | --- | --- |
|
|
75
|
-
| success | 150° green | filled dot |
|
|
76
|
-
| warning | 95° gold | triangle |
|
|
77
|
-
| danger | 48° burnt orange | square / octagon |
|
|
78
|
-
| info | 290° violet | circle |
|
|
79
|
-
|
|
80
|
-
**Danger is not red, and that is on purpose.** The brand *is* red. A red brand
|
|
81
|
-
plus a red danger state cannot be told apart on a page that shows both, and no
|
|
82
|
-
credible danger red sits far enough from the brand hue to fix it. Moving the
|
|
83
|
-
meaning is the only thing that works. The cost is real — orange is a weaker
|
|
84
|
-
danger signal than red — so every component that carries a tone also carries an
|
|
85
|
-
**icon with a distinct silhouette** and a **screen-reader word**. Colour is
|
|
86
|
-
never the only signal. Destructive actions must additionally use an explicit
|
|
87
|
-
verb ("Delete brief", not "Confirm").
|
|
88
|
-
|
|
89
|
-
Each tone exposes four tokens: `-text` (the AA-safe reading colour), `-mark`
|
|
90
|
-
(dots/icons/tracks, which may be brighter because they are larger), `-soft` (the
|
|
91
|
-
tinted fill), and `-rule` (the leading rule).
|
|
92
|
-
|
|
93
|
-
**Both appearances, both directions.** Light is the default; dark comes from
|
|
94
|
-
`prefers-color-scheme` *and* from `[data-theme="dark"]`, and `[data-theme="light"]`
|
|
95
|
-
forces light back under a dark OS. Dark is not an inversion — the ladder is
|
|
96
|
-
rebuilt from different steps of the same ramps and every foreground/background
|
|
97
|
-
pair was re-checked. Accents gain chroma in dark, because a light-mode accent
|
|
98
|
-
goes muddy once its surround darkens.
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## The spacing scale
|
|
103
|
-
|
|
104
|
-
One 4px grid: `0 1 2 3 4 5 6 7 8 10 12 14 16 20 24` → `0px … 96px`.
|
|
105
|
-
|
|
106
|
-
`SpaceStep` is a union type, so `gap={13}` does not compile. That single
|
|
107
|
-
constraint removes the most common reason a nice interface feels subtly off.
|
|
108
|
-
|
|
109
|
-
Grouping goes **space first, background shape second, line last**. The gap
|
|
110
|
-
between groups is at least 2× the gap within one. There is deliberately no
|
|
111
|
-
`Divider` primitive — reaching for a divider is almost always a failure of
|
|
112
|
-
spacing.
|
|
113
|
-
|
|
114
|
-
**Radius is sharp and concentric.** `xs 3 · sm 4 · md 6 · lg 10 · xl 14`.
|
|
115
|
-
A card at `lg` (10px) with a 4px frame gives its tray `md` (6px):
|
|
116
|
-
`outer = inner + padding`, both directions.
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Type
|
|
121
|
-
|
|
122
|
-
Named by role, never by size: `2xs xs sm base md lg xl 2xl 3xl stat`, plus
|
|
123
|
-
`--lv-prose-*` for long-form.
|
|
124
|
-
|
|
125
|
-
- UI chrome sits at 13–14px; long-form prose at 16px / 1.62, capped at `68ch`.
|
|
126
|
-
- Large type gets negative tracking, small uppercase labels get positive.
|
|
127
|
-
- **`tabular-nums` on every comparable number**, by default, not opt-in —
|
|
128
|
-
`StatTile`, `ProgressTrack`, `FieldRow numeric`, `StatusPill`, `Timeline meta`
|
|
129
|
-
all set it themselves.
|
|
130
|
-
- Type carries hierarchy before colour does. There are only four ink steps on
|
|
131
|
-
purpose; a fifth would mean weight and size had stopped doing their job.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Layout stability
|
|
136
|
-
|
|
137
|
-
A premium UI is mechanically solid: surfaces hold still and only contents
|
|
138
|
-
change. Every v2 component reserves its space.
|
|
139
|
-
|
|
140
|
-
- **`StatTile`** — `slots` sizes a `min-width` in `ch` for the largest plausible
|
|
141
|
-
value. Set it to your worst case *including separators* (`1,284,930` → `9`).
|
|
142
|
-
The delta row is always in the box; when there is no delta it is
|
|
143
|
-
`visibility: hidden`, never `display: none`.
|
|
144
|
-
- **`ProgressTrack`** — the percentage lives in a 4ch slot, so 9% → 100% cannot
|
|
145
|
-
nudge the label.
|
|
146
|
-
- **`EmptyState` / `LoadingSkeleton` / `ErrorState`** — all three take the same
|
|
147
|
-
`reserve` prop feeding one `min-height`. Give all three the loaded content's
|
|
148
|
-
typical height and the region stops jumping through the whole lifecycle.
|
|
149
|
-
`LoadingSkeleton`'s `variant` mirrors the real primitives (`stat`, `fields`,
|
|
150
|
-
`document`) so the skeleton is the same shape, not just the same area.
|
|
151
|
-
- **`Action`** — loading keeps the label in the DOM holding the width and hides
|
|
152
|
-
it behind the spinner. Disabled changes paint only. The button never resizes
|
|
153
|
-
under the pointer.
|
|
154
|
-
- **`DocumentShell`** — the nav's active indicator is a 2px leading rule that is
|
|
155
|
-
always rendered and transparent when inactive. Adding it on activation would
|
|
156
|
-
shove every label sideways as you scroll.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Motion
|
|
161
|
-
|
|
162
|
-
Spring on small, ease on large. `--lv-ease-spring` is for buttons, toggles, and
|
|
163
|
-
badges only — never a panel, and never a data value, because overshoot on a
|
|
164
|
-
number reads as imprecise. Press scale is `0.96` exactly; below `0.95` feels
|
|
165
|
-
exaggerated. Never `transition: all`.
|
|
166
|
-
|
|
167
|
-
`prefers-reduced-motion` collapses every duration token to 1ms and kills the
|
|
168
|
-
skeleton sheen and the indeterminate travel outright — reduced motion shows the
|
|
169
|
-
*final* state, not a frozen animation.
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## When to reach for which
|
|
174
|
-
|
|
175
|
-
| You have | Use |
|
|
176
|
-
| --- | --- |
|
|
177
|
-
| a long document with sections | `DocumentShell` + `DocumentSection` |
|
|
178
|
-
| body copy inside one | `Prose` (capped measure, `text-wrap: pretty`) |
|
|
179
|
-
| label/value pairs | `DefinitionList` + `FieldRow` — one shared label column |
|
|
180
|
-
| a number someone will compare | `StatTile` (set `slots`) |
|
|
181
|
-
| a state the reader must not miss | `Callout` (icon + word + rule) |
|
|
182
|
-
| a state in a row or a header | `StatusPill` (shape carries it) |
|
|
183
|
-
| a measured quantity 0–100 | `ProgressTrack` |
|
|
184
|
-
| an ordered sequence of events | `Timeline` |
|
|
185
|
-
| the thing inside the card that matters | `Panel` (the recess) |
|
|
186
|
-
| vertical / horizontal flow | `Stack` / `Inline` |
|
|
187
|
-
| a responsive tile field | `Grid minColumnWidth` — breaks where content stops fitting, not at a device preset |
|
|
188
|
-
| controls above content | `Toolbar` (`Toolbar.Spacer` before `Toolbar.Divider`) |
|
|
189
|
-
| nothing yet / loading / broken | `EmptyState` / `LoadingSkeleton` / `ErrorState` — same `reserve` |
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Reviewing it
|
|
194
|
-
|
|
195
|
-
`__demo__/showcase.tsx` exports `<V2Showcase />`, which renders every primitive
|
|
196
|
-
in every state — default, hover, focus, disabled, loading, empty, error,
|
|
197
|
-
overflow — on one page, with a light/dark toggle. That page is the review
|
|
198
|
-
surface; if a change is not visible there, it is not reviewable.
|
|
199
|
-
|
|
200
|
-
## Rules for adding to v2
|
|
201
|
-
|
|
202
|
-
1. New tokens are `--lv-*`. New classes are `.lv-*`. No exceptions — the
|
|
203
|
-
namespace is what keeps v1 safe.
|
|
204
|
-
2. Spacing props take a `SpaceStep`. Never a raw number, never a string.
|
|
205
|
-
3. Every state a component can be in must occupy the same box as every other.
|
|
206
|
-
4. Any tone must be carried by an icon or a shape as well as a hue.
|
|
207
|
-
5. When in doubt, remove. Fewer borders, fills, colours, and font sizes.
|
|
208
|
-
Premium reads as confident and quiet; busy reads as cheap.
|