@estiva-app/ui 0.7.0 → 0.8.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/README.md +23 -6
- package/dist/Button.d.ts +12 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +19 -5
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/IconButton.d.ts +9 -2
- package/dist/IconButton.d.ts.map +1 -1
- package/dist/PersonTrigger.d.ts +2 -1
- package/dist/PersonTrigger.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -1
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/cn.d.ts +10 -1
- package/dist/cn.d.ts.map +1 -1
- package/dist/index.js +353 -326
- package/dist/index.js.map +4 -4
- package/package.json +17 -3
- package/src/AppShell.stories.tsx +7 -1
- package/src/AppShell.tsx +1 -1
- package/src/Avatar.stories.tsx +3 -1
- package/src/Banner.stories.tsx +6 -1
- package/src/Banner.tsx +2 -2
- package/src/Breadcrumb.stories.tsx +3 -0
- package/src/Button.mdx +17 -3
- package/src/Button.stories.tsx +4 -1
- package/src/Button.test.tsx +119 -0
- package/src/Button.tsx +37 -23
- package/src/Checkbox.mdx +17 -6
- package/src/Checkbox.stories.tsx +10 -5
- package/src/Checkbox.test.tsx +73 -0
- package/src/Checkbox.tsx +52 -25
- package/src/Chip.stories.tsx +4 -0
- package/src/Chip.tsx +5 -5
- package/src/ChipInput.stories.tsx +4 -0
- package/src/DialogShell.tsx +1 -1
- package/src/EditableText.stories.tsx +6 -1
- package/src/IconButton.mdx +12 -1
- package/src/IconButton.stories.tsx +9 -4
- package/src/IconButton.test.tsx +102 -0
- package/src/IconButton.tsx +30 -17
- package/src/IdentityMenu.stories.tsx +5 -1
- package/src/Kbd.tsx +2 -2
- package/src/NavItem.stories.tsx +3 -0
- package/src/Person.stories.tsx +3 -0
- package/src/PersonTrigger.mdx +9 -1
- package/src/PersonTrigger.stories.tsx +3 -0
- package/src/PersonTrigger.test.tsx +52 -0
- package/src/PersonTrigger.tsx +8 -9
- package/src/Select.stories.tsx +7 -2
- package/src/Sidebar.stories.tsx +6 -0
- package/src/Tabs.mdx +13 -4
- package/src/Tabs.test.tsx +117 -0
- package/src/Tabs.tsx +45 -34
- package/src/Toast.tsx +8 -8
- package/src/TopBar.stories.tsx +7 -1
- package/src/cn.test.ts +20 -1
- package/src/cn.ts +18 -2
- package/stories/Choosing.mdx +101 -0
- package/stories/DesignTokens.mdx +10 -0
- package/stories/GettingStarted.mdx +108 -0
- package/stories/Introduction.mdx +36 -0
- package/stories/TokensPage.tsx +293 -0
- package/tailwind-preset.js +17 -0
- package/tokens.css +48 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useReducer, useRef, useState, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Design Tokens page: every token the preset names, its live value in the
|
|
5
|
+
* theme chosen from the toolbar, and which components use it.
|
|
6
|
+
*
|
|
7
|
+
* Three things keep the page true without anyone maintaining it:
|
|
8
|
+
* - Values are read from the CSS variables at render time. Nothing is typed in.
|
|
9
|
+
* - "Used by" is read from the components' own source: Vite hands this file
|
|
10
|
+
* the raw `src/*.tsx` text, and a token's users are whichever files spell
|
|
11
|
+
* its class. The apps are not counted.
|
|
12
|
+
* - A type specimen measures itself after it renders, so the size, line height
|
|
13
|
+
* and weight printed beside it are what the browser drew.
|
|
14
|
+
*
|
|
15
|
+
* The page renders inside Storybook's `<Unstyled>` block. Outside it, the docs
|
|
16
|
+
* container sets 16px Nunito Sans on every div, at the same specificity as a
|
|
17
|
+
* token class and later in the sheet, which is why the ramp used to look like
|
|
18
|
+
* one size.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ── Which component spells which token ── */
|
|
22
|
+
|
|
23
|
+
const SOURCES = import.meta.glob('../src/*.tsx', { query: '?raw', import: 'default', eager: true }) as Record<string, string>
|
|
24
|
+
const COMPONENTS = Object.entries(SOURCES)
|
|
25
|
+
.filter(([path]) => !/\.(stories|test)\.tsx$/.test(path))
|
|
26
|
+
.map(([path, source]) => ({ name: path.replace(/^.*\//, '').replace(/\.tsx$/, ''), source }))
|
|
27
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
28
|
+
|
|
29
|
+
const COLOUR_UTILITIES = 'bg|text|border|ring|divide|placeholder|fill|stroke|outline'
|
|
30
|
+
|
|
31
|
+
/** Component names whose source uses `<utility>-<key>`, under any variant. */
|
|
32
|
+
function usedBy(key: string, utilities: string): string[] {
|
|
33
|
+
const re = new RegExp(`(?:^|[^a-z0-9-])(?:[a-z]+:)*(?:${utilities})-${key}(?![a-z0-9-])`)
|
|
34
|
+
return COMPONENTS.filter(({ source }) => re.test(source)).map(({ name }) => name)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── Live values ── */
|
|
38
|
+
|
|
39
|
+
/** Re-render whenever the theme on <html> changes (toolbar, or a docs page's parent). */
|
|
40
|
+
function useThemeTick() {
|
|
41
|
+
const [tick, bump] = useReducer((n: number) => n + 1, 0)
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const obs = new MutationObserver(bump)
|
|
44
|
+
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme', 'class'] })
|
|
45
|
+
return () => obs.disconnect()
|
|
46
|
+
}, [])
|
|
47
|
+
return tick
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveVar(name: string): string {
|
|
51
|
+
if (typeof window === 'undefined') return ''
|
|
52
|
+
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
|
|
53
|
+
return v.startsWith('#') ? v.toUpperCase() : v.replace(/\s+/g, ' ')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── The vocabulary (mirrors tailwind-preset.js; tokens.test.ts holds the two together) ── */
|
|
57
|
+
|
|
58
|
+
type Swatch = 'fill' | 'text' | 'text-inverse' | 'border' | 'outline' | 'shadow' | 'drop-shadow'
|
|
59
|
+
type Token = { key: string; cssVar: string; cls: string; swatch: Swatch; note?: string }
|
|
60
|
+
type Family = { label: string; blurb: string; utilities: string; tokens: Token[] }
|
|
61
|
+
|
|
62
|
+
const colour = (prefix: string, key: string, swatch: Swatch, note?: string): Token => ({
|
|
63
|
+
key: `${prefix}-${key}`,
|
|
64
|
+
cssVar: `--${prefix}-${key}`,
|
|
65
|
+
cls: `${swatch === 'text' || swatch === 'text-inverse' ? 'text' : swatch === 'border' || swatch === 'outline' ? 'border' : 'bg'}-${prefix}-${key}`,
|
|
66
|
+
swatch,
|
|
67
|
+
note,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const FAMILIES: Family[] = [
|
|
71
|
+
{
|
|
72
|
+
label: 'Background',
|
|
73
|
+
blurb: 'What things sit on. Base is the page, surface a panel, elevated a popup, inset a field. Hover, selected and active are the row states; wash is the faint white a key face wears.',
|
|
74
|
+
utilities: COLOUR_UTILITIES,
|
|
75
|
+
tokens: ['base', 'surface', 'elevated', 'inset', 'hover', 'selected', 'active', 'disabled', 'wash'].map((k) => colour('bg', k, 'fill')),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: 'Text',
|
|
79
|
+
blurb: 'Primary for content, secondary for labels and metadata, muted for placeholders and counts, disabled for what cannot be used. Inverse sits on the accent; interactive is the link colour.',
|
|
80
|
+
utilities: COLOUR_UTILITIES,
|
|
81
|
+
tokens: ['primary', 'secondary', 'muted', 'disabled', 'inverse', 'interactive'].map((k) => colour('text', k, k === 'inverse' ? 'text-inverse' : 'text')),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'Border',
|
|
85
|
+
blurb: 'Subtle separates rows, default outlines a control, strong outlines a key or a checkbox, focus is the ring a focused field shows.',
|
|
86
|
+
utilities: COLOUR_UTILITIES,
|
|
87
|
+
tokens: ['subtle', 'default', 'strong', 'focus'].map((k) => colour('border', k, 'border')),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: 'Accent',
|
|
91
|
+
blurb: 'The brand colour: the primary button, the checked box, the brand chip. Muted is its wash, outline its thin border.',
|
|
92
|
+
utilities: COLOUR_UTILITIES,
|
|
93
|
+
tokens: [colour('accent', 'primary', 'fill'), colour('accent', 'hover', 'fill'), colour('accent', 'muted', 'fill'), colour('accent', 'outline', 'outline')],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: 'Tones',
|
|
97
|
+
blurb: 'Info, warning, success and error, each as a text colour (default), a wash (muted) and a thin border (outline). Banners, chips and toasts are built from these.',
|
|
98
|
+
utilities: COLOUR_UTILITIES,
|
|
99
|
+
tokens: ['info', 'warning', 'success', 'error'].flatMap((tone) => [
|
|
100
|
+
colour(tone, 'default', 'text'),
|
|
101
|
+
colour(tone, 'muted', 'fill'),
|
|
102
|
+
colour(tone, 'outline', 'outline'),
|
|
103
|
+
]),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: 'Overlay',
|
|
107
|
+
blurb: 'The scrim behind a dialog.',
|
|
108
|
+
utilities: COLOUR_UTILITIES,
|
|
109
|
+
tokens: [{ key: 'scrim', cssVar: '--scrim', cls: 'bg-scrim', swatch: 'fill' }],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
label: 'Shadow',
|
|
113
|
+
blurb: 'Three elevations, the focus ring, and the glows and inner highlight Signal adds. The two icon glows are drop shadows, so they follow the shape of the icon.',
|
|
114
|
+
utilities: 'shadow|drop-shadow',
|
|
115
|
+
tokens: [
|
|
116
|
+
{ key: 'sm', cssVar: '--shadow-sm', cls: 'shadow-sm', swatch: 'shadow' },
|
|
117
|
+
{ key: 'md', cssVar: '--shadow-md', cls: 'shadow-md', swatch: 'shadow' },
|
|
118
|
+
{ key: 'lg', cssVar: '--shadow-lg', cls: 'shadow-lg', swatch: 'shadow' },
|
|
119
|
+
{ key: 'focus-ring', cssVar: '--focus-ring', cls: 'shadow-focus-ring', swatch: 'shadow' },
|
|
120
|
+
{ key: 'glow-warning', cssVar: '--glow-warning', cls: 'shadow-glow-warning', swatch: 'shadow' },
|
|
121
|
+
{ key: 'highlight-inset', cssVar: '--highlight-inset', cls: 'shadow-highlight-inset', swatch: 'shadow' },
|
|
122
|
+
{ key: 'glow-success', cssVar: '--glow-success', cls: 'drop-shadow-glow-success', swatch: 'drop-shadow' },
|
|
123
|
+
{ key: 'glow-accent', cssVar: '--glow-accent', cls: 'drop-shadow-glow-accent', swatch: 'drop-shadow' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
type TypeToken = { key: string; cls: string }
|
|
129
|
+
const TYPE_GROUPS: { label: string; blurb: string; tokens: TypeToken[] }[] = [
|
|
130
|
+
{ label: 'Headings', blurb: 'h1 is a page title, h2 a section, h3 a card or dialog title, h4 a row title, h5 a small label.', tokens: ['h1', 'h2', 'h3', 'h4', 'h5'].map((k) => ({ key: k, cls: `text-${k}` })) },
|
|
131
|
+
{ label: 'Body', blurb: 'body-1 for reading, body-2 for the interface, caption for what sits beside it.', tokens: ['body-1', 'body-2', 'body-2-strong', 'caption'].map((k) => ({ key: k, cls: `text-${k}` })) },
|
|
132
|
+
{ label: 'Controls', blurb: 'The sizes controls are set in, so a button, a field and a chip read the same everywhere.', tokens: ['btn-default', 'btn-small', 'input-label', 'input-value', 'input-helper', 'chip', 'menu'].map((k) => ({ key: k, cls: `text-${k}` })) },
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
const RADII = [
|
|
136
|
+
{ key: 'none', cls: 'rounded-none' },
|
|
137
|
+
{ key: 'sm', cls: 'rounded-sm' },
|
|
138
|
+
{ key: 'md', cls: 'rounded-md' },
|
|
139
|
+
{ key: 'lg', cls: 'rounded-lg' },
|
|
140
|
+
{ key: 'xl', cls: 'rounded-xl' },
|
|
141
|
+
{ key: '2xl', cls: 'rounded-2xl' },
|
|
142
|
+
{ key: '3xl', cls: 'rounded-3xl' },
|
|
143
|
+
{ key: 'full', cls: 'rounded-full' },
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
/* ── Pieces ── */
|
|
147
|
+
|
|
148
|
+
function Section({ label, blurb, children }: { label: string; blurb: string; children: ReactNode }) {
|
|
149
|
+
return (
|
|
150
|
+
<section className="mt-10 first:mt-0">
|
|
151
|
+
<h2 className="text-h5 uppercase tracking-[0.08em] text-text-secondary">{label}</h2>
|
|
152
|
+
<p className="mt-1.5 max-w-[640px] text-body-2 text-text-secondary">{blurb}</p>
|
|
153
|
+
<div className="mt-3">{children}</div>
|
|
154
|
+
</section>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function Users({ names }: { names: string[] }) {
|
|
159
|
+
if (names.length === 0) return <span className="text-caption text-text-secondary">— none yet</span>
|
|
160
|
+
return (
|
|
161
|
+
<span className="truncate text-caption text-text-secondary" title={names.join(', ')}>
|
|
162
|
+
{names.join(', ')}
|
|
163
|
+
</span>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function SwatchBox({ token }: { token: Token }) {
|
|
168
|
+
const v = `var(${token.cssVar})`
|
|
169
|
+
const base = 'h-6 w-10 shrink-0 rounded-md'
|
|
170
|
+
switch (token.swatch) {
|
|
171
|
+
case 'fill':
|
|
172
|
+
return <div className={`${base} border border-border-subtle`} style={{ background: v }} />
|
|
173
|
+
case 'text':
|
|
174
|
+
case 'text-inverse':
|
|
175
|
+
return (
|
|
176
|
+
<div className={`${base} flex items-center justify-center border border-border-subtle ${token.swatch === 'text-inverse' ? 'bg-accent-primary' : 'bg-bg-surface'}`}>
|
|
177
|
+
<span className="text-body-2-strong leading-none" style={{ color: v }}>Ag</span>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
case 'border':
|
|
181
|
+
return <div className={`${base} bg-bg-surface`} style={{ border: `2px solid ${v}` }} />
|
|
182
|
+
case 'outline':
|
|
183
|
+
return <div className={base} style={{ background: `var(--${token.key.replace(/-outline$/, '')}-muted)`, border: `1px solid ${v}` }} />
|
|
184
|
+
case 'shadow':
|
|
185
|
+
return <div className="my-1 h-8 w-12 shrink-0 rounded-md bg-bg-surface" style={{ boxShadow: v }} />
|
|
186
|
+
case 'drop-shadow':
|
|
187
|
+
return <div className="my-1 h-8 w-12 shrink-0 rounded-md bg-bg-surface" style={{ filter: `drop-shadow(${v})` }} />
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function TokenRow({ token, utilities }: { token: Token; utilities: string }) {
|
|
192
|
+
return (
|
|
193
|
+
<div className="grid grid-cols-[48px_9rem_13rem_12rem_minmax(0,1fr)] items-center gap-4 border-t border-border-subtle py-1.5 first:border-t-0">
|
|
194
|
+
<SwatchBox token={token} />
|
|
195
|
+
<span className="truncate text-body-2 text-text-primary">{token.key}</span>
|
|
196
|
+
<code className="truncate font-mono text-caption text-text-secondary">{token.cls}</code>
|
|
197
|
+
<code className="truncate font-mono text-caption text-text-secondary" title={resolveVar(token.cssVar)}>
|
|
198
|
+
{resolveVar(token.cssVar)}
|
|
199
|
+
</code>
|
|
200
|
+
<Users names={usedBy(token.key, utilities)} />
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** A line set in its own token, measuring what the browser drew. */
|
|
206
|
+
function Specimen({ token, tick }: { token: TypeToken; tick: number }) {
|
|
207
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
208
|
+
const [spec, setSpec] = useState('')
|
|
209
|
+
useLayoutEffect(() => {
|
|
210
|
+
if (!ref.current) return
|
|
211
|
+
const cs = getComputedStyle(ref.current)
|
|
212
|
+
const tracking = cs.letterSpacing === 'normal' ? '' : ` · ${cs.letterSpacing}`
|
|
213
|
+
setSpec(`${cs.fontSize} / ${cs.lineHeight} · ${cs.fontWeight}${tracking}`)
|
|
214
|
+
}, [token.cls, tick])
|
|
215
|
+
return (
|
|
216
|
+
<div className="grid grid-cols-[minmax(0,1fr)_9rem_13rem] items-baseline gap-4 border-t border-border-subtle py-1.5 first:border-t-0">
|
|
217
|
+
<div ref={ref} className={`${token.cls} truncate text-text-primary`}>
|
|
218
|
+
The quick brown fox jumps
|
|
219
|
+
</div>
|
|
220
|
+
<code className="truncate font-mono text-caption text-text-secondary">{token.cls}</code>
|
|
221
|
+
<code className="truncate font-mono text-caption text-text-secondary">{spec}</code>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function ColumnHeads({ first, cols }: { first: string; cols: string[] }) {
|
|
227
|
+
return (
|
|
228
|
+
<div className={`grid items-center gap-4 pb-2 ${first === 'Specimen' ? 'grid-cols-[minmax(0,1fr)_9rem_13rem]' : 'grid-cols-[48px_9rem_13rem_12rem_minmax(0,1fr)]'}`}>
|
|
229
|
+
{first === 'Specimen' ? null : <span />}
|
|
230
|
+
{[first, ...cols].map((c) => (
|
|
231
|
+
<span key={c} className="text-caption text-text-secondary">{c}</span>
|
|
232
|
+
))}
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* ── The page ── */
|
|
238
|
+
|
|
239
|
+
export function TokensPage() {
|
|
240
|
+
const tick = useThemeTick()
|
|
241
|
+
return (
|
|
242
|
+
<div className="mx-auto max-w-[960px] font-sans text-text-primary">
|
|
243
|
+
<header>
|
|
244
|
+
<h1 className="text-h2 text-text-primary">Design Tokens</h1>
|
|
245
|
+
<p className="mt-2 max-w-[640px] text-body-2 text-text-secondary">
|
|
246
|
+
Every colour, size, radius and shadow an Estiva surface may use, by name. The name is the class: the background token
|
|
247
|
+
<code className="font-mono text-caption"> surface </code>is<code className="font-mono text-caption"> bg-bg-surface</code>. Values are
|
|
248
|
+
live for the theme in the toolbar. <em>Used by</em> lists the package's own components that spell the token; the apps are not counted.
|
|
249
|
+
</p>
|
|
250
|
+
</header>
|
|
251
|
+
|
|
252
|
+
<div className="mt-10">
|
|
253
|
+
{FAMILIES.map((family, i) => (
|
|
254
|
+
<Section key={family.label} label={family.label} blurb={family.blurb}>
|
|
255
|
+
{i === 0 ? <ColumnHeads first="Token" cols={['Class', 'Value', 'Used by']} /> : null}
|
|
256
|
+
{family.tokens.map((t) => (
|
|
257
|
+
<TokenRow key={t.cssVar + tick} token={t} utilities={family.utilities} />
|
|
258
|
+
))}
|
|
259
|
+
</Section>
|
|
260
|
+
))}
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div className="mt-14">
|
|
264
|
+
{TYPE_GROUPS.map((g, i) => (
|
|
265
|
+
<Section key={g.label} label={`Type · ${g.label}`} blurb={g.blurb}>
|
|
266
|
+
{i === 0 ? <ColumnHeads first="Specimen" cols={['Class', 'Size / line height · weight']} /> : null}
|
|
267
|
+
{g.tokens.map((t) => (
|
|
268
|
+
<Specimen key={t.key} token={t} tick={tick} />
|
|
269
|
+
))}
|
|
270
|
+
</Section>
|
|
271
|
+
))}
|
|
272
|
+
<p className="mt-3 text-caption text-text-secondary">Geist throughout; Geist Mono for code.</p>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div className="mt-14">
|
|
276
|
+
<Section label="Radius" blurb="Four steps from a key (sm) to a card (2xl), and full for a face or a pill.">
|
|
277
|
+
<div className="flex flex-wrap gap-6 pt-1">
|
|
278
|
+
{RADII.map((r) => (
|
|
279
|
+
<div key={r.key} className="flex flex-col items-center gap-2">
|
|
280
|
+
<div className={`size-12 border-2 border-accent-primary bg-accent-muted ${r.cls}`} />
|
|
281
|
+
<code className="font-mono text-caption text-text-secondary">{r.cls}</code>
|
|
282
|
+
</div>
|
|
283
|
+
))}
|
|
284
|
+
</div>
|
|
285
|
+
</Section>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<p className="mt-14 border-t border-border-subtle pt-4 text-caption text-text-secondary">
|
|
289
|
+
A colour with no token is a missing token: add it to tokens.css in every theme block and to the preset. Never a hex value in an app, never an opacity modifier on a token.
|
|
290
|
+
</p>
|
|
291
|
+
</div>
|
|
292
|
+
)
|
|
293
|
+
}
|
package/tailwind-preset.js
CHANGED
|
@@ -146,6 +146,15 @@ export default {
|
|
|
146
146
|
'success-muted': 'var(--success-muted)',
|
|
147
147
|
'error-default': 'var(--error-default)',
|
|
148
148
|
'error-muted': 'var(--error-muted)',
|
|
149
|
+
// transparent colours (D16): a designed wash, outline or scrim, per theme.
|
|
150
|
+
// Never an opacity modifier on another token: `bg-bg-inset/40` compiles to nothing.
|
|
151
|
+
'bg-wash': 'var(--bg-wash)',
|
|
152
|
+
'accent-outline': 'var(--accent-outline)',
|
|
153
|
+
'info-outline': 'var(--info-outline)',
|
|
154
|
+
'warning-outline': 'var(--warning-outline)',
|
|
155
|
+
'success-outline': 'var(--success-outline)',
|
|
156
|
+
'error-outline': 'var(--error-outline)',
|
|
157
|
+
'scrim': 'var(--scrim)',
|
|
149
158
|
},
|
|
150
159
|
boxShadow: {
|
|
151
160
|
'sm': 'var(--shadow-sm)',
|
|
@@ -154,6 +163,14 @@ export default {
|
|
|
154
163
|
// The ring a focused control wears where a theme wants one: Signal's
|
|
155
164
|
// glow. Every theme defines it; the shared inputs use it under `signal:`.
|
|
156
165
|
'focus-ring': 'var(--focus-ring)',
|
|
166
|
+
// Signal's glow and inner highlight (D16); the components use them under `signal:`.
|
|
167
|
+
'glow-warning': 'var(--glow-warning)',
|
|
168
|
+
'highlight-inset': 'var(--highlight-inset)',
|
|
169
|
+
},
|
|
170
|
+
dropShadow: {
|
|
171
|
+
// The icon glows (D16), as `drop-shadow-glow-*`; a filter, so they follow the icon's shape.
|
|
172
|
+
'glow-success': 'var(--glow-success)',
|
|
173
|
+
'glow-accent': 'var(--glow-accent)',
|
|
157
174
|
},
|
|
158
175
|
keyframes: {
|
|
159
176
|
'skeleton-in': {
|
package/tokens.css
CHANGED
|
@@ -65,6 +65,18 @@
|
|
|
65
65
|
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.10), 0 2px 4px -2px rgba(0,0,0,0.10);
|
|
66
66
|
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.10), 0 4px 6px -4px rgba(0,0,0,0.10);
|
|
67
67
|
--focus-ring: 0 0 0 3px rgba(139, 92, 246, 0.16);
|
|
68
|
+
/* Transparent colours (D16): designed per theme, never an opacity modifier. */
|
|
69
|
+
--bg-wash: rgba(0, 0, 0, 0.04);
|
|
70
|
+
--accent-outline: rgba(139, 92, 246, 0.3);
|
|
71
|
+
--info-outline: rgba(59, 130, 246, 0.3);
|
|
72
|
+
--warning-outline: rgba(245, 158, 11, 0.3);
|
|
73
|
+
--success-outline: rgba(14, 160, 111, 0.3);
|
|
74
|
+
--error-outline: rgba(239, 68, 68, 0.3);
|
|
75
|
+
--glow-warning: 0 0 5px rgba(245, 158, 11, 0.4);
|
|
76
|
+
--glow-success: 0 0 5px rgba(14, 160, 111, 0.7);
|
|
77
|
+
--glow-accent: 0 0 5px rgba(139, 92, 246, 0.6);
|
|
78
|
+
--highlight-inset: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
|
79
|
+
--scrim: rgba(0, 0, 0, 0.5);
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
/* ─── dark ─── Peek's dark theme (peek/src/index.css .dark, 2026-08-28). */
|
|
@@ -114,6 +126,18 @@
|
|
|
114
126
|
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.40), 0 2px 4px -2px rgba(0,0,0,0.30);
|
|
115
127
|
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.50), 0 4px 6px -4px rgba(0,0,0,0.40);
|
|
116
128
|
--focus-ring: 0 0 0 3px rgba(167, 139, 250, 0.16);
|
|
129
|
+
/* Transparent colours (D16): designed per theme, never an opacity modifier. */
|
|
130
|
+
--bg-wash: rgba(255, 255, 255, 0.05);
|
|
131
|
+
--accent-outline: rgba(167, 139, 250, 0.3);
|
|
132
|
+
--info-outline: rgba(96, 165, 250, 0.3);
|
|
133
|
+
--warning-outline: rgba(251, 191, 36, 0.3);
|
|
134
|
+
--success-outline: rgba(52, 211, 153, 0.3);
|
|
135
|
+
--error-outline: rgba(248, 113, 113, 0.3);
|
|
136
|
+
--glow-warning: 0 0 5px rgba(251, 191, 36, 0.4);
|
|
137
|
+
--glow-success: 0 0 5px rgba(52, 211, 153, 0.7);
|
|
138
|
+
--glow-accent: 0 0 5px rgba(167, 139, 250, 0.6);
|
|
139
|
+
--highlight-inset: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
|
140
|
+
--scrim: rgba(0, 0, 0, 0.5);
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
|
|
@@ -167,6 +191,18 @@
|
|
|
167
191
|
--shadow-lg: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 12px 28px rgba(0, 0, 0, 0.5), 0 36px 90px -16px rgba(0, 0, 0, 0.8);
|
|
168
192
|
/* the glow a focused control wears — Peek's, verbatim */
|
|
169
193
|
--focus-ring: 0 0 0 3px rgba(86, 200, 255, 0.12), 0 0 28px -8px rgba(86, 200, 255, 0.4);
|
|
194
|
+
/* Transparent colours (D16): designed per theme, never an opacity modifier. */
|
|
195
|
+
--bg-wash: rgba(255, 255, 255, 0.05);
|
|
196
|
+
--accent-outline: rgba(86, 200, 255, 0.3);
|
|
197
|
+
--info-outline: rgba(86, 200, 255, 0.3); /* the accent blue: what the info chip has always drawn */
|
|
198
|
+
--warning-outline: rgba(255, 176, 32, 0.3);
|
|
199
|
+
--success-outline: rgba(63, 222, 140, 0.3);
|
|
200
|
+
--error-outline: rgba(255, 107, 107, 0.3);
|
|
201
|
+
--glow-warning: 0 0 5px rgba(255, 176, 32, 0.4);
|
|
202
|
+
--glow-success: 0 0 5px rgba(63, 222, 140, 0.7);
|
|
203
|
+
--glow-accent: 0 0 5px rgba(86, 200, 255, 0.6);
|
|
204
|
+
--highlight-inset: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
|
205
|
+
--scrim: rgba(0, 0, 0, 0.5);
|
|
170
206
|
}
|
|
171
207
|
|
|
172
208
|
/* ─── ship ─── Ship's palette (Katerina's ruling, 2026-08-28; ship/web/src/index.css). */
|
|
@@ -226,4 +262,16 @@
|
|
|
226
262
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
|
227
263
|
/* defined, as every theme must; Ship's controls show focus with the border alone */
|
|
228
264
|
--focus-ring: 0 0 0 3px rgba(75, 88, 212, 0.16);
|
|
265
|
+
/* Transparent colours (D16): designed per theme, never an opacity modifier. */
|
|
266
|
+
--bg-wash: rgba(255, 255, 255, 0.05);
|
|
267
|
+
--accent-outline: rgba(75, 88, 212, 0.3);
|
|
268
|
+
--info-outline: rgba(91, 123, 232, 0.3);
|
|
269
|
+
--warning-outline: rgba(224, 176, 74, 0.3);
|
|
270
|
+
--success-outline: rgba(70, 192, 138, 0.3);
|
|
271
|
+
--error-outline: rgba(229, 105, 122, 0.3);
|
|
272
|
+
--glow-warning: 0 0 5px rgba(224, 176, 74, 0.4);
|
|
273
|
+
--glow-success: 0 0 5px rgba(70, 192, 138, 0.7);
|
|
274
|
+
--glow-accent: 0 0 5px rgba(75, 88, 212, 0.6);
|
|
275
|
+
--highlight-inset: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
|
276
|
+
--scrim: rgba(0, 0, 0, 0.5);
|
|
229
277
|
}
|