@threadlabs/looma 0.36.0 → 0.37.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/components/ui-avatar/ui-avatar.html +1 -1
- package/components/ui-avatar-group/ui-avatar-group.html +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +2 -2
- package/docs/catalog.json +3 -3
- package/docs/customizer-render.mjs +2 -2
- package/docs/customizer.js +27 -3
- package/docs/theme.mjs +3 -2
- package/docs/value-editors.mjs +4 -3
- package/package.json +2 -2
- package/styles/ui-avatar-group.css +1 -1
- package/styles/ui-avatar.css +1 -1
- package/theme-system.css +5 -4
- package/tokens.css +18 -15
- package/vue/UiAvatar.vue +1 -1
- package/vue/UiAvatarGroup.vue +1 -1
- package/vue/chunks/UiAvatar.js +1 -1
- package/vue/chunks/UiAvatarGroup.js +1 -1
- package/vue/components.css +2 -2
package/docs/README.md
CHANGED
|
@@ -76,9 +76,9 @@ The [public token reference](https://github.com/threadlabs-studio/looma/blob/mai
|
|
|
76
76
|
| Finish and illumination | `finish` → `--ui-finish`; `edge-light` → `--ui-edge-light`; `glow` → `--ui-glow`; `light-x` → `--ui-light-x`; `light-y` → `--ui-light-y`; `light-spread` → `--ui-light-spread`; `light-strength` → `--ui-light-strength` |
|
|
77
77
|
| Motion | `response-time` → `--ui-motion-fast`; `response-curve` → `--ui-motion-ease` |
|
|
78
78
|
|
|
79
|
-
Palette generation decisions exist only in the JSON recipe; edit it and regenerate to change resolved colors. Geometry, material and motion properties feed the included CSS recipe. `--ui-control-size` remains a derived public height role: author numeric roominess (default 2.5, range 2–4) instead of an absolute control size. Spacing (default 1, range 0.75–1.5) changes space without changing heights. Base text defaults to `1rem
|
|
79
|
+
Palette generation decisions exist only in the JSON recipe; edit it and regenerate to change resolved colors. Geometry, material and motion properties feed the included CSS recipe. `--ui-control-size` remains a derived public height role: author numeric roominess (default 2.5, range 2–4) instead of an absolute control size. Spacing (default 1, range 0.75–1.5) changes space without changing heights. Base text defaults to `16px` and sets the document root. Type and geometry derive from `1rem` once; the base editor accepts only px. Other relative length inputs preserve their units; editor ranges convert through the chosen root, and generated guards keep the documented pixel bounds. Visible size samples report actual rem/px heights. Set the base on `:root`; opposite-mode boundaries retain that same rem root.
|
|
80
80
|
|
|
81
|
-
Before 1.0 there are no compatibility aliases. Migrate recipe `density` to `spacing` and CSS `--ui-density` to `--ui-spacing`. Replace length-valued recipe `control-size` with numeric `control-roominess` by dividing the desired medium height by base text size: `40px / 16px` becomes `2.5`.
|
|
81
|
+
Before 1.0 there are no compatibility aliases. Migrate recipe `density` to `spacing` and CSS `--ui-density` to `--ui-spacing`. Replace length-valued recipe `control-size` with numeric `control-roominess` by dividing the desired medium height by base text size: `40px / 16px` becomes `2.5`. Base `text-size` is now px-only and sets the document root. Replace older relative base values with an explicit px value using their original root; no compatibility conversion guesses it.
|
|
82
82
|
|
|
83
83
|
## Choose fonts
|
|
84
84
|
|
package/docs/catalog.json
CHANGED
|
@@ -443,7 +443,7 @@
|
|
|
443
443
|
],
|
|
444
444
|
"schemaVersion": 1,
|
|
445
445
|
"package": "@threadlabs/looma",
|
|
446
|
-
"version": "0.
|
|
446
|
+
"version": "0.37.0",
|
|
447
447
|
"groups": [
|
|
448
448
|
{
|
|
449
449
|
"id": "layout",
|
|
@@ -729,7 +729,7 @@
|
|
|
729
729
|
"name": "--ui-border-width"
|
|
730
730
|
},
|
|
731
731
|
{
|
|
732
|
-
"name": "--ui-font-size"
|
|
732
|
+
"name": "--ui-font-size-md"
|
|
733
733
|
},
|
|
734
734
|
{
|
|
735
735
|
"name": "--ui-font-size-sm"
|
|
@@ -975,7 +975,7 @@
|
|
|
975
975
|
"name": "--ui-border-width"
|
|
976
976
|
},
|
|
977
977
|
{
|
|
978
|
-
"name": "--ui-font-size"
|
|
978
|
+
"name": "--ui-font-size-md"
|
|
979
979
|
},
|
|
980
980
|
{
|
|
981
981
|
"name": "--ui-font-size-xs"
|
|
@@ -16,7 +16,7 @@ export function customizerResource(site, input = defaultRecipe()) {
|
|
|
16
16
|
return example ? `<looma-example-card ${cardFields.filter(key => key !== 'id').map(key => `${key.replace(/[A-Z]/g, letter => '-' + letter.toLowerCase())}="${escape(String(example[key]))}"`).join(' ')} identity="${escape(example.id)}" loading="lazy"></looma-example-card>` : '';
|
|
17
17
|
};
|
|
18
18
|
const renderField = field => {
|
|
19
|
-
const value = recipe.values[field.id], editor = valueEditor(field, value);
|
|
19
|
+
const value = recipe.values[field.id], editor = valueEditor(field, value, { remPixels: parseFloat(recipe.values['text-size']) });
|
|
20
20
|
const common = `size="sm" data-token="${field.id}" label="${escape(field.label)}"`;
|
|
21
21
|
let control;
|
|
22
22
|
if (field.type === 'font') {
|
|
@@ -45,7 +45,7 @@ export function customizerResource(site, input = defaultRecipe()) {
|
|
|
45
45
|
'Colour': `${renderPreset('palette', 'overview')}<ui-text variant="caption">Coordinated light and dark ranges.</ui-text><ui-button data-action="refine-palette">Refine palette</ui-button>`,
|
|
46
46
|
'Material': `${renderPreset('material', 'overview')}<ui-text variant="caption">Material and light, shared everywhere.</ui-text><ui-button data-action="refine-material">Refine material & light</ui-button>`,
|
|
47
47
|
'Motion': `<ui-text variant="caption">Open and close this real documentation example to feel the response pace and curve. Reduced motion follows your system preference.</ui-text>${documentationCard('ui-popover')}`,
|
|
48
|
-
'Size & spacing': `<ui-text variant="caption">Shared size ladder: buttons, fields and swatches align at each step. Touch raises the family together.</ui-text><ui-stack>${['sm','md','lg'].map((size,index) => `<ui-stack gap="xs" data-size-sample="${size}"><ui-text variant="caption">${['Small','Medium','Large'][index]}
|
|
48
|
+
'Size & spacing': `<ui-text variant="caption">Shared size ladder: buttons, fields and swatches align at each step. Touch raises the family together.</ui-text><ui-stack>${['sm','md','lg'].map((size,index) => `<ui-stack gap="xs" data-size-sample="${size}"><ui-text variant="caption">${['Small','Medium','Large'][index]}<span data-size-readout></span></ui-text><ui-cluster><ui-button size="${size}">Action</ui-button><ui-input size="${size}" style="--ui-input-inline-size: 10ch" label="${size} size sample" value="Frodo"></ui-input><ui-color-picker size="${size}" label="${size} swatch sample"></ui-color-picker></ui-cluster></ui-stack>`).join('')}</ui-stack><div class="spacing-samples" data-spacing-samples role="img" aria-label="Spacing progression"><span></span><span></span><span></span><span></span></div><ui-disclosure summary="How the scale grows"><ui-stack><p>Control roominess relates every field and button to base text. Size ratio separates small from large. On touch, the whole family grows together to keep targets comfortable. Spacing and its curve shape page rhythm independently.</p>${themeFields.filter(item => ['scale-contrast','space-curve'].includes(item.id)).map(renderField).join('')}</ui-stack></ui-disclosure>`,
|
|
49
49
|
};
|
|
50
50
|
const families = [...new Set(themeFields.map(field => field.family))].filter(family => !['Colour', 'Material'].includes(family)).map(family => `<ui-disclosure summary="${family}"><ui-stack>${themeFields.filter(item => item.family === family && !['scale-contrast', 'space-curve'].includes(item.id)).map(renderField).join('')}${supplements[family] ?? ''}</ui-stack></ui-disclosure>`).join('');
|
|
51
51
|
const refinement = (family, content) => `<section data-refinement-view="${family}" hidden tabindex="-1" aria-label="${family === 'material' ? 'Material and light refinement' : 'Palette refinement'}"><ui-stack gap="xl"><ui-cluster justify="between"><ui-button data-action="back">Back to customization</ui-button><ui-button data-action="restore-${family}">Restore ${family} preset</ui-button></ui-cluster><ui-text variant="heading" as="h2">${family === 'material' ? 'Refine material & light' : 'Refine your palette'}</ui-text><p>These are advanced refinements of the same persistent theme. Every change applies throughout your documentation.</p>${renderPreset(family, 'refinement')}${content}</ui-stack></section>`;
|
package/docs/customizer.js
CHANGED
|
@@ -29,10 +29,12 @@ export function connect(host) {
|
|
|
29
29
|
const sayApplied = text => say(theme?.storageAvailable ? text : 'Theme applies to this page. Browser storage is unavailable; download CSS or a recipe to keep it.');
|
|
30
30
|
const fields = new Map(themeFields.map(field => [field.id, field]));
|
|
31
31
|
const unitContext = () => ({ remPixels: parseFloat(view.getComputedStyle(document.documentElement).fontSize) });
|
|
32
|
+
const editorFor = (field, value) => valueEditor(field, value, { remPixels: parseFloat(recipe.values['text-size']) });
|
|
33
|
+
let boundsBase;
|
|
32
34
|
const updateControl = (element, value) => {
|
|
33
35
|
const field = fields.get(element.dataset.token);
|
|
34
36
|
if (!field || !element.dataset.editorKind) { componentRegistry.update(element, { value }); return; }
|
|
35
|
-
const editor =
|
|
37
|
+
const editor = editorFor(field, value);
|
|
36
38
|
if (element.dataset.editorKind === 'curve') {
|
|
37
39
|
element.closest('[data-curve-for]').hidden = editor.kind !== 'curve';
|
|
38
40
|
if (editor.kind === 'curve') componentRegistry.update(element, { value });
|
|
@@ -40,13 +42,33 @@ export function connect(host) {
|
|
|
40
42
|
};
|
|
41
43
|
const updateUnits = () => {
|
|
42
44
|
for (const control of root.querySelectorAll('[data-unit-for]')) {
|
|
43
|
-
const field = fields.get(control.dataset.unitFor), editor =
|
|
45
|
+
const field = fields.get(control.dataset.unitFor), editor = editorFor(field, recipe.values[field.id]);
|
|
44
46
|
if (![...control.options].some(option => option.value === editor.unit)) control.add(new view.Option(editor.unit, editor.unit));
|
|
45
47
|
componentRegistry.update(control, { value: editor.unit });
|
|
46
48
|
}
|
|
47
49
|
};
|
|
50
|
+
const updateSizes = () => {
|
|
51
|
+
const samples = [...root.querySelectorAll('[data-size-sample]')].filter(sample => sample.getClientRects().length);
|
|
52
|
+
if (!samples.length) return;
|
|
53
|
+
const base = parseFloat(view.getComputedStyle(document.documentElement).fontSize);
|
|
54
|
+
const rounded = value => Number(value.toFixed(2));
|
|
55
|
+
for (const sample of samples) {
|
|
56
|
+
const button = sample.querySelector('button');
|
|
57
|
+
if (button) {
|
|
58
|
+
const height = button.getBoundingClientRect().height;
|
|
59
|
+
sample.querySelector('[data-size-readout]').textContent = ` · ${rounded(height / base)}rem (${rounded(height)}px)`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
48
63
|
const paint = () => {
|
|
49
64
|
const css = theme?.css || themeCSS(recipe);
|
|
65
|
+
if (boundsBase !== recipe.values['text-size']) {
|
|
66
|
+
boundsBase = recipe.values['text-size'];
|
|
67
|
+
for (const control of root.querySelectorAll('[data-token]')) {
|
|
68
|
+
const field = fields.get(control.dataset.token);
|
|
69
|
+
if (field?.type.includes('length') && field.id !== 'text-size') updateControl(control, recipe.values[field.id]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
50
72
|
for (const family of ['material','palette']) {
|
|
51
73
|
const state = refinementState(recipe, family), presets = family === 'material' ? materialPresets : themePalettes;
|
|
52
74
|
for (const select of root.querySelectorAll(`[data-refinement-preset="${family}"]`)) {
|
|
@@ -73,6 +95,7 @@ export function connect(host) {
|
|
|
73
95
|
: 'Finish catches the light. Spread shapes its reflection; strength affects reflection, edge light and glow together.';
|
|
74
96
|
const gaps = root.querySelector('[data-spacing-samples]');
|
|
75
97
|
gaps.setAttribute('aria-label', `Spacing progression with curve ${recipe.values['space-curve']}`);
|
|
98
|
+
updateSizes();
|
|
76
99
|
};
|
|
77
100
|
const show = (next, persist = true) => {
|
|
78
101
|
recipe = validateRecipe(next);
|
|
@@ -310,6 +333,7 @@ export function connect(host) {
|
|
|
310
333
|
selectedExample = event.detail.value ?? '';
|
|
311
334
|
filterExamples();
|
|
312
335
|
};
|
|
336
|
+
root.addEventListener('toggle', updateSizes, true);
|
|
313
337
|
root.addEventListener('looma-example-remove', remove);
|
|
314
338
|
root.addEventListener('input', input);
|
|
315
339
|
root.addEventListener('cancel', cancel);
|
|
@@ -334,5 +358,5 @@ export function connect(host) {
|
|
|
334
358
|
root.querySelector('[data-action="save"]').hidden = false;
|
|
335
359
|
}).catch(error => { if (alive && error.name !== 'AbortError') say(error.message, true); });
|
|
336
360
|
}
|
|
337
|
-
return () => { flush(); clearComparison(); root.removeEventListener('cancel', cancel); root.removeEventListener('keydown', keyboard); alive = false; abort.abort(); root.removeEventListener('looma-example-remove', remove); root.removeEventListener('input', input); root.removeEventListener('value-change', selectExample); root.removeEventListener('value-change', selectFont); root.removeEventListener('blur', blur, true); root.removeEventListener('change', change); root.removeEventListener('click', click); view.removeEventListener('beforeunload', flush); view.removeEventListener('message', ready); view.removeEventListener(themeEvent, synchronize); };
|
|
361
|
+
return () => { root.removeEventListener('toggle', updateSizes, true); flush(); clearComparison(); root.removeEventListener('cancel', cancel); root.removeEventListener('keydown', keyboard); alive = false; abort.abort(); root.removeEventListener('looma-example-remove', remove); root.removeEventListener('input', input); root.removeEventListener('value-change', selectExample); root.removeEventListener('value-change', selectFont); root.removeEventListener('blur', blur, true); root.removeEventListener('change', change); root.removeEventListener('click', click); view.removeEventListener('beforeunload', flush); view.removeEventListener('message', ready); view.removeEventListener(themeEvent, synchronize); };
|
|
338
362
|
}
|
package/docs/theme.mjs
CHANGED
|
@@ -19,7 +19,7 @@ export const themeFields = [
|
|
|
19
19
|
['Colour', 'palette-relationship', 'Palette relationship', null, 'choice', 'preserve', 'Keep seed hues, or relate quiet surfaces to the brand through neighboring or complementary hues.'],
|
|
20
20
|
['Type', 'body-font', 'Body font', '--ui-font-sans', 'font', 'system-ui, sans-serif', 'The shared interface typeface.'],
|
|
21
21
|
['Type', 'display-font', 'Display font', '--ui-font-display', 'font', 'system-ui, sans-serif', 'The heading typeface.'],
|
|
22
|
-
['Type', 'text-size', '
|
|
22
|
+
['Type', 'text-size', 'Base text size', '--ui-font-size', 'positive-length', '16px', 'Sets the document root in px. Type, spacing and layout use rem relationships and scale from this base once.'],
|
|
23
23
|
['Type', 'text-weight', 'Emphasis weight', '--ui-font-medium', 'weight', '500', 'Control-label emphasis; body and headings keep their roles.'],
|
|
24
24
|
['Size & spacing', 'control-roominess', 'Control roominess', '--ui-control-roominess', 'positive', '2.5', 'Shared control height as a multiple of base text size; buttons, fields and swatches stay aligned.'],
|
|
25
25
|
['Size & spacing', 'spacing', 'Spacing', '--ui-spacing', 'positive', '1', 'More or less breathing room throughout the interface.'],
|
|
@@ -161,6 +161,7 @@ export function validateRecipe(input) {
|
|
|
161
161
|
if (field.type === 'weight') valid &&= number >= 1 && number <= 1000;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
+
if (field.id === 'text-size' && valid && !value.endsWith('px')) throw new Error('Base text size: enter a px value for the document root.');
|
|
164
165
|
if (!valid) throw new Error(`${field.label}: enter a valid ${field.type.replaceAll('-', ' ')} value.`);
|
|
165
166
|
if (Object.hasOwn(bounds, field.id)) {
|
|
166
167
|
const number = boundedNumber(field, value), [min, max] = bounds[field.id];
|
|
@@ -210,7 +211,7 @@ export function deriveTheme(input, { mode } = {}) {
|
|
|
210
211
|
const tokens = Object.fromEntries(themeFields.filter(field => field.property).map(field => [field.property, values[field.id]]));
|
|
211
212
|
for (const field of themeFields.filter(field => field.type.includes('length') && Object.hasOwn(bounds, field.id))) {
|
|
212
213
|
if (boundedNumber(field, values[field.id]) === undefined) {
|
|
213
|
-
const [min, max] = bounds[field.id]; tokens[field.property] = `clamp(${min
|
|
214
|
+
const [min, max] = bounds[field.id]; tokens[field.property] = `clamp(${min}px, ${values[field.id]}, ${max}px)`;
|
|
214
215
|
}
|
|
215
216
|
}
|
|
216
217
|
const cacheKey = JSON.stringify([mode, ...paletteKeys.map(key => values[key])]);
|
package/docs/value-editors.mjs
CHANGED
|
@@ -3,14 +3,15 @@ const lengthUnits = { px: 1, rem: 16, em: 16, cm: 96 / 2.54, mm: 96 / 25.4, Q: 9
|
|
|
3
3
|
const timeUnits = { ms: 1, s: 1000 };
|
|
4
4
|
const rounded = value => String(Number(Number(value).toFixed(6)));
|
|
5
5
|
|
|
6
|
-
export function valueEditor(field, value) {
|
|
6
|
+
export function valueEditor(field, value, { remPixels = 16 } = {}) {
|
|
7
|
+
if (field.id === 'text-size') return { kind: 'number', value, unit: 'px', min: field.min, max: field.max, step: 1 };
|
|
7
8
|
if (field.type === 'choice') return { kind: 'choice', value };
|
|
8
9
|
if (field.type === 'easing') return { kind: /^step-/.test(value) ? 'text' : 'curve', value };
|
|
9
10
|
if (['colour', 'font'].includes(field.type)) return { kind: 'text', value };
|
|
10
11
|
if (field.type.includes('length') || field.type === 'time') {
|
|
11
12
|
const unit = value.match(/[a-z]+$/i)?.[0] ?? 'px';
|
|
12
|
-
//
|
|
13
|
-
const units = field.type === 'time' ? timeUnits : lengthUnits;
|
|
13
|
+
// Convert authoring limits against the same root as the displayed quantity.
|
|
14
|
+
const units = field.type === 'time' ? timeUnits : { ...lengthUnits, rem: remPixels };
|
|
14
15
|
const factor = units[unit];
|
|
15
16
|
return { kind: 'number', value: value === '0' ? '0px' : value, unit,
|
|
16
17
|
min: factor ? field.min / factor : .001, max: factor ? field.max / factor : 1000000,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadlabs/looma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"@tiptap/pm": ">=2 <3",
|
|
128
128
|
"@tiptap/vue-3": ">=2 <3",
|
|
129
129
|
"vue": "^3.5.43",
|
|
130
|
-
"markupress": "^0.8.
|
|
130
|
+
"markupress": "^0.8.7",
|
|
131
131
|
"@nextwebwg/htmlkit": "1.0.0-alpha.69",
|
|
132
132
|
"vite": "^8.3.4"
|
|
133
133
|
},
|
package/styles/ui-avatar.css
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
inline-size: 1.25rem;
|
|
61
61
|
block-size: 1.25rem;
|
|
62
62
|
/* Below the type scale on purpose: two initials need room inside a 1.25rem circle. */
|
|
63
|
-
font-size: calc(var(--ui-font-size) * 0.5625);
|
|
63
|
+
font-size: calc(var(--ui-font-size-md) * 0.5625);
|
|
64
64
|
letter-spacing: 0.02em;
|
|
65
65
|
}
|
|
66
66
|
|
package/theme-system.css
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
/* Reserve the same order when the remembered theme arrives before baseline CSS. */
|
|
6
6
|
@layer tokens, base, theme, components;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/* Keep the root font seed unregistered: a registered rem length used by root
|
|
9
|
+
font-size creates a dependency cycle (CSS Properties and Values API 2.7.2). */
|
|
9
10
|
@property --ui-control-size { syntax: "<length>"; inherits: true; initial-value: 40px; }
|
|
10
11
|
@property --ui-radius-md { syntax: "<length>"; inherits: true; initial-value: 8px; }
|
|
11
12
|
@property --ui-border-width { syntax: "<length>"; inherits: true; initial-value: 1px; }
|
|
@@ -33,9 +34,9 @@
|
|
|
33
34
|
:where(:root, [data-theme], [data-contrast]) {
|
|
34
35
|
/* Controls use a ratio. Spacing uses an independent curve, so increasing the size
|
|
35
36
|
contrast cannot unexpectedly turn linear spacing into quadratic spacing. */
|
|
36
|
-
--ui-control-size: calc(
|
|
37
|
+
--ui-control-size: calc(1rem * var(--ui-control-roominess));
|
|
37
38
|
--_ui-theme-ratio: max(1, var(--ui-scale-ratio));
|
|
38
|
-
--_ui-theme-unit: calc(
|
|
39
|
+
--_ui-theme-unit: calc(1rem * 0.25 * max(0, var(--ui-spacing)));
|
|
39
40
|
--_ui-theme-curve: max(0, var(--ui-space-curve));
|
|
40
41
|
--ui-control-size-sm: calc(var(--ui-control-size) / var(--_ui-theme-ratio));
|
|
41
42
|
--ui-control-size-md: var(--ui-control-size);
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
--ui-control-inset-block-sm: max(0px, calc((var(--ui-control-size-sm) - var(--ui-font-size-sm) * var(--ui-line-height-tight) - 2 * var(--ui-border-width)) / 2));
|
|
54
55
|
--ui-control-inset-block-md: max(0px, calc((var(--ui-control-size-md) - var(--ui-font-size-md) * var(--ui-line-height-tight) - 2 * var(--ui-border-width)) / 2));
|
|
55
56
|
--ui-control-inset-block-lg: max(0px, calc((var(--ui-control-size-lg) - var(--ui-font-size-md) * var(--ui-line-height-tight) - 2 * var(--ui-border-width)) / 2));
|
|
56
|
-
--ui-font-size-xs: max(12px, calc(
|
|
57
|
+
--ui-font-size-xs: max(12px, calc(1rem * 0.75));
|
|
57
58
|
--ui-action-inset-sm: var(--ui-space-2);
|
|
58
59
|
--ui-action-inset-md: var(--ui-space-3);
|
|
59
60
|
--ui-action-inset-lg: var(--ui-space-4);
|
package/tokens.css
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
--ui-text: #1a1a1a;
|
|
26
26
|
--ui-focus-halo: 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(in srgb, var(--ui-focus-ring) 40%, transparent);
|
|
27
27
|
|
|
28
|
+
/* The base type token owns the document root; rem-based roles scale from it once. */
|
|
29
|
+
font-size: var(--ui-font-size);
|
|
30
|
+
|
|
28
31
|
/* Type. */
|
|
29
32
|
--ui-font-sans:
|
|
30
33
|
system-ui,
|
|
@@ -38,7 +41,7 @@
|
|
|
38
41
|
"Apple Color Emoji",
|
|
39
42
|
"Segoe UI Emoji";
|
|
40
43
|
--ui-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
41
|
-
--ui-font-size:
|
|
44
|
+
--ui-font-size: 16px;
|
|
42
45
|
--ui-font-medium: 500;
|
|
43
46
|
--ui-line-height: 1.4;
|
|
44
47
|
|
|
@@ -92,14 +95,14 @@
|
|
|
92
95
|
--ui-accent-line-width: var(--ui-border-width);
|
|
93
96
|
|
|
94
97
|
/* Type scale, from the base size. */
|
|
95
|
-
--ui-font-size-2xs: calc(
|
|
96
|
-
--ui-font-size-xs: calc(
|
|
98
|
+
--ui-font-size-2xs: calc(1rem * 0.6875);
|
|
99
|
+
--ui-font-size-xs: calc(1rem * 0.75);
|
|
97
100
|
/* Dense text: controls, labels, and metadata. Two steps below body copy, never one pixel. */
|
|
98
|
-
--ui-font-size-sm: calc(
|
|
99
|
-
--ui-font-size-md:
|
|
100
|
-
--ui-font-size-lg: calc(
|
|
101
|
-
--ui-font-size-xl: calc(
|
|
102
|
-
--ui-font-size-2xl: calc(
|
|
101
|
+
--ui-font-size-sm: calc(1rem * 0.875);
|
|
102
|
+
--ui-font-size-md: 1rem;
|
|
103
|
+
--ui-font-size-lg: calc(1rem * 1.125);
|
|
104
|
+
--ui-font-size-xl: calc(1rem * 1.25);
|
|
105
|
+
--ui-font-size-2xl: calc(1rem * 1.5);
|
|
103
106
|
--ui-line-height-tight: 1.2;
|
|
104
107
|
--ui-line-height-md: var(--ui-line-height);
|
|
105
108
|
/* Prose, not controls: the editor's document body reads at a looser line height. */
|
|
@@ -260,14 +263,14 @@
|
|
|
260
263
|
--ui-control-size-lg: calc(var(--ui-control-size) * 1.2);
|
|
261
264
|
|
|
262
265
|
/* Choice marks follow typography; row heights follow the shared control scale. */
|
|
263
|
-
--ui-choice-mark-size: calc(
|
|
264
|
-
--ui-choice-track-height: calc(
|
|
265
|
-
--ui-choice-track-width: calc(
|
|
266
|
+
--ui-choice-mark-size: calc(1rem * 1.125);
|
|
267
|
+
--ui-choice-track-height: calc(1rem * 1.5);
|
|
268
|
+
--ui-choice-track-width: calc(1rem * 2.5);
|
|
266
269
|
|
|
267
270
|
/* Icon sizes, used by icon buttons and field affordances. */
|
|
268
|
-
--ui-icon-size-sm:
|
|
269
|
-
--ui-icon-size-md: calc(
|
|
270
|
-
--ui-icon-size-lg: calc(
|
|
271
|
+
--ui-icon-size-sm: 1rem;
|
|
272
|
+
--ui-icon-size-md: calc(1rem * 1.25);
|
|
273
|
+
--ui-icon-size-lg: calc(1rem * 1.5);
|
|
271
274
|
|
|
272
275
|
/* Anticipatory interaction: guide → near → direct. */
|
|
273
276
|
--ui-affordance-guide-size: var(--ui-space-1);
|
|
@@ -392,7 +395,7 @@
|
|
|
392
395
|
background-color: var(--ui-control-surface);
|
|
393
396
|
background-repeat: no-repeat;
|
|
394
397
|
background-position: center;
|
|
395
|
-
background-size: calc(
|
|
398
|
+
background-size: calc(1rem * 0.7);
|
|
396
399
|
}
|
|
397
400
|
|
|
398
401
|
[data-ui-choice-option][data-multiple][aria-selected="true"]::before {
|
package/vue/UiAvatar.vue
CHANGED
|
@@ -366,7 +366,7 @@ useComponentHost(() => import('../components/ui-avatar/ui-avatar.js'), {
|
|
|
366
366
|
inline-size: 1.25rem;
|
|
367
367
|
block-size: 1.25rem;
|
|
368
368
|
/* Below the type scale on purpose: two initials need room inside a 1.25rem circle. */
|
|
369
|
-
font-size: calc(var(--ui-font-size) * 0.5625);
|
|
369
|
+
font-size: calc(var(--ui-font-size-md) * 0.5625);
|
|
370
370
|
letter-spacing: 0.02em;
|
|
371
371
|
}
|
|
372
372
|
|
package/vue/UiAvatarGroup.vue
CHANGED
|
@@ -301,7 +301,7 @@ useComponentHost(() => import('../components/ui-avatar-group/ui-avatar-group.js'
|
|
|
301
301
|
inline-size: 1.25rem;
|
|
302
302
|
block-size: 1.25rem;
|
|
303
303
|
/* Matches the xs Avatar's initials. */
|
|
304
|
-
font-size: calc(var(--ui-font-size) * 0.5625);
|
|
304
|
+
font-size: calc(var(--ui-font-size-md) * 0.5625);
|
|
305
305
|
line-height: 1;
|
|
306
306
|
}
|
|
307
307
|
|
package/vue/chunks/UiAvatar.js
CHANGED
package/vue/components.css
CHANGED
|
@@ -180,7 +180,7 @@ svg {
|
|
|
180
180
|
inline-size: 1.25rem;
|
|
181
181
|
block-size: 1.25rem;
|
|
182
182
|
/* Below the type scale on purpose: two initials need room inside a 1.25rem circle. */
|
|
183
|
-
font-size: calc(var(--ui-font-size) * 0.5625);
|
|
183
|
+
font-size: calc(var(--ui-font-size-md) * 0.5625);
|
|
184
184
|
letter-spacing: 0.02em;
|
|
185
185
|
}
|
|
186
186
|
:scope[data-ui-avatar-state~="hasImage"]:where([data-hn-ui-avatar]) {
|
|
@@ -297,7 +297,7 @@ svg {
|
|
|
297
297
|
inline-size: 1.25rem;
|
|
298
298
|
block-size: 1.25rem;
|
|
299
299
|
/* Matches the xs Avatar's initials. */
|
|
300
|
-
font-size: calc(var(--ui-font-size) * 0.5625);
|
|
300
|
+
font-size: calc(var(--ui-font-size-md) * 0.5625);
|
|
301
301
|
line-height: 1;
|
|
302
302
|
}
|
|
303
303
|
.overflow[hidden]:where([data-hn-ui-avatar-group]) {
|