@lovett/ui 0.0.8 → 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/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.