@motion-proto/live-tokens 0.8.0 → 0.10.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-add-component/SKILL.md +488 -0
- package/README.md +84 -29
- package/dist-plugin/index.cjs +177 -125
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +177 -125
- package/package.json +8 -2
- package/src/editor/component-editor/BadgeEditor.svelte +44 -42
- package/src/editor/component-editor/ButtonEditor.svelte +224 -0
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -7
- package/src/editor/component-editor/CornerBadgeEditor.svelte +44 -34
- package/src/editor/component-editor/ImageLightboxEditor.svelte +58 -0
- package/src/editor/component-editor/InputEditor.svelte +272 -0
- package/src/editor/component-editor/NotificationEditor.svelte +44 -65
- package/src/editor/component-editor/ProgressBarEditor.svelte +71 -87
- package/src/editor/component-editor/SegmentedControlEditor.svelte +98 -37
- package/src/editor/component-editor/SideNavigationEditor.svelte +342 -0
- package/src/editor/component-editor/index.ts +16 -1
- package/src/editor/component-editor/registry.ts +138 -28
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
- package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +9 -10
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +60 -36
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +38 -1
- package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -1
- package/src/editor/component-editor/scaffolding/componentSources.ts +3 -3
- package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
- package/src/editor/component-editor/scaffolding/siblings.ts +2 -2
- package/src/editor/component-editor/scaffolding/types.ts +2 -1
- package/src/editor/core/components/componentConfigKeys.ts +14 -3
- package/src/editor/core/components/componentConfigService.ts +7 -6
- package/src/editor/core/manifests/manifestService.ts +5 -4
- package/src/editor/core/storage/apiBase.ts +15 -0
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +1 -1
- package/src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts +41 -0
- package/src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts +59 -0
- package/src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts +29 -0
- package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +46 -0
- package/src/editor/core/themes/migrations/index.ts +10 -0
- package/src/editor/core/themes/slices/components.ts +9 -0
- package/src/editor/core/themes/themeInit.ts +3 -2
- package/src/editor/core/themes/themeService.ts +3 -2
- package/src/editor/index.ts +10 -1
- package/src/editor/pages/ComponentEditorPage.svelte +53 -3
- package/src/editor/pages/EditorShell.svelte +53 -3
- package/src/editor/ui/UIEasingSelector.svelte +240 -0
- package/src/editor/ui/variantScales.ts +34 -0
- package/src/system/components/Button.svelte +34 -85
- package/src/system/components/CollapsibleSection.svelte +1 -48
- package/src/system/components/CornerBadge.svelte +72 -138
- package/src/system/components/Dialog.svelte +24 -4
- package/src/system/components/ImageLightbox.svelte +578 -0
- package/src/system/components/Input.svelte +387 -0
- package/src/system/components/ProgressBar.svelte +62 -258
- package/src/system/components/SectionDivider.svelte +117 -43
- package/src/system/components/SegmentedControl.svelte +81 -15
- package/src/system/components/SideNavigation.svelte +777 -0
- package/src/system/styles/tokens.css +43 -0
- package/src/system/styles/tokens.generated.css +4 -183
- package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 6/7.",
|
|
6
6
|
"keywords": [
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"src/system",
|
|
24
24
|
"src/app/site.css",
|
|
25
25
|
"dist-plugin",
|
|
26
|
+
".claude/skills",
|
|
26
27
|
"!**/*.test.ts",
|
|
27
28
|
"!**/*.spec.ts",
|
|
28
|
-
"!**/__tests__/**"
|
|
29
|
+
"!**/__tests__/**",
|
|
30
|
+
"!src/system/components/Stat.svelte",
|
|
31
|
+
"!src/system/components/StatEditor.svelte"
|
|
29
32
|
],
|
|
30
33
|
"exports": {
|
|
31
34
|
".": {
|
|
@@ -94,6 +97,9 @@
|
|
|
94
97
|
"@tsconfig/svelte": "^5.0.8",
|
|
95
98
|
"@types/node": "^24.12.0",
|
|
96
99
|
"happy-dom": "^20.9.0",
|
|
100
|
+
"highlight.js": "^11.11.1",
|
|
101
|
+
"marked": "^18.0.4",
|
|
102
|
+
"mermaid": "^11.15.0",
|
|
97
103
|
"sass": "^1.98.0",
|
|
98
104
|
"svelte": "^5.55.5",
|
|
99
105
|
"svelte-check": "^4.4.8",
|
|
@@ -1,58 +1,59 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import type { Token, TypeGroupConfig } from './scaffolding/types';
|
|
2
|
+
import type { Token } from './scaffolding/types';
|
|
4
3
|
import { badgeVariants } from '../../system/components/Badge.svelte';
|
|
5
4
|
|
|
6
5
|
export const component = 'badge';
|
|
7
6
|
const variants = badgeVariants;
|
|
8
7
|
type Variant = typeof variants[number];
|
|
9
8
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
9
|
+
// Base part: shape, spacing, and typography props that almost never differ
|
|
10
|
+
// between variants. Defaults are identical across the palette; the linked
|
|
11
|
+
// block surfaces that equality so authors edit one value and it co-applies.
|
|
12
|
+
// Tagged with `element` so StateBlock partitions the panel into labeled
|
|
13
|
+
// `frame` and `text` subsections instead of one long column.
|
|
14
|
+
function variantBaseTokens(v: Variant): Token[] {
|
|
13
15
|
return [
|
|
14
|
-
{ label: '
|
|
15
|
-
{ label: '
|
|
16
|
-
{ label: 'border width', canBeLinked: true, groupKey: 'border-width', variable: `--badge-${
|
|
17
|
-
{ label: '
|
|
18
|
-
{ label: '
|
|
19
|
-
{ label: '
|
|
20
|
-
{ label: '
|
|
21
|
-
{ label: '
|
|
16
|
+
{ label: 'padding', canBeLinked: true, groupKey: 'padding', variable: `--badge-${v}-padding`, element: 'frame' },
|
|
17
|
+
{ label: 'corner radius', canBeLinked: true, groupKey: 'radius', variable: `--badge-${v}-radius`, element: 'frame' },
|
|
18
|
+
{ label: 'border width', canBeLinked: true, groupKey: 'border-width', variable: `--badge-${v}-border-width`, element: 'frame' },
|
|
19
|
+
{ label: 'badge shadow', canBeLinked: true, groupKey: 'shadow', variable: `--badge-${v}-shadow`, element: 'frame' },
|
|
20
|
+
{ label: 'backdrop blur', canBeLinked: true, groupKey: 'blur', variable: `--badge-${v}-blur`, element: 'frame' },
|
|
21
|
+
{ label: 'icon size', canBeLinked: true, groupKey: 'icon-size', variable: `--badge-${v}-icon-size`, element: 'frame' },
|
|
22
|
+
{ label: 'font family', canBeLinked: true, groupKey: 'font-family', variable: `--badge-${v}-text-font-family`, element: 'text' },
|
|
23
|
+
{ label: 'font size', canBeLinked: true, groupKey: 'font-size', variable: `--badge-${v}-text-font-size`, element: 'text' },
|
|
24
|
+
{ label: 'font weight', canBeLinked: true, groupKey: 'font-weight', variable: `--badge-${v}-text-font-weight`, element: 'text' },
|
|
25
|
+
{ label: 'line height', canBeLinked: true, groupKey: 'line-height', variable: `--badge-${v}-text-line-height`, element: 'text' },
|
|
22
26
|
];
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
sizeVariable: `--badge-${variant}-text-font-size`,
|
|
30
|
-
weightVariable: `--badge-${variant}-text-font-weight`,
|
|
31
|
-
lineHeightVariable: `--badge-${variant}-text-line-height`,
|
|
32
|
-
}];
|
|
33
|
-
}
|
|
34
|
-
function variantTypeGroupTokens(variant: Variant): Token[] {
|
|
28
|
+
|
|
29
|
+
// Colors part: the shade triple that actually distinguishes one variant from
|
|
30
|
+
// another. groupKey keys siblings across variants so a user can opt into a
|
|
31
|
+
// shared value via the link UI, but they default divergent (variant = palette).
|
|
32
|
+
function variantColorTokens(v: Variant): Token[] {
|
|
35
33
|
return [
|
|
36
|
-
{ label: '
|
|
37
|
-
{ label: '
|
|
38
|
-
{ label: '
|
|
39
|
-
{ label: 'line height', canBeLinked: true, groupKey: 'line-height', variable: `--badge-${variant}-text-line-height` },
|
|
34
|
+
{ label: 'surface color', groupKey: 'surface', variable: `--badge-${v}-surface` },
|
|
35
|
+
{ label: 'border color', groupKey: 'border', variable: `--badge-${v}-border` },
|
|
36
|
+
{ label: 'text color', groupKey: 'text', variable: `--badge-${v}-text` },
|
|
40
37
|
];
|
|
41
38
|
}
|
|
39
|
+
|
|
40
|
+
function variantStates(v: Variant): Record<string, Token[]> {
|
|
41
|
+
return { base: variantBaseTokens(v), colors: variantColorTokens(v) };
|
|
42
|
+
}
|
|
43
|
+
|
|
42
44
|
export const allTokens: Token[] = variants.flatMap((v) => [
|
|
43
|
-
...
|
|
44
|
-
...
|
|
45
|
-
...variantTypeGroupTokens(v),
|
|
45
|
+
...variantBaseTokens(v),
|
|
46
|
+
...variantColorTokens(v),
|
|
46
47
|
]);
|
|
47
48
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
const linkableProps = [
|
|
51
|
-
'border-width', 'radius', 'padding', 'shadow', 'blur', 'icon-size',
|
|
52
|
-
'text-font-family', 'text-font-size', 'text-font-weight', 'text-line-height',
|
|
53
|
-
] as const;
|
|
49
|
+
// Only base props join the linked block — colors are intentionally per-variant
|
|
50
|
+
// so each shade can be retuned without dragging shape/type behind it.
|
|
54
51
|
const linkableContexts = new Map<string, string>(
|
|
55
|
-
variants.flatMap((v) =>
|
|
52
|
+
variants.flatMap((v) =>
|
|
53
|
+
variantBaseTokens(v)
|
|
54
|
+
.filter((t) => t.canBeLinked)
|
|
55
|
+
.map((t) => [t.variable, `${v} base`] as [string, string]),
|
|
56
|
+
),
|
|
56
57
|
);
|
|
57
58
|
|
|
58
59
|
const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
|
|
@@ -67,7 +68,9 @@
|
|
|
67
68
|
import { buildSiblings } from './scaffolding/siblings';
|
|
68
69
|
|
|
69
70
|
let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
|
|
70
|
-
let
|
|
71
|
+
let visibleVariantStates = $derived((v: Variant) => Object.fromEntries(
|
|
72
|
+
Object.entries(variantStates(v)).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
|
|
73
|
+
) as Record<string, Token[]>);
|
|
71
74
|
</script>
|
|
72
75
|
|
|
73
76
|
<ComponentEditorBase {component} title="Badge" description="Pill-shaped badges with color variants." tokens={allTokens} {linked} variants={variantOptions}>
|
|
@@ -75,10 +78,9 @@
|
|
|
75
78
|
<VariantGroup
|
|
76
79
|
name={v}
|
|
77
80
|
title={v.charAt(0).toUpperCase() + v.slice(1)}
|
|
78
|
-
states={
|
|
79
|
-
typeGroups={{ [v]: variantTypeGroups(v) }}
|
|
81
|
+
states={visibleVariantStates(v)}
|
|
80
82
|
{component}
|
|
81
|
-
siblings={buildSiblings(variants, v,
|
|
83
|
+
siblings={buildSiblings(variants, v, variantStates)}
|
|
82
84
|
>
|
|
83
85
|
<div class="badge-showcase-grid">
|
|
84
86
|
<Badge variant={v}>{v.charAt(0).toUpperCase() + v.slice(1)}</Badge>
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { buildSiblings } from './scaffolding/siblings';
|
|
3
|
+
import type { Token } from './scaffolding/types';
|
|
4
|
+
|
|
5
|
+
export const component = 'button';
|
|
6
|
+
|
|
7
|
+
const variants = ['primary', 'secondary', 'outline', 'success', 'danger', 'warning'] as const;
|
|
8
|
+
type Variant = typeof variants[number];
|
|
9
|
+
const stateNames = ['default', 'hover', 'disabled'] as const;
|
|
10
|
+
type StateName = typeof stateNames[number];
|
|
11
|
+
function statePrefix(v: Variant, s: StateName): string {
|
|
12
|
+
return s === 'default' ? `--button-${v}` : `--button-${v}-${s}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Shape + type. These don't change per state in the runtime — promoted into
|
|
16
|
+
// their own "base" part so state panels carry only what genuinely varies.
|
|
17
|
+
// Tagged with `element` so StateBlock partitions base into labeled `frame`
|
|
18
|
+
// and `text` subsections, matching the Badge editor's layout.
|
|
19
|
+
function variantBaseTokens(v: Variant): Token[] {
|
|
20
|
+
return [
|
|
21
|
+
{ label: 'padding', canBeLinked: true, groupKey: 'padding', variable: `--button-${v}-padding`, element: 'frame' },
|
|
22
|
+
{ label: 'padding-top', canBeLinked: true, groupKey: 'padding-top', variable: `--button-${v}-padding-top`, hidden: true, element: 'frame' },
|
|
23
|
+
{ label: 'padding-right', canBeLinked: true, groupKey: 'padding-right', variable: `--button-${v}-padding-right`, hidden: true, element: 'frame' },
|
|
24
|
+
{ label: 'padding-bottom', canBeLinked: true, groupKey: 'padding-bottom', variable: `--button-${v}-padding-bottom`, hidden: true, element: 'frame' },
|
|
25
|
+
{ label: 'padding-left', canBeLinked: true, groupKey: 'padding-left', variable: `--button-${v}-padding-left`, hidden: true, element: 'frame' },
|
|
26
|
+
{ label: 'corner radius', canBeLinked: true, groupKey: 'radius', variable: `--button-${v}-radius`, element: 'frame' },
|
|
27
|
+
{ label: 'border width', canBeLinked: true, groupKey: 'border-width', variable: `--button-${v}-border-width`, element: 'frame' },
|
|
28
|
+
{ label: 'icon size', canBeLinked: true, groupKey: 'icon-size', variable: `--button-${v}-icon-size`, element: 'frame' },
|
|
29
|
+
{ label: 'font family', canBeLinked: true, groupKey: 'font-family', variable: `--button-${v}-text-font-family`, element: 'text' },
|
|
30
|
+
{ label: 'font size', canBeLinked: true, groupKey: 'font-size', variable: `--button-${v}-text-font-size`, element: 'text' },
|
|
31
|
+
{ label: 'font weight', canBeLinked: true, groupKey: 'font-weight', variable: `--button-${v}-text-font-weight`, element: 'text' },
|
|
32
|
+
{ label: 'line height', canBeLinked: true, groupKey: 'line-height', variable: `--button-${v}-text-line-height`, element: 'text' },
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// State panels carry only what changes between states: the three color slots.
|
|
37
|
+
function variantStateTokens(v: Variant, s: StateName): Token[] {
|
|
38
|
+
const colorVar = s === 'default' ? `--button-${v}-text` : `--button-${v}-${s}-text`;
|
|
39
|
+
return [
|
|
40
|
+
{ label: 'surface color', groupKey: 'surface', variable: `${statePrefix(v, s)}-surface` },
|
|
41
|
+
{ label: 'border color', groupKey: 'border', variable: `${statePrefix(v, s)}-border` },
|
|
42
|
+
{ label: 'text color', groupKey: 'text', variable: colorVar },
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Outline is the only variant that paints a surface tint on :active; the rest
|
|
47
|
+
// express press feedback through transform/shadow only. Expose just the one
|
|
48
|
+
// tunable property here rather than adding an active state to every variant.
|
|
49
|
+
const outlineActiveTokens: Token[] = [
|
|
50
|
+
{ label: 'surface color', groupKey: 'surface', variable: '--button-outline-active-surface' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function variantStates(v: Variant): Record<string, Token[]> {
|
|
54
|
+
const out: Record<string, Token[]> = {};
|
|
55
|
+
out.base = variantBaseTokens(v);
|
|
56
|
+
out.default = variantStateTokens(v, 'default');
|
|
57
|
+
out.hover = variantStateTokens(v, 'hover');
|
|
58
|
+
if (v === 'outline') out.active = outlineActiveTokens;
|
|
59
|
+
out.disabled = variantStateTokens(v, 'disabled');
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Small-size schema. One shared spec across all variants (matches the runtime
|
|
64
|
+
// `.small` rule, which is variant-agnostic). Per-side padding rows are hidden
|
|
65
|
+
// and surface only when the editor splits the padding control.
|
|
66
|
+
// Same frame/text element split as the default-size base for visual parity.
|
|
67
|
+
const smallStates: Record<string, Token[]> = {
|
|
68
|
+
small: [
|
|
69
|
+
{ label: 'padding', groupKey: 'small-padding', variable: '--button-small-padding', element: 'frame' },
|
|
70
|
+
{ label: 'padding-top', groupKey: 'small-padding-top', variable: '--button-small-padding-top', hidden: true, element: 'frame' },
|
|
71
|
+
{ label: 'padding-right', groupKey: 'small-padding-right', variable: '--button-small-padding-right', hidden: true, element: 'frame' },
|
|
72
|
+
{ label: 'padding-bottom', groupKey: 'small-padding-bottom', variable: '--button-small-padding-bottom', hidden: true, element: 'frame' },
|
|
73
|
+
{ label: 'padding-left', groupKey: 'small-padding-left', variable: '--button-small-padding-left', hidden: true, element: 'frame' },
|
|
74
|
+
{ label: 'icon size', groupKey: 'small-icon-size', variable: '--button-small-icon-size', element: 'frame' },
|
|
75
|
+
{ label: 'font size', groupKey: 'small-font-size', variable: '--button-small-text-font-size', element: 'text' },
|
|
76
|
+
{ label: 'font weight', groupKey: 'small-font-weight', variable: '--button-small-text-font-weight', element: 'text' },
|
|
77
|
+
{ label: 'line height', groupKey: 'small-line-height', variable: '--button-small-text-line-height', element: 'text' },
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
const smallTokensFlat: Token[] = Object.values(smallStates).flat();
|
|
81
|
+
|
|
82
|
+
export const allTokens: Token[] = [
|
|
83
|
+
...variants.flatMap((v) => Object.values(variantStates(v)).flat()),
|
|
84
|
+
...smallTokensFlat,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// All shape + type props live under each variant's "base" part; they link
|
|
88
|
+
// across variants from there. Small tokens stay in their own namespace —
|
|
89
|
+
// no cross-size linking.
|
|
90
|
+
const linkableContexts = new Map<string, string>(
|
|
91
|
+
variants.flatMap((v) => [
|
|
92
|
+
[`--button-${v}-padding`, `${v} base`] as const,
|
|
93
|
+
[`--button-${v}-radius`, `${v} base`] as const,
|
|
94
|
+
[`--button-${v}-border-width`, `${v} base`] as const,
|
|
95
|
+
[`--button-${v}-text-font-family`, `${v} base`] as const,
|
|
96
|
+
[`--button-${v}-text-font-size`, `${v} base`] as const,
|
|
97
|
+
[`--button-${v}-text-font-weight`, `${v} base`] as const,
|
|
98
|
+
[`--button-${v}-text-line-height`, `${v} base`] as const,
|
|
99
|
+
[`--button-${v}-icon-size`, `${v} base`] as const,
|
|
100
|
+
]),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<script lang="ts">
|
|
107
|
+
import Button from '../../system/components/Button.svelte';
|
|
108
|
+
import Toggle from '../ui/Toggle.svelte';
|
|
109
|
+
import VariantGroup from './scaffolding/VariantGroup.svelte';
|
|
110
|
+
import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
|
|
111
|
+
import { editorState, setComponentAlias } from '../core/store/editorStore';
|
|
112
|
+
import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
|
|
113
|
+
|
|
114
|
+
let shimmerRef = $derived($editorState.components.button?.aliases['--button-shimmer']);
|
|
115
|
+
let shimmerEnabled = $derived(!(shimmerRef?.kind === 'token' && shimmerRef.name === '--shimmer-off'));
|
|
116
|
+
|
|
117
|
+
function handleShimmerChange(checked: boolean) {
|
|
118
|
+
setComponentAlias('button', '--button-shimmer', { kind: 'token', name: checked ? '--shimmer-on' : '--shimmer-off' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let previewSize = $state<'default' | 'small'>('default');
|
|
122
|
+
|
|
123
|
+
let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
|
|
124
|
+
|
|
125
|
+
let visibleVariantStates = $derived((v: Variant) => Object.fromEntries(
|
|
126
|
+
Object.entries(variantStates(v)).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
|
|
127
|
+
) as Record<string, Token[]>);
|
|
128
|
+
|
|
129
|
+
let visibleSmallStates = $derived(Object.fromEntries(
|
|
130
|
+
Object.entries(smallStates).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
|
|
131
|
+
) as Record<string, Token[]>);
|
|
132
|
+
|
|
133
|
+
// At small size, no variant strip — the small spec is shared across all
|
|
134
|
+
// variants, so the strip would have nothing to navigate between.
|
|
135
|
+
let baseVariantOptions = $derived(previewSize === 'small' ? [] : variantOptions);
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
{#snippet sizeAction()}
|
|
139
|
+
<label>
|
|
140
|
+
<span>Size</span>
|
|
141
|
+
<select bind:value={previewSize}>
|
|
142
|
+
<option value="default">Default</option>
|
|
143
|
+
<option value="small">Small</option>
|
|
144
|
+
</select>
|
|
145
|
+
</label>
|
|
146
|
+
{/snippet}
|
|
147
|
+
|
|
148
|
+
<ComponentEditorBase {component} title="Button" description="Reusable button component with multiple variants and sizes." tokens={allTokens} {linked} variants={baseVariantOptions}>
|
|
149
|
+
{#if previewSize === 'default'}
|
|
150
|
+
{#each variants as v}
|
|
151
|
+
<VariantGroup
|
|
152
|
+
name={v}
|
|
153
|
+
title={v.charAt(0).toUpperCase() + v.slice(1)}
|
|
154
|
+
states={visibleVariantStates(v)}
|
|
155
|
+
{component}
|
|
156
|
+
siblings={buildSiblings(variants, v, variantStates)}
|
|
157
|
+
previewActions={sizeAction}
|
|
158
|
+
>
|
|
159
|
+
{#snippet extraPropertyRows(stateName)}
|
|
160
|
+
{#if stateName === 'hover'}
|
|
161
|
+
<div class="property-row">
|
|
162
|
+
<span class="property-label">hover shimmer</span>
|
|
163
|
+
<Toggle checked={shimmerEnabled} onchange={handleShimmerChange} />
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
{/snippet}
|
|
167
|
+
{#snippet children({ activeState })}
|
|
168
|
+
{@const forceClass = activeState === 'hover' ? 'force-hover' : ''}
|
|
169
|
+
{@const isDisabled = activeState === 'disabled'}
|
|
170
|
+
<div class="button-showcase-grid">
|
|
171
|
+
<div class="button-showcase-item">
|
|
172
|
+
<Button variant={v} disabled={isDisabled} class={forceClass}>{v.charAt(0).toUpperCase() + v.slice(1)}</Button>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="button-showcase-item">
|
|
175
|
+
<Button variant={v} icon="fas fa-star" iconPosition="left" disabled={isDisabled} class={forceClass}>With Icon</Button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{/snippet}
|
|
179
|
+
</VariantGroup>
|
|
180
|
+
{/each}
|
|
181
|
+
{:else}
|
|
182
|
+
<VariantGroup
|
|
183
|
+
name="small"
|
|
184
|
+
title="Small"
|
|
185
|
+
states={visibleSmallStates}
|
|
186
|
+
{component}
|
|
187
|
+
previewActions={sizeAction}
|
|
188
|
+
>
|
|
189
|
+
{#snippet children()}
|
|
190
|
+
<div class="small-preview">
|
|
191
|
+
{#each variants as v}
|
|
192
|
+
<Button variant={v} size="small">{v.charAt(0).toUpperCase() + v.slice(1)}</Button>
|
|
193
|
+
{/each}
|
|
194
|
+
{#each variants as v}
|
|
195
|
+
<Button variant={v} size="small" icon="fas fa-star" iconPosition="left">{v.charAt(0).toUpperCase() + v.slice(1)}</Button>
|
|
196
|
+
{/each}
|
|
197
|
+
</div>
|
|
198
|
+
{/snippet}
|
|
199
|
+
</VariantGroup>
|
|
200
|
+
{/if}
|
|
201
|
+
</ComponentEditorBase>
|
|
202
|
+
|
|
203
|
+
<style>
|
|
204
|
+
.button-showcase-grid {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-wrap: wrap;
|
|
207
|
+
gap: var(--space-16);
|
|
208
|
+
align-items: start;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.button-showcase-item {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
gap: var(--space-8);
|
|
215
|
+
align-items: flex-start;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.small-preview {
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-wrap: wrap;
|
|
221
|
+
gap: var(--space-12);
|
|
222
|
+
align-items: center;
|
|
223
|
+
}
|
|
224
|
+
</style>
|
|
@@ -6,15 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
const VARIANTS = ['chromeless', 'divider', 'container'] as const;
|
|
8
8
|
type Variant = typeof VARIANTS[number];
|
|
9
|
-
|
|
10
|
-
// the editor). The CSS keeps the historical `active` slug for compatibility;
|
|
11
|
-
// the UI surfaces it as "Current" because users read `:active` as click-press.
|
|
12
|
-
const HEADER_STATES = ['default', 'hover', 'active'] as const;
|
|
9
|
+
const HEADER_STATES = ['default', 'hover'] as const;
|
|
13
10
|
type HeaderState = typeof HEADER_STATES[number];
|
|
14
11
|
const HEADER_STATE_LABELS: Record<HeaderState, string> = {
|
|
15
12
|
default: 'Default',
|
|
16
13
|
hover: 'Hover',
|
|
17
|
-
active: 'Current',
|
|
18
14
|
};
|
|
19
15
|
|
|
20
16
|
const VARIANT_LABELS: Record<Variant, string> = {
|
|
@@ -154,12 +150,10 @@
|
|
|
154
150
|
{@const isContainerPart = activeState === 'Container'}
|
|
155
151
|
{@const isBody = activeState === 'Body'}
|
|
156
152
|
{@const forceClass = activeState === 'Header / Hover' ? 'force-hover' : ''}
|
|
157
|
-
{@const forceActive = activeState === 'Header / Current'}
|
|
158
153
|
<CollapsibleSection
|
|
159
154
|
variant={v}
|
|
160
155
|
label="Click to expand"
|
|
161
156
|
expanded={isBody}
|
|
162
|
-
active={forceActive}
|
|
163
157
|
class={forceClass}
|
|
164
158
|
>
|
|
165
159
|
<p style="margin: 0; color: var(--text-secondary);">
|
|
@@ -3,37 +3,45 @@
|
|
|
3
3
|
import { badgeVariants } from '../../system/components/Badge.svelte';
|
|
4
4
|
|
|
5
5
|
export const component = 'cornerbadge';
|
|
6
|
+
type Variant = typeof badgeVariants[number];
|
|
6
7
|
|
|
7
|
-
//
|
|
8
|
-
|
|
8
|
+
// Shape, spacing, type — uniform across variants, one flat token set.
|
|
9
|
+
// Authored from any variant's panel; edits write the same flat keys.
|
|
10
|
+
const baseTokens: Token[] = [
|
|
11
|
+
{ label: 'offset from corner', canBeLinked: true, groupKey: 'margin', variable: '--corner-badge-margin', element: 'frame' },
|
|
12
|
+
{ label: 'padding', canBeLinked: true, groupKey: 'padding', variable: '--corner-badge-padding', element: 'frame' },
|
|
13
|
+
{ label: 'padding-top', canBeLinked: true, groupKey: 'padding-top', variable: '--corner-badge-padding-top', hidden: true, element: 'frame' },
|
|
14
|
+
{ label: 'padding-right', canBeLinked: true, groupKey: 'padding-right', variable: '--corner-badge-padding-right', hidden: true, element: 'frame' },
|
|
15
|
+
{ label: 'padding-bottom', canBeLinked: true, groupKey: 'padding-bottom', variable: '--corner-badge-padding-bottom', hidden: true, element: 'frame' },
|
|
16
|
+
{ label: 'padding-left', canBeLinked: true, groupKey: 'padding-left', variable: '--corner-badge-padding-left', hidden: true, element: 'frame' },
|
|
17
|
+
{ label: 'outer corner radius', canBeLinked: true, groupKey: 'outer-radius', variable: '--corner-badge-outer-radius', element: 'frame' },
|
|
18
|
+
{ label: 'inner corner radius', canBeLinked: true, groupKey: 'inner-radius', variable: '--corner-badge-inner-radius', element: 'frame' },
|
|
19
|
+
{ label: 'horizontal-axis radius', canBeLinked: true, groupKey: 'h-axis-radius', variable: '--corner-badge-h-axis-radius', element: 'frame' },
|
|
20
|
+
{ label: 'vertical-axis radius', canBeLinked: true, groupKey: 'v-axis-radius', variable: '--corner-badge-v-axis-radius', element: 'frame' },
|
|
21
|
+
{ label: 'font family', canBeLinked: true, groupKey: 'text-font-family', variable: '--corner-badge-text-font-family', element: 'text' },
|
|
22
|
+
{ label: 'font size', canBeLinked: true, groupKey: 'text-font-size', variable: '--corner-badge-text-font-size', element: 'text' },
|
|
23
|
+
{ label: 'font weight', canBeLinked: true, groupKey: 'text-font-weight', variable: '--corner-badge-text-font-weight', element: 'text' },
|
|
24
|
+
{ label: 'line height', canBeLinked: true, groupKey: 'text-line-height', variable: '--corner-badge-text-line-height', element: 'text' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Per-variant color slots — surface, border, text — rebind the inner Badge's
|
|
28
|
+
// colors inside .corner-badge-{v} scope.
|
|
29
|
+
function variantColorTokens(v: Variant): Token[] {
|
|
9
30
|
return [
|
|
10
|
-
{ label: '
|
|
11
|
-
{ label: '
|
|
12
|
-
{ label: '
|
|
13
|
-
{ label: 'horizontal-axis radius', canBeLinked: true, groupKey: 'h-axis-radius', variable: `--corner-badge-${v}-h-axis-radius` },
|
|
14
|
-
{ label: 'vertical-axis radius', canBeLinked: true, groupKey: 'v-axis-radius', variable: `--corner-badge-${v}-v-axis-radius` },
|
|
15
|
-
{ label: 'padding', canBeLinked: true, groupKey: 'padding', variable: `--corner-badge-${v}-padding` },
|
|
16
|
-
// Hidden per-side overrides for UIPaddingSelector split mode; declared so siblings link across variants.
|
|
17
|
-
{ label: 'padding-top', canBeLinked: true, groupKey: 'padding-top', variable: `--corner-badge-${v}-padding-top`, hidden: true },
|
|
18
|
-
{ label: 'padding-right', canBeLinked: true, groupKey: 'padding-right', variable: `--corner-badge-${v}-padding-right`, hidden: true },
|
|
19
|
-
{ label: 'padding-bottom', canBeLinked: true, groupKey: 'padding-bottom', variable: `--corner-badge-${v}-padding-bottom`, hidden: true },
|
|
20
|
-
{ label: 'padding-left', canBeLinked: true, groupKey: 'padding-left', variable: `--corner-badge-${v}-padding-left`, hidden: true },
|
|
21
|
-
{ label: 'font family', canBeLinked: true, groupKey: 'text-font-family', variable: `--corner-badge-${v}-text-font-family` },
|
|
22
|
-
{ label: 'font size', canBeLinked: true, groupKey: 'text-font-size', variable: `--corner-badge-${v}-text-font-size` },
|
|
23
|
-
{ label: 'font weight', canBeLinked: true, groupKey: 'text-font-weight', variable: `--corner-badge-${v}-text-font-weight` },
|
|
24
|
-
{ label: 'line height', canBeLinked: true, groupKey: 'text-line-height', variable: `--corner-badge-${v}-text-line-height` },
|
|
31
|
+
{ label: 'surface color', groupKey: 'surface', variable: `--corner-badge-${v}-surface` },
|
|
32
|
+
{ label: 'border color', groupKey: 'border', variable: `--corner-badge-${v}-border` },
|
|
33
|
+
{ label: 'text color', groupKey: 'text', variable: `--corner-badge-${v}-text` },
|
|
25
34
|
];
|
|
26
35
|
}
|
|
27
|
-
export const allTokens: Token[] = badgeVariants.flatMap((v) => variantTokens(v));
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
),
|
|
36
|
-
|
|
37
|
+
function variantStates(v: Variant): Record<string, Token[]> {
|
|
38
|
+
return { base: baseTokens, colors: variantColorTokens(v) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const allTokens: Token[] = [
|
|
42
|
+
...baseTokens,
|
|
43
|
+
...badgeVariants.flatMap((v) => variantColorTokens(v)),
|
|
44
|
+
];
|
|
37
45
|
|
|
38
46
|
const variantOptions = badgeVariants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
|
|
39
47
|
</script>
|
|
@@ -42,15 +50,11 @@
|
|
|
42
50
|
import CornerBadge, { type CornerAnchor } from '../../system/components/CornerBadge.svelte';
|
|
43
51
|
import VariantGroup from './scaffolding/VariantGroup.svelte';
|
|
44
52
|
import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
|
|
45
|
-
import { editorState } from '../core/store/editorStore';
|
|
46
|
-
import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
|
|
47
53
|
import { buildSiblings } from './scaffolding/siblings';
|
|
48
54
|
import demoImageUrl from '../../system/assets/newspaper.webp';
|
|
49
55
|
|
|
50
|
-
let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
|
|
51
|
-
let visibleVariantTokens = $derived((v: typeof badgeVariants[number]) => withLinkedDisabled(variantTokens(v), linked.varSet));
|
|
52
|
-
|
|
53
56
|
let anchor: CornerAnchor = $state('bottom-right');
|
|
57
|
+
|
|
54
58
|
const anchorGrid: ReadonlyArray<{ value: CornerAnchor; icon: string; label: string }> = [
|
|
55
59
|
{ value: 'top-left', icon: 'fas fa-arrow-up-left', label: 'Top left' },
|
|
56
60
|
{ value: 'top-right', icon: 'fas fa-arrow-up-right', label: 'Top right' },
|
|
@@ -59,14 +63,20 @@
|
|
|
59
63
|
];
|
|
60
64
|
</script>
|
|
61
65
|
|
|
62
|
-
<ComponentEditorBase
|
|
66
|
+
<ComponentEditorBase
|
|
67
|
+
{component}
|
|
68
|
+
title="Corner Badge"
|
|
69
|
+
description="Badge pinned flush to a corner of a positioned ancestor. Composes <code>Badge</code>; carries its own per-variant color tokens that override the inner Badge inside the corner scope."
|
|
70
|
+
tokens={allTokens}
|
|
71
|
+
variants={variantOptions}
|
|
72
|
+
>
|
|
63
73
|
{#each badgeVariants as v}
|
|
64
74
|
<VariantGroup
|
|
65
75
|
name={v}
|
|
66
76
|
title={v.charAt(0).toUpperCase() + v.slice(1)}
|
|
67
|
-
states={
|
|
77
|
+
states={variantStates(v)}
|
|
68
78
|
{component}
|
|
69
|
-
siblings={buildSiblings(badgeVariants, v,
|
|
79
|
+
siblings={buildSiblings(badgeVariants, v, variantStates)}
|
|
70
80
|
>
|
|
71
81
|
{#snippet canvasToolbarExtras()}
|
|
72
82
|
<span class="canvas-toolbar-eyebrow">Anchor</span>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { Token } from './scaffolding/types';
|
|
3
|
+
|
|
4
|
+
export const component = 'imagelightbox';
|
|
5
|
+
|
|
6
|
+
// Three parts: tile (closed inline + animated modal surface), overlay (scrim),
|
|
7
|
+
// chrome (shared toolbar + close-button look). Chrome carries a hover state
|
|
8
|
+
// for the interactive controls. No second variant.
|
|
9
|
+
const states: Record<string, Token[]> = {
|
|
10
|
+
tile: [
|
|
11
|
+
{ label: 'corner radius', groupKey: 'radius', variable: '--imagelightbox-tile-radius' },
|
|
12
|
+
{ label: 'border color', groupKey: 'border', variable: '--imagelightbox-tile-border' },
|
|
13
|
+
{ label: 'border width', groupKey: 'width', variable: '--imagelightbox-tile-border-width' },
|
|
14
|
+
{ label: 'tile shadow', variable: '--imagelightbox-tile-shadow' },
|
|
15
|
+
],
|
|
16
|
+
overlay: [
|
|
17
|
+
{ label: 'backdrop color', groupKey: 'surface', variable: '--imagelightbox-overlay-surface' },
|
|
18
|
+
],
|
|
19
|
+
chrome: [
|
|
20
|
+
{ label: 'surface color', groupKey: 'surface', variable: '--imagelightbox-chrome-surface' },
|
|
21
|
+
{ label: 'border color', groupKey: 'border', variable: '--imagelightbox-chrome-border' },
|
|
22
|
+
{ label: 'border width', groupKey: 'width', variable: '--imagelightbox-chrome-border-width' },
|
|
23
|
+
{ label: 'corner radius', groupKey: 'radius', variable: '--imagelightbox-chrome-radius' },
|
|
24
|
+
{ label: 'icon color', groupKey: 'icon', variable: '--imagelightbox-chrome-icon' },
|
|
25
|
+
{ label: 'hover surface color', groupKey: 'surface', variable: '--imagelightbox-chrome-hover-surface' },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const allTokens: Token[] = Object.values(states).flat();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script lang="ts">
|
|
33
|
+
import ImageLightbox from '../../system/components/ImageLightbox.svelte';
|
|
34
|
+
import VariantGroup from './scaffolding/VariantGroup.svelte';
|
|
35
|
+
import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
|
|
36
|
+
import demoImageUrl from '../../system/assets/offering.webp';
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<ComponentEditorBase
|
|
40
|
+
{component}
|
|
41
|
+
title="Image Lightbox"
|
|
42
|
+
description="Click an inline image to expand it into a centered modal with a backdrop. Extended mode adds zoom controls and drag panning."
|
|
43
|
+
tokens={allTokens}
|
|
44
|
+
>
|
|
45
|
+
<VariantGroup name="imagelightbox" title="Image Lightbox" {states} {component}>
|
|
46
|
+
<div class="preview-frame">
|
|
47
|
+
<ImageLightbox src={demoImageUrl} alt="Demo image" width={1024} height={640} extended />
|
|
48
|
+
</div>
|
|
49
|
+
</VariantGroup>
|
|
50
|
+
</ComponentEditorBase>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.preview-frame {
|
|
54
|
+
width: 100%;
|
|
55
|
+
max-width: 28rem;
|
|
56
|
+
margin: 0 auto;
|
|
57
|
+
}
|
|
58
|
+
</style>
|