@motion-proto/live-tokens 0.91.2 → 0.93.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/.claude/skills/live-tokens-create-page/SKILL.md +10 -3
- package/.claude/skills/live-tokens-set-type/SKILL.md +3 -3
- package/CHANGELOG.md +171 -0
- package/bin/lib/tokenVocabulary.mjs +2 -2
- package/bin/rules/testRuns.mjs +1 -1
- package/bin/rules/tokens.mjs +3 -3
- package/dist-plugin/chunk-7XSUHJUH.js +1652 -0
- package/dist-plugin/index.cjs +950 -15
- package/dist-plugin/index.js +1 -1
- package/dist-plugin/setType/index.cjs +23 -2
- package/dist-plugin/setType/index.js +23 -2
- package/dist-plugin/tokensCssMigrations/index.cjs +954 -15
- package/dist-plugin/tokensCssMigrations/index.d.cts +23 -3
- package/dist-plugin/tokensCssMigrations/index.d.ts +23 -3
- package/dist-plugin/tokensCssMigrations/index.js +5 -1
- package/package.json +1 -1
- package/src/app/site.css +243 -9
- package/src/demo/TestingLoops.svelte +9 -9
- package/src/editor/core/fonts/weightCoverage.ts +31 -2
- package/src/editor/core/palettes/tokenRegistry.ts +36 -37
- package/src/editor/core/store/editorKeybindings.ts +1 -1
- package/src/editor/core/themes/parsers/mediaBlocks.ts +46 -0
- package/src/editor/core/typeScale/typeScale.ts +341 -0
- package/src/editor/skill-atlas/SourcePane.svelte +3 -3
- package/src/editor/skill-atlas/skillSources.generated.ts +2 -2
- package/src/editor/skill-atlas/trees/create-page.ts +14 -14
- package/src/editor/skill-atlas/trees/set-type.ts +2 -2
- package/src/editor/ui/UIFontFamilySelector.svelte +5 -1
- package/src/editor/ui/UIFontWeightSelector.svelte +4 -0
- package/src/editor/ui/UILetterSpacingSelector.svelte +4 -0
- package/src/editor/ui/UILineHeightSelector.svelte +4 -0
- package/src/editor/ui/UITextTransformSelector.svelte +4 -0
- package/src/editor/ui/UIVariantSelector.svelte +6 -8
- package/src/editor/ui/VariablesTab.svelte +4 -4
- package/src/editor/ui/colors/ColorsTab.svelte +99 -5
- package/src/editor/ui/colors/paletteBaseColor.ts +17 -0
- package/src/editor/ui/sections/textStyles.ts +44 -92
- package/src/editor/ui/text-styles/ControlBody.svelte +100 -0
- package/src/editor/ui/text-styles/ResetIcon.svelte +60 -0
- package/src/editor/ui/text-styles/StepRow.svelte +330 -0
- package/src/editor/ui/text-styles/StyleControl.svelte +128 -0
- package/src/editor/ui/text-styles/TextStylesSection.svelte +551 -0
- package/src/editor/ui/text-styles/textStyleEdits.ts +308 -0
- package/src/editor/ui/variantScales.ts +16 -0
- package/src/system/styles/tokens.css +412 -48
- package/src/testing-js/page-compliance.contract.js +233 -80
- package/src/testing-js/page-compliance.contract.js.map +1 -1
- package/dist-plugin/chunk-GWMEYBZ2.js +0 -715
- package/src/editor/ui/TextStylesSection.svelte +0 -171
|
@@ -1,110 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
1
|
+
import {
|
|
2
|
+
DEFAULTS,
|
|
3
|
+
EYEBROW_PREFIX,
|
|
4
|
+
STEPS,
|
|
5
|
+
USAGES,
|
|
6
|
+
stepPrefix,
|
|
7
|
+
type StylePrefix,
|
|
8
|
+
type Usage,
|
|
9
|
+
} from '../../core/typeScale/typeScale';
|
|
11
10
|
|
|
12
11
|
export interface TextStyle {
|
|
13
12
|
name: string;
|
|
14
13
|
label: string;
|
|
15
14
|
/** Variable prefix, e.g. `--heading-xl`; suffixes form each axis var. */
|
|
16
|
-
prefix:
|
|
17
|
-
/**
|
|
15
|
+
prefix: StylePrefix;
|
|
16
|
+
/** The elements `site.css` styles with it, or else its class. */
|
|
18
17
|
defaultElement: string;
|
|
19
18
|
preview: string;
|
|
20
19
|
/** Whether this style exposes an editable `-text-transform` axis. */
|
|
21
20
|
hasTextTransform?: boolean;
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
const USAGE_LABEL: Readonly<Record<Usage, string>> = {
|
|
24
|
+
display: 'Display',
|
|
25
|
+
heading: 'Heading',
|
|
26
|
+
body: 'Body',
|
|
27
|
+
editorial: 'Editorial',
|
|
28
|
+
code: 'Code',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const USAGE_PREVIEW: Readonly<Record<Usage, string>> = {
|
|
32
|
+
display: 'Tokens that move with every edit',
|
|
33
|
+
heading: 'Choosing a palette for the launch page',
|
|
34
|
+
body: 'Body copy for comfortable reading.',
|
|
35
|
+
editorial: 'A pull quote sets the tone for the essay.',
|
|
36
|
+
code: 'const total = sum(items.map(price));',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Every text style in `tokens.css` order: each usage's steps, largest first, then the eyebrow. */
|
|
24
40
|
export const TEXT_STYLES: TextStyle[] = [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: 'heading-md',
|
|
41
|
-
label: 'Heading MD',
|
|
42
|
-
prefix: '--heading-md',
|
|
43
|
-
defaultElement: 'h3',
|
|
44
|
-
preview: 'Subsection heading',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'heading-sm',
|
|
48
|
-
label: 'Heading SM',
|
|
49
|
-
prefix: '--heading-sm',
|
|
50
|
-
defaultElement: 'h4',
|
|
51
|
-
preview: 'Minor heading',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'body-md',
|
|
55
|
-
label: 'Body MD',
|
|
56
|
-
prefix: '--body-md',
|
|
57
|
-
defaultElement: 'p',
|
|
58
|
-
preview: 'Body copy for comfortable reading.',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'body-sm',
|
|
62
|
-
label: 'Body SM',
|
|
63
|
-
prefix: '--body-sm',
|
|
64
|
-
defaultElement: 'small',
|
|
65
|
-
preview: 'Captions and secondary text.',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'editorial-xl',
|
|
69
|
-
label: 'Editorial XL',
|
|
70
|
-
prefix: '--editorial-xl',
|
|
71
|
-
defaultElement: 'editorial-xl',
|
|
72
|
-
preview: 'Pull quotes and opening statements.',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'editorial-lg',
|
|
76
|
-
label: 'Editorial LG',
|
|
77
|
-
prefix: '--editorial-lg',
|
|
78
|
-
defaultElement: 'editorial-lg',
|
|
79
|
-
preview: 'Standfirsts and section ledes.',
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'editorial-md',
|
|
83
|
-
label: 'Editorial MD',
|
|
84
|
-
prefix: '--editorial-md',
|
|
85
|
-
defaultElement: 'editorial-md',
|
|
86
|
-
preview: 'Long-form reading, set in the editorial face.',
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: 'editorial-sm',
|
|
90
|
-
label: 'Editorial SM',
|
|
91
|
-
prefix: '--editorial-sm',
|
|
92
|
-
defaultElement: 'editorial-sm',
|
|
93
|
-
preview: 'Captions, credits, and asides.',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: 'code',
|
|
97
|
-
label: 'Code',
|
|
98
|
-
prefix: '--code',
|
|
99
|
-
defaultElement: 'code',
|
|
100
|
-
preview: 'const total = sum(items);',
|
|
101
|
-
},
|
|
41
|
+
...USAGES.flatMap((usage) =>
|
|
42
|
+
[...STEPS].reverse().map((step): TextStyle => {
|
|
43
|
+
const name = `${usage}-${step}`;
|
|
44
|
+
const elements = DEFAULTS.usages[usage].elements[step];
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
label: `${USAGE_LABEL[usage]} ${step.toUpperCase()}`,
|
|
48
|
+
prefix: stepPrefix(usage, step),
|
|
49
|
+
defaultElement: elements ? elements.join(', ') : `.${name}`,
|
|
50
|
+
preview: USAGE_PREVIEW[usage],
|
|
51
|
+
};
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
102
54
|
{
|
|
103
55
|
name: 'eyebrow',
|
|
104
56
|
label: 'Eyebrow',
|
|
105
|
-
prefix:
|
|
106
|
-
defaultElement: 'eyebrow',
|
|
107
|
-
preview: '
|
|
57
|
+
prefix: EYEBROW_PREFIX,
|
|
58
|
+
defaultElement: '.eyebrow',
|
|
59
|
+
preview: 'Release notes',
|
|
108
60
|
hasTextTransform: true,
|
|
109
61
|
},
|
|
110
62
|
];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
width: string;
|
|
6
|
+
/** Null draws no link bar. */
|
|
7
|
+
linked: boolean | null;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
/** Content after the body, such as a lock or a unit. */
|
|
10
|
+
after?: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { width, linked, children, after }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
{#if linked === null}
|
|
17
|
+
<div class="ctl-body" style:--w={width}>{@render children()}</div>
|
|
18
|
+
{:else}
|
|
19
|
+
<div class="linkwrap" class:is-unlinked={!linked}>
|
|
20
|
+
<div class="ctl-body" style:--w={width}>{@render children()}</div>
|
|
21
|
+
{@render after?.()}
|
|
22
|
+
</div>
|
|
23
|
+
{/if}
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
/* The link bar matches the editor's token selectors: a white bar flush
|
|
27
|
+
against a linked control, an amber tick beside an indented unlinked one. */
|
|
28
|
+
.linkwrap {
|
|
29
|
+
position: relative;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: var(--ui-space-6);
|
|
33
|
+
min-width: 0;
|
|
34
|
+
padding-left: 4px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.linkwrap::before {
|
|
38
|
+
content: "";
|
|
39
|
+
position: absolute;
|
|
40
|
+
top: 50%;
|
|
41
|
+
left: 0;
|
|
42
|
+
width: 4px;
|
|
43
|
+
height: 1.75rem;
|
|
44
|
+
background: var(--ui-text-primary);
|
|
45
|
+
border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md);
|
|
46
|
+
transform: translateY(-50%);
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
transition:
|
|
49
|
+
width 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
50
|
+
height 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
51
|
+
background-color 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.linkwrap.is-unlinked {
|
|
55
|
+
padding-left: 10px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.linkwrap.is-unlinked::before {
|
|
59
|
+
width: 2px;
|
|
60
|
+
height: 0.875rem;
|
|
61
|
+
background: var(--ui-link-broken);
|
|
62
|
+
border-radius: 1px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Unlinking indents the body by 6px and narrows it by as much, so whatever
|
|
66
|
+
follows holds its position. */
|
|
67
|
+
.ctl-body {
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
width: var(--w);
|
|
72
|
+
min-width: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.is-unlinked > .ctl-body {
|
|
76
|
+
width: calc(var(--w) - 6px);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ctl-body > :global(*) {
|
|
80
|
+
flex: 1 1 auto;
|
|
81
|
+
min-width: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ctl-body :global(.ui-token-selector) {
|
|
85
|
+
display: block;
|
|
86
|
+
width: 100%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* The trigger names the value; a resolved-value line under it would push
|
|
90
|
+
the lock off the trigger's centre line. */
|
|
91
|
+
.ctl-body :global(.ui-ts-meta-text) {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (prefers-reduced-motion: reduce) {
|
|
96
|
+
.linkwrap::before {
|
|
97
|
+
transition: none;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Names the value in the button's accessible label. */
|
|
6
|
+
label: string;
|
|
7
|
+
visible: boolean;
|
|
8
|
+
onreset: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { label, visible, onreset }: Props = $props();
|
|
12
|
+
|
|
13
|
+
// The icon leaves the DOM once its value resets, so focus moves to the
|
|
14
|
+
// control it restored.
|
|
15
|
+
async function reset(e: MouseEvent) {
|
|
16
|
+
const scope = (e.currentTarget as HTMLElement).closest<HTMLElement>('[data-reset-scope]');
|
|
17
|
+
onreset();
|
|
18
|
+
await tick();
|
|
19
|
+
scope?.querySelector<HTMLElement>('input, select, button:not(.reset)')?.focus();
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if visible}
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
class="reset"
|
|
27
|
+
aria-label="Reset {label} to default"
|
|
28
|
+
title="Reset to default"
|
|
29
|
+
onclick={reset}
|
|
30
|
+
>
|
|
31
|
+
<i class="fas fa-rotate-left" aria-hidden="true"></i>
|
|
32
|
+
</button>
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.reset {
|
|
37
|
+
flex: none;
|
|
38
|
+
display: inline-grid;
|
|
39
|
+
place-items: center;
|
|
40
|
+
width: 18px;
|
|
41
|
+
height: 18px;
|
|
42
|
+
padding: 0;
|
|
43
|
+
background: none;
|
|
44
|
+
border: none;
|
|
45
|
+
border-radius: var(--ui-radius-md);
|
|
46
|
+
color: var(--ui-text-tertiary);
|
|
47
|
+
font-size: 0.6875rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.reset:hover {
|
|
52
|
+
color: var(--ui-text-primary);
|
|
53
|
+
background: var(--ui-surface-higher);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.reset:focus-visible {
|
|
57
|
+
outline: 2px solid var(--ui-text-primary);
|
|
58
|
+
outline-offset: 1px;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import UIFontFamilySelector from '../UIFontFamilySelector.svelte';
|
|
3
|
+
import UIFontWeightSelector from '../UIFontWeightSelector.svelte';
|
|
4
|
+
import UILetterSpacingSelector from '../UILetterSpacingSelector.svelte';
|
|
5
|
+
import UILineHeightSelector from '../UILineHeightSelector.svelte';
|
|
6
|
+
import UITextTransformSelector from '../UITextTransformSelector.svelte';
|
|
7
|
+
import type { TextStyle } from '../sections/textStyles';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULTS,
|
|
10
|
+
VIEWPORTS,
|
|
11
|
+
axisName,
|
|
12
|
+
sizeName,
|
|
13
|
+
usageName,
|
|
14
|
+
type StyleAxis,
|
|
15
|
+
type Viewport,
|
|
16
|
+
} from '../../core/typeScale/typeScale';
|
|
17
|
+
import ControlBody from './ControlBody.svelte';
|
|
18
|
+
import ResetIcon from './ResetIcon.svelte';
|
|
19
|
+
import StyleControl from './StyleControl.svelte';
|
|
20
|
+
import {
|
|
21
|
+
SIZE_RANGE_PX,
|
|
22
|
+
SMALL_SIZE_PX,
|
|
23
|
+
faceLabel,
|
|
24
|
+
formatPx,
|
|
25
|
+
isLinked,
|
|
26
|
+
isStored,
|
|
27
|
+
linkExpression,
|
|
28
|
+
sizesOf,
|
|
29
|
+
valueOf,
|
|
30
|
+
weightLabel,
|
|
31
|
+
type LinkAxis,
|
|
32
|
+
type StyleTarget,
|
|
33
|
+
type TextStyleEdits,
|
|
34
|
+
type TokenView,
|
|
35
|
+
} from './textStyleEdits';
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
target: StyleTarget;
|
|
39
|
+
style: TextStyle;
|
|
40
|
+
view: TokenView;
|
|
41
|
+
/** The viewport the sample renders at and the sizes highlight. */
|
|
42
|
+
viewport: Viewport;
|
|
43
|
+
edits: TextStyleEdits;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let { target, style, view, viewport, edits }: Props = $props();
|
|
47
|
+
|
|
48
|
+
const VIEWPORT_LABEL: Record<Viewport, string> = { desktop: 'Desktop', tablet: 'Tablet', phone: 'Phone' };
|
|
49
|
+
|
|
50
|
+
let sizes = $derived(sizesOf(view, target));
|
|
51
|
+
let prefix = $derived(target.prefix);
|
|
52
|
+
const axis = (a: StyleAxis) => axisName(prefix, a);
|
|
53
|
+
let chip = $derived(
|
|
54
|
+
target.step === null
|
|
55
|
+
? style.defaultElement
|
|
56
|
+
: DEFAULTS.usages[target.usage].elements[target.step]?.join(', '),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
let sampleStyle = $derived(
|
|
60
|
+
[
|
|
61
|
+
`font-family: var(${axis('font-family')})`,
|
|
62
|
+
`font-size: var(${sizeName(prefix, viewport)})`,
|
|
63
|
+
`font-weight: var(${axis('font-weight')})`,
|
|
64
|
+
`line-height: var(${axis('line-height')})`,
|
|
65
|
+
`letter-spacing: var(${axis('letter-spacing')})`,
|
|
66
|
+
...(style.hasTextTransform ? [`text-transform: var(${axis('text-transform')})`] : []),
|
|
67
|
+
].join('; '),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// A typed value that the store leaves unchanged would otherwise stay in the
|
|
71
|
+
// input, so the input re-reads the size the store now resolves.
|
|
72
|
+
function editSize(vp: Viewport, input: HTMLInputElement) {
|
|
73
|
+
edits.setSize(target, vp, input.valueAsNumber);
|
|
74
|
+
input.value = formatPx(sizesOf(edits.view(), target)[vp].px);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function link(a: LinkAxis, label: (value: string) => string) {
|
|
78
|
+
return {
|
|
79
|
+
linked: isLinked(view, target, a),
|
|
80
|
+
text: label(valueOf(view, usageName(target.usage, a))),
|
|
81
|
+
title: `Linked to ${linkExpression(target, a)}`,
|
|
82
|
+
ontoggle: () => edits.toggleLink(target, a),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const write = (a: StyleAxis) => (value: string | null) => edits.writeToken(axis(a), value);
|
|
87
|
+
const reset = (a: StyleAxis) => () => edits.resetKey(axis(a));
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<div class="step">
|
|
91
|
+
<div class="step-id">
|
|
92
|
+
<code class="step-name">{style.name}</code>
|
|
93
|
+
{#if chip}
|
|
94
|
+
<span class="chip">{chip}</span>
|
|
95
|
+
{/if}
|
|
96
|
+
{#if sizes[viewport].px < SMALL_SIZE_PX}
|
|
97
|
+
<span class="flag">Below {SMALL_SIZE_PX}px</span>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="sizes">
|
|
102
|
+
{#each VIEWPORTS as vp (vp)}
|
|
103
|
+
{@const size = sizes[vp]}
|
|
104
|
+
<div class="size-row" class:is-current={vp === viewport} data-reset-scope>
|
|
105
|
+
<span class="size-label">{VIEWPORT_LABEL[vp]}</span>
|
|
106
|
+
<ControlBody width="4.75rem" linked={!size.edited}>
|
|
107
|
+
<input
|
|
108
|
+
class="size-input"
|
|
109
|
+
type="number"
|
|
110
|
+
min={SIZE_RANGE_PX.min}
|
|
111
|
+
max={SIZE_RANGE_PX.max}
|
|
112
|
+
step="any"
|
|
113
|
+
value={formatPx(size.px)}
|
|
114
|
+
aria-label="{style.name} {vp} size in px"
|
|
115
|
+
title={size.edited ? `Recommended ${formatPx(size.recommendedPx)}px` : 'Recommended size'}
|
|
116
|
+
onchange={(e) => editSize(vp, e.currentTarget)}
|
|
117
|
+
/>
|
|
118
|
+
{#snippet after()}
|
|
119
|
+
<span class="unit">px</span>
|
|
120
|
+
<ResetIcon
|
|
121
|
+
label="{style.name} {vp} size"
|
|
122
|
+
visible={size.edited}
|
|
123
|
+
onreset={() => edits.resetSize(target, vp)}
|
|
124
|
+
/>
|
|
125
|
+
{/snippet}
|
|
126
|
+
</ControlBody>
|
|
127
|
+
</div>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="step-sample" style={sampleStyle}>{style.preview}</div>
|
|
132
|
+
|
|
133
|
+
<div class="step-controls">
|
|
134
|
+
<StyleControl
|
|
135
|
+
label="Face"
|
|
136
|
+
name="{style.name} face"
|
|
137
|
+
width="10rem"
|
|
138
|
+
resetVisible={isStored(view, axis('font-family'))}
|
|
139
|
+
onreset={reset('font-family')}
|
|
140
|
+
link={link('font-family', faceLabel)}
|
|
141
|
+
>
|
|
142
|
+
<UIFontFamilySelector variable={axis('font-family')} onwrite={write('font-family')} />
|
|
143
|
+
</StyleControl>
|
|
144
|
+
<StyleControl
|
|
145
|
+
label="Weight"
|
|
146
|
+
name="{style.name} weight"
|
|
147
|
+
width="8rem"
|
|
148
|
+
resetVisible={isStored(view, axis('font-weight'))}
|
|
149
|
+
onreset={reset('font-weight')}
|
|
150
|
+
link={link('font-weight', weightLabel)}
|
|
151
|
+
>
|
|
152
|
+
<UIFontWeightSelector variable={axis('font-weight')} onwrite={write('font-weight')} />
|
|
153
|
+
</StyleControl>
|
|
154
|
+
<StyleControl
|
|
155
|
+
label="Line height"
|
|
156
|
+
name="{style.name} line height"
|
|
157
|
+
width="8rem"
|
|
158
|
+
resetVisible={isStored(view, axis('line-height'))}
|
|
159
|
+
onreset={reset('line-height')}
|
|
160
|
+
>
|
|
161
|
+
<UILineHeightSelector variable={axis('line-height')} onwrite={write('line-height')} />
|
|
162
|
+
</StyleControl>
|
|
163
|
+
<StyleControl
|
|
164
|
+
label="Letter spacing"
|
|
165
|
+
name="{style.name} letter spacing"
|
|
166
|
+
width="9.5rem"
|
|
167
|
+
resetVisible={isStored(view, axis('letter-spacing'))}
|
|
168
|
+
onreset={reset('letter-spacing')}
|
|
169
|
+
>
|
|
170
|
+
<UILetterSpacingSelector variable={axis('letter-spacing')} onwrite={write('letter-spacing')} />
|
|
171
|
+
</StyleControl>
|
|
172
|
+
{#if style.hasTextTransform}
|
|
173
|
+
<StyleControl
|
|
174
|
+
label="Transform"
|
|
175
|
+
name="{style.name} transform"
|
|
176
|
+
width="8rem"
|
|
177
|
+
resetVisible={isStored(view, axis('text-transform'))}
|
|
178
|
+
onreset={reset('text-transform')}
|
|
179
|
+
>
|
|
180
|
+
<UITextTransformSelector variable={axis('text-transform')} onwrite={write('text-transform')} />
|
|
181
|
+
</StyleControl>
|
|
182
|
+
{/if}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<style>
|
|
187
|
+
.step {
|
|
188
|
+
display: grid;
|
|
189
|
+
grid-template-columns: 7.5rem 11.5rem minmax(0, 1fr);
|
|
190
|
+
column-gap: var(--ui-space-16);
|
|
191
|
+
row-gap: var(--ui-space-10);
|
|
192
|
+
padding: var(--ui-space-12) var(--ui-space-10);
|
|
193
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.step:last-child {
|
|
197
|
+
border-bottom: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.step-id {
|
|
201
|
+
grid-row: 1 / span 2;
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
align-items: flex-start;
|
|
205
|
+
gap: var(--ui-space-6);
|
|
206
|
+
min-width: 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.step-name {
|
|
210
|
+
font-family: var(--ui-font-mono);
|
|
211
|
+
font-size: var(--ui-font-size-sm);
|
|
212
|
+
color: var(--ui-text-primary);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.chip {
|
|
216
|
+
padding: 1px var(--ui-space-8);
|
|
217
|
+
border: 1px solid var(--ui-border-low);
|
|
218
|
+
border-radius: var(--ui-radius-full);
|
|
219
|
+
color: var(--ui-text-secondary);
|
|
220
|
+
font-family: var(--ui-font-mono);
|
|
221
|
+
font-size: var(--ui-font-size-xs);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.flag {
|
|
225
|
+
padding: 1px var(--ui-space-8);
|
|
226
|
+
border: 1px dashed var(--ui-border-high);
|
|
227
|
+
border-radius: var(--ui-radius-full);
|
|
228
|
+
color: var(--ui-text-primary);
|
|
229
|
+
font-size: var(--ui-font-size-xs);
|
|
230
|
+
white-space: nowrap;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.sizes {
|
|
234
|
+
grid-row: 1 / span 2;
|
|
235
|
+
display: grid;
|
|
236
|
+
align-content: start;
|
|
237
|
+
gap: var(--ui-space-6);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.size-row {
|
|
241
|
+
display: grid;
|
|
242
|
+
grid-template-columns: 3.5rem auto;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: var(--ui-space-4);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.size-label {
|
|
248
|
+
color: var(--ui-text-muted);
|
|
249
|
+
font-size: var(--ui-font-size-xs);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.size-row.is-current .size-label {
|
|
253
|
+
color: var(--ui-text-primary);
|
|
254
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.size-input {
|
|
258
|
+
box-sizing: border-box;
|
|
259
|
+
width: 100%;
|
|
260
|
+
height: 1.75rem;
|
|
261
|
+
padding: 0 var(--ui-space-8);
|
|
262
|
+
background: var(--ui-surface-high);
|
|
263
|
+
border: 1px solid var(--ui-border-low);
|
|
264
|
+
border-radius: var(--ui-radius-md);
|
|
265
|
+
color: var(--ui-text-primary);
|
|
266
|
+
font: inherit;
|
|
267
|
+
font-size: var(--ui-font-size-sm);
|
|
268
|
+
font-variant-numeric: tabular-nums;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.size-input:hover {
|
|
272
|
+
border-color: var(--ui-border-high);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.size-input:focus-visible {
|
|
276
|
+
outline: 2px solid var(--ui-text-primary);
|
|
277
|
+
outline-offset: 2px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.unit {
|
|
281
|
+
flex: none;
|
|
282
|
+
color: var(--ui-text-muted);
|
|
283
|
+
font-size: var(--ui-font-size-xs);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Samples take the editor's ink; colour sits outside a text style. */
|
|
287
|
+
.step-sample {
|
|
288
|
+
grid-column: 3;
|
|
289
|
+
min-width: 0;
|
|
290
|
+
max-width: min(100%, 22ch);
|
|
291
|
+
color: var(--ui-text-primary);
|
|
292
|
+
display: -webkit-box;
|
|
293
|
+
-webkit-line-clamp: 2;
|
|
294
|
+
line-clamp: 2;
|
|
295
|
+
-webkit-box-orient: vertical;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
overflow-wrap: anywhere;
|
|
298
|
+
text-wrap: balance;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.step-controls {
|
|
302
|
+
grid-column: 3;
|
|
303
|
+
align-self: end;
|
|
304
|
+
display: flex;
|
|
305
|
+
flex-wrap: wrap;
|
|
306
|
+
gap: var(--ui-space-12);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@media (max-width: 720px) {
|
|
310
|
+
.step {
|
|
311
|
+
grid-template-columns: minmax(0, 1fr);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.step-id {
|
|
315
|
+
grid-row: auto;
|
|
316
|
+
flex-direction: row;
|
|
317
|
+
align-items: center;
|
|
318
|
+
flex-wrap: wrap;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.sizes {
|
|
322
|
+
grid-row: auto;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.step-sample,
|
|
326
|
+
.step-controls {
|
|
327
|
+
grid-column: auto;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
</style>
|