@motion-proto/live-tokens 0.1.1 → 0.3.2
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 +168 -21
- package/dist-plugin/index.cjs +823 -336
- package/dist-plugin/index.d.cts +9 -7
- package/dist-plugin/index.d.ts +9 -7
- package/dist-plugin/index.js +822 -335
- package/package.json +46 -20
- package/src/assets/newspaper.webp +0 -0
- package/src/assets/offering.webp +0 -0
- package/src/component-editor/BadgeEditor.svelte +170 -0
- package/src/component-editor/CalloutEditor.svelte +103 -0
- package/src/component-editor/CardEditor.svelte +184 -0
- package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
- package/src/component-editor/CornerBadgeEditor.svelte +207 -0
- package/src/component-editor/DialogEditor.svelte +172 -0
- package/src/component-editor/ImageEditor.svelte +72 -0
- package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
- package/src/component-editor/NotificationEditor.svelte +160 -0
- package/src/component-editor/ProgressBarEditor.svelte +124 -0
- package/src/component-editor/RadioButtonEditor.svelte +140 -0
- package/src/component-editor/SectionDividerEditor.svelte +263 -0
- package/src/component-editor/SegmentedControlEditor.svelte +154 -0
- package/src/component-editor/StandardButtonsEditor.svelte +178 -0
- package/src/component-editor/TabBarEditor.svelte +137 -0
- package/src/component-editor/TableEditor.svelte +128 -0
- package/src/component-editor/TooltipEditor.svelte +122 -0
- package/src/component-editor/editorTokens.test.ts +93 -0
- package/src/component-editor/groupKeySlots.test.ts +67 -0
- package/src/component-editor/groupKeySnapshot.test.ts +52 -0
- package/src/component-editor/index.ts +5 -0
- package/src/component-editor/registry.ts +246 -0
- package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
- package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
- package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
- package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
- package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
- package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
- package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
- package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
- package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
- package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
- package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
- package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
- package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
- package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
- package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
- package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
- package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
- package/src/component-editor/scaffolding/componentSources.ts +9 -0
- package/src/component-editor/scaffolding/defaultSections.ts +16 -0
- package/src/component-editor/scaffolding/editorContext.ts +44 -0
- package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
- package/src/component-editor/scaffolding/siblings.ts +33 -0
- package/src/component-editor/scaffolding/types.ts +39 -0
- package/src/components/Badge.svelte +231 -42
- package/src/components/Button.svelte +324 -124
- package/src/components/Callout.svelte +145 -0
- package/src/components/Card.svelte +123 -25
- package/src/components/CollapsibleSection.svelte +213 -35
- package/src/components/CornerBadge.svelte +224 -0
- package/src/components/Dialog.svelte +137 -114
- package/src/components/Image.svelte +43 -0
- package/src/components/InlineEditActions.svelte +74 -14
- package/src/components/Notification.svelte +184 -163
- package/src/components/ProgressBar.svelte +216 -22
- package/src/components/RadioButton.svelte +110 -40
- package/src/components/SectionDivider.svelte +428 -74
- package/src/components/SegmentedControl.svelte +203 -0
- package/src/components/TabBar.svelte +146 -21
- package/src/components/Table.svelte +102 -0
- package/src/components/Tooltip.svelte +45 -19
- package/src/components/types.ts +51 -0
- package/src/data/google-fonts.json +75 -0
- package/src/lib/ColumnsOverlay.svelte +20 -7
- package/src/lib/LiveEditorOverlay.svelte +257 -78
- package/src/lib/columnsOverlay.ts +21 -17
- package/src/lib/componentConfig.test.ts +204 -0
- package/src/lib/componentConfigKeys.ts +19 -0
- package/src/lib/componentConfigService.ts +88 -0
- package/src/lib/copyPopover.ts +30 -0
- package/src/lib/cssVarSync.ts +59 -7
- package/src/lib/editorConfigStore.ts +0 -10
- package/src/lib/editorCore.ts +402 -0
- package/src/lib/editorKeybindings.ts +52 -0
- package/src/lib/editorPersistence.ts +106 -0
- package/src/lib/editorRenderer.ts +74 -0
- package/src/lib/editorStore.test.ts +328 -0
- package/src/lib/editorStore.ts +412 -0
- package/src/lib/editorTypes.ts +100 -0
- package/src/lib/editorViewStore.ts +55 -0
- package/src/lib/files/versionedFileResource.ts +140 -0
- package/src/lib/fontLoader.ts +130 -0
- package/src/lib/fontMigration.ts +140 -0
- package/src/lib/fontParse.ts +168 -0
- package/src/lib/index.ts +48 -30
- package/src/lib/lazyConfig.test.ts +54 -0
- package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
- package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
- package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
- package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
- package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
- package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
- package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
- package/src/lib/migrations/index.ts +93 -0
- package/src/lib/migrations/migrations.test.ts +341 -0
- package/src/lib/navLinkTypes.ts +1 -0
- package/src/lib/overlayState.ts +3 -0
- package/src/lib/paletteDerivation.ts +300 -0
- package/src/lib/parentRouteStore.ts +42 -0
- package/src/lib/parsers/globalRootBlock.ts +32 -0
- package/src/lib/presetService.ts +94 -0
- package/src/lib/router.ts +42 -10
- package/src/lib/scrollSection.ts +45 -0
- package/src/lib/slices/columns.ts +59 -0
- package/src/lib/slices/components.ts +362 -0
- package/src/lib/slices/domainVars.ts +15 -0
- package/src/lib/slices/fonts.ts +30 -0
- package/src/lib/slices/gradients.ts +153 -0
- package/src/lib/slices/overlays.ts +132 -0
- package/src/lib/slices/palettes.ts +26 -0
- package/src/lib/slices/shadows.ts +123 -0
- package/src/lib/storage.ts +88 -0
- package/src/lib/themeInit.ts +74 -0
- package/src/lib/themeService.ts +101 -0
- package/src/lib/themeTypes.ts +146 -0
- package/src/lib/tokenRegistry.ts +148 -0
- package/src/pages/ComponentEditorPage.svelte +384 -0
- package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
- package/src/pages/Editor.svelte +98 -0
- package/src/pages/Editor.svelte.d.ts +2 -0
- package/src/pages/EditorShell.svelte +348 -0
- package/src/styles/_padding.scss +34 -0
- package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
- package/src/styles/fonts.css +22 -10
- package/src/styles/form-controls.css +14 -16
- package/src/styles/tokens.css +1322 -0
- package/src/styles/ui-editor.css +126 -0
- package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
- package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
- package/src/ui/EditorViewSwitcher.svelte +180 -0
- package/src/ui/FontStackEditor.svelte +360 -0
- package/src/ui/GradientEditor.svelte +461 -0
- package/src/ui/GradientStopPicker.svelte +74 -0
- package/src/ui/PaletteEditor.svelte +1590 -0
- package/src/ui/PaletteEditor.test.ts +108 -0
- package/src/ui/PresetFileManager.svelte +567 -0
- package/src/ui/ProjectFontsSection.svelte +645 -0
- package/src/{showcase → ui}/SurfacesTab.svelte +39 -39
- package/src/{showcase → ui}/TextTab.svelte +27 -27
- package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
- package/src/ui/Toggle.svelte +108 -0
- package/src/ui/UICopyPopover.svelte +78 -0
- package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
- package/src/ui/UIFontFamilySelector.svelte +309 -0
- package/src/ui/UIFontSizeSelector.svelte +165 -0
- package/src/ui/UIFontWeightSelector.svelte +52 -0
- package/src/ui/UILineHeightSelector.svelte +47 -0
- package/src/ui/UILinkToggle.svelte +60 -0
- package/src/ui/UIOptionItem.svelte +74 -0
- package/src/ui/UIOptionList.svelte +27 -0
- package/src/ui/UIPaddingSelector.svelte +661 -0
- package/src/ui/UIPaletteSelector.svelte +1084 -0
- package/src/ui/UIRadio.svelte +72 -0
- package/src/ui/UIRadioGroup.svelte +59 -0
- package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
- package/src/ui/UITokenSelector.svelte +509 -0
- package/src/ui/UIVariantSelector.svelte +145 -0
- package/src/ui/VariablesTab.svelte +252 -0
- package/src/ui/index.ts +31 -0
- package/src/ui/keepInViewport.ts +84 -0
- package/src/ui/palette/GradientStopEditor.svelte +482 -0
- package/src/ui/palette/OverridesPanel.svelte +526 -0
- package/src/ui/palette/PaletteBase.svelte +165 -0
- package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
- package/src/ui/palette/paletteEditorState.ts +89 -0
- package/src/ui/sections/ColumnsSection.svelte +273 -0
- package/src/ui/sections/GradientsSection.svelte +147 -0
- package/src/ui/sections/OverlaysSection.svelte +670 -0
- package/src/ui/sections/ShadowsSection.svelte +1250 -0
- package/src/ui/sections/TokenScaleTable.svelte +332 -0
- package/src/ui/sections/tokenScales.ts +81 -0
- package/src/ui/variantScales.ts +108 -0
- package/src/components/DetailNav.svelte +0 -78
- package/src/components/Toggle.svelte +0 -86
- package/src/lib/tokenInit.ts +0 -29
- package/src/lib/tokenService.ts +0 -144
- package/src/lib/tokenTypes.ts +0 -45
- package/src/pages/Admin.svelte +0 -100
- package/src/pages/ShowcasePage.svelte +0 -144
- package/src/showcase/BackupBrowser.svelte +0 -617
- package/src/showcase/ComponentsTab.svelte +0 -105
- package/src/showcase/PaletteEditor.svelte +0 -2579
- package/src/showcase/PaletteSelector.svelte +0 -627
- package/src/showcase/TokenMap.svelte +0 -54
- package/src/showcase/VariablesTab.svelte +0 -2655
- package/src/showcase/VisualsTab.svelte +0 -231
- package/src/showcase/demos/BadgeDemo.svelte +0 -56
- package/src/showcase/demos/CardDemo.svelte +0 -50
- package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
- package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
- package/src/showcase/demos/DialogDemo.svelte +0 -42
- package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
- package/src/showcase/demos/NotificationDemo.svelte +0 -147
- package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
- package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
- package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
- package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
- package/src/showcase/demos/TabBarDemo.svelte +0 -58
- package/src/showcase/demos/TooltipDemo.svelte +0 -52
- package/src/showcase/editor.css +0 -93
- package/src/showcase/index.ts +0 -17
- package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
- package/src/styles/fonts/Domine/OFL.txt +0 -97
- package/src/styles/fonts/Domine/README.txt +0 -66
- /package/src/{showcase → ui}/curveEngine.ts +0 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade, slide } from 'svelte/transition';
|
|
3
|
+
import TokenLayout from './TokenLayout.svelte';
|
|
4
|
+
import LinkageChart from './LinkageChart.svelte';
|
|
5
|
+
import { getEditorContext } from './editorContext';
|
|
6
|
+
import type { LinkedBlockResult, LinkedGroup } from './linkedBlock';
|
|
7
|
+
|
|
8
|
+
/** Honor prefers-reduced-motion: matches the rest of the editor's motion
|
|
9
|
+
vocabulary (UITokenSelector pop-bar gates its transitions the same way).
|
|
10
|
+
Read once at module mount; user can toggle in OS preferences and reload. */
|
|
11
|
+
const reduceMotion = typeof window !== 'undefined'
|
|
12
|
+
&& window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;
|
|
13
|
+
const fadeDur = reduceMotion ? 0 : 140;
|
|
14
|
+
const slideDur = reduceMotion ? 0 : 200;
|
|
15
|
+
|
|
16
|
+
export let component: string;
|
|
17
|
+
export let linked: LinkedBlockResult;
|
|
18
|
+
|
|
19
|
+
const editorCtx = getEditorContext();
|
|
20
|
+
const focusedVariant = editorCtx?.focusedVariant;
|
|
21
|
+
const focusedState = editorCtx?.focusedState;
|
|
22
|
+
const hoveredLinkedVariable = editorCtx?.hoveredLinkedVariable;
|
|
23
|
+
|
|
24
|
+
/** Forward a chart row click to whichever tab strip the label belongs to. The chart
|
|
25
|
+
doesn't know if its rows are variants (top-level tab strip) or states (per-VariantGroup
|
|
26
|
+
state tabs), so we set both stores; each consumer adopts the value only if it names
|
|
27
|
+
one of its own tabs. */
|
|
28
|
+
function handleChartSelect(e: CustomEvent<string>) {
|
|
29
|
+
editorCtx?.focusedVariant.set(e.detail);
|
|
30
|
+
editorCtx?.focusedState.set(e.detail);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Pick the sibling that backs the cell the user is currently focused on, so the row
|
|
34
|
+
reflects that specific variant×state. Tries the most specific match first
|
|
35
|
+
(`"variant state"`), then either dimension alone, finally the group's default rep. */
|
|
36
|
+
function pickFocusedVariable(g: LinkedGroup, variant: string | null, state: string | null): string {
|
|
37
|
+
const map = g.contextToVariable;
|
|
38
|
+
if (variant && state) {
|
|
39
|
+
const both = map.get(`${variant} ${state}`);
|
|
40
|
+
if (both) return both;
|
|
41
|
+
}
|
|
42
|
+
if (variant) {
|
|
43
|
+
const v = map.get(variant);
|
|
44
|
+
if (v) return v;
|
|
45
|
+
}
|
|
46
|
+
if (state) {
|
|
47
|
+
const s = map.get(state);
|
|
48
|
+
if (s) return s;
|
|
49
|
+
}
|
|
50
|
+
return g.token.variable;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Caption for a card's chart describing the linkage scope. 2-token context
|
|
54
|
+
labels (e.g. "primary default") imply variants × states; single-token
|
|
55
|
+
labels imply one axis. Tuned to typography linking across variants only. */
|
|
56
|
+
function captionFor(contexts: string[]): string {
|
|
57
|
+
if (contexts.length === 0) return '';
|
|
58
|
+
const has2D = contexts.some((c) => /\s/.test(c));
|
|
59
|
+
if (has2D) return 'Links across variants and states';
|
|
60
|
+
return 'Links across variants';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Format the bracket text on the drill-down row. ≤2 names → comma-list;
|
|
64
|
+
>2 names → numeric ("3 unlinked"). The names ARE the divergence info,
|
|
65
|
+
so this is the primary signal when broken; the chart is drill-down detail. */
|
|
66
|
+
function formatUnlinked(brokenContexts: string[]): string {
|
|
67
|
+
if (brokenContexts.length === 0) return '';
|
|
68
|
+
if (brokenContexts.length <= 2) {
|
|
69
|
+
return `${brokenContexts.join(', ')} unlinked`;
|
|
70
|
+
}
|
|
71
|
+
return `${brokenContexts.length} unlinked`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$: focusedV = (focusedVariant ? $focusedVariant : null) ?? null;
|
|
75
|
+
$: focusedS = (focusedState ? $focusedState : null) ?? null;
|
|
76
|
+
$: hoveredVar = (hoveredLinkedVariable ? $hoveredLinkedVariable : null) ?? null;
|
|
77
|
+
|
|
78
|
+
function setHover(variable: string | null) {
|
|
79
|
+
hoveredLinkedVariable?.set(variable);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One card per LinkedGroup. Stable order = editor-declared order. */
|
|
83
|
+
$: cards = linked.groups.map((g) => ({
|
|
84
|
+
contexts: g.contexts,
|
|
85
|
+
brokenContexts: g.brokenContexts,
|
|
86
|
+
row: { ...g.token, variable: pickFocusedVariable(g, focusedV, focusedS) },
|
|
87
|
+
caption: captionFor(g.contexts),
|
|
88
|
+
unlinkedText: formatUnlinked(g.brokenContexts),
|
|
89
|
+
isBroken: g.brokenContexts.length > 0,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
/** Section-level summary: count of properties with any broken peers. The
|
|
93
|
+
header tells the user *whether to look*; the per-card text tells them
|
|
94
|
+
*what's broken*. Two layers, two granularities. */
|
|
95
|
+
$: brokenPropertyCount = cards.filter((c) => c.isBroken).length;
|
|
96
|
+
$: hasAnyBroken = brokenPropertyCount > 0;
|
|
97
|
+
|
|
98
|
+
/** Default closed; the section header's summary count + "in sync / N unlinked"
|
|
99
|
+
text is the at-a-glance signal, so users opt into the matrix only when
|
|
100
|
+
they need it. */
|
|
101
|
+
let sectionToggleOverride: boolean | null = null;
|
|
102
|
+
$: sectionExpanded = sectionToggleOverride ?? false;
|
|
103
|
+
function toggleSection() {
|
|
104
|
+
sectionToggleOverride = !sectionExpanded;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Per-card chart expand state, keyed by the card's row variable (which is
|
|
108
|
+
stable per LinkedGroup since pickFocusedVariable runs against the same
|
|
109
|
+
group on every render). */
|
|
110
|
+
let expandedCards: Record<string, boolean> = {};
|
|
111
|
+
function toggleCard(variable: string) {
|
|
112
|
+
expandedCards = { ...expandedCards, [variable]: !expandedCards[variable] };
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
{#if cards.length > 0}
|
|
117
|
+
<section class="linked-block">
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
class="section-header"
|
|
121
|
+
class:expanded={sectionExpanded}
|
|
122
|
+
aria-expanded={sectionExpanded}
|
|
123
|
+
on:click={toggleSection}
|
|
124
|
+
>
|
|
125
|
+
<i class="fas fa-chevron-right chevron"></i>
|
|
126
|
+
<span class="section-title">Linked properties</span>
|
|
127
|
+
<span class="section-summary">
|
|
128
|
+
<span class="section-summary-sep">·</span>
|
|
129
|
+
<span class="section-summary-count">{cards.length}</span>
|
|
130
|
+
<span class="section-summary-sep">·</span>
|
|
131
|
+
{#if hasAnyBroken}
|
|
132
|
+
<span class="section-summary-broken">{brokenPropertyCount} unlinked</span>
|
|
133
|
+
{:else}
|
|
134
|
+
<span class="section-summary-ok">in sync</span>
|
|
135
|
+
{/if}
|
|
136
|
+
</span>
|
|
137
|
+
</button>
|
|
138
|
+
{#if sectionExpanded}
|
|
139
|
+
<div class="linked-grid" transition:slide|local={{ duration: slideDur }}>
|
|
140
|
+
{#each cards as card (card.row.variable)}
|
|
141
|
+
{@const cardExpanded = expandedCards[card.row.variable] === true}
|
|
142
|
+
{@const cardHovered = hoveredVar === card.row.variable}
|
|
143
|
+
<article
|
|
144
|
+
class="linked-card"
|
|
145
|
+
class:broken={card.isBroken}
|
|
146
|
+
class:hovered={cardHovered}
|
|
147
|
+
on:mouseenter={() => setHover(card.row.variable)}
|
|
148
|
+
on:mouseleave={() => setHover(null)}
|
|
149
|
+
>
|
|
150
|
+
<h4 class="property-name">{card.row.label}</h4>
|
|
151
|
+
<div class="control-row">
|
|
152
|
+
<TokenLayout
|
|
153
|
+
tokens={[card.row]}
|
|
154
|
+
{component}
|
|
155
|
+
linkedOrder={linked.linkedOrder}
|
|
156
|
+
isLinkedBlock
|
|
157
|
+
on:change
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
class="drill-down"
|
|
163
|
+
class:expanded={cardExpanded}
|
|
164
|
+
aria-expanded={cardExpanded}
|
|
165
|
+
on:click={() => toggleCard(card.row.variable)}
|
|
166
|
+
>
|
|
167
|
+
<i class="fas fa-chevron-right chevron"></i>
|
|
168
|
+
<span class="drill-label">Links</span>
|
|
169
|
+
{#if !cardExpanded && card.isBroken}
|
|
170
|
+
<span
|
|
171
|
+
class="unlinked-bracket"
|
|
172
|
+
transition:fade|local={{ duration: fadeDur }}
|
|
173
|
+
>({card.unlinkedText})</span>
|
|
174
|
+
{/if}
|
|
175
|
+
</button>
|
|
176
|
+
{#if cardExpanded}
|
|
177
|
+
<div class="chart-wrap" transition:slide|local={{ duration: slideDur }}>
|
|
178
|
+
<LinkageChart
|
|
179
|
+
contexts={card.contexts}
|
|
180
|
+
broken={card.brokenContexts}
|
|
181
|
+
caption={card.caption}
|
|
182
|
+
selectedRow={focusedV}
|
|
183
|
+
selectedCol={focusedS}
|
|
184
|
+
on:select={handleChartSelect}
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
{/if}
|
|
188
|
+
</article>
|
|
189
|
+
{/each}
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
</section>
|
|
193
|
+
{/if}
|
|
194
|
+
|
|
195
|
+
<style>
|
|
196
|
+
/* Panel chrome matches NonStylableConfig (the "Configuration" panel) so the
|
|
197
|
+
three sibling sections — config, variants, linked — read as a coherent
|
|
198
|
+
family. Background + full border + radius give the section the same
|
|
199
|
+
presence as its siblings even when collapsed to just the header row. */
|
|
200
|
+
.linked-block {
|
|
201
|
+
margin-top: var(--ui-space-12);
|
|
202
|
+
padding: var(--ui-space-12);
|
|
203
|
+
border: 1px solid var(--ui-border-faint);
|
|
204
|
+
border-radius: var(--ui-radius-md);
|
|
205
|
+
background: var(--ui-surface-low);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Section header — clickable strip that doubles as the collapsed-state
|
|
209
|
+
summary. Reads "Linked properties · 7 · in sync" or "· 2 unlinked"; the
|
|
210
|
+
count + status are the at-a-glance signal. The header sits at the top of
|
|
211
|
+
the panel; when the body opens, it slides in below the header (the panel
|
|
212
|
+
grows downward, the header stays put). */
|
|
213
|
+
.section-header {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: var(--ui-space-8);
|
|
217
|
+
width: 100%;
|
|
218
|
+
padding: 0;
|
|
219
|
+
background: none;
|
|
220
|
+
border: 0;
|
|
221
|
+
color: inherit;
|
|
222
|
+
font: inherit;
|
|
223
|
+
text-align: left;
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
}
|
|
226
|
+
.section-header .chevron {
|
|
227
|
+
font-size: 0.625rem;
|
|
228
|
+
color: var(--ui-text-tertiary);
|
|
229
|
+
transition: transform var(--ui-transition-fast);
|
|
230
|
+
width: 0.75rem;
|
|
231
|
+
}
|
|
232
|
+
.section-header.expanded .chevron {
|
|
233
|
+
transform: rotate(90deg);
|
|
234
|
+
}
|
|
235
|
+
.section-title {
|
|
236
|
+
font-size: var(--ui-font-size-md);
|
|
237
|
+
font-weight: 500;
|
|
238
|
+
color: var(--ui-text-primary);
|
|
239
|
+
}
|
|
240
|
+
.section-summary {
|
|
241
|
+
display: inline-flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: var(--ui-space-6);
|
|
244
|
+
font-size: var(--ui-font-size-sm);
|
|
245
|
+
color: var(--ui-text-tertiary);
|
|
246
|
+
}
|
|
247
|
+
.section-summary-sep {
|
|
248
|
+
color: var(--ui-border-default);
|
|
249
|
+
}
|
|
250
|
+
.section-summary-count {
|
|
251
|
+
font-family: var(--ui-font-mono);
|
|
252
|
+
font-variant-numeric: tabular-nums;
|
|
253
|
+
color: var(--ui-text-secondary);
|
|
254
|
+
}
|
|
255
|
+
.section-summary-ok {
|
|
256
|
+
color: var(--ui-text-tertiary);
|
|
257
|
+
}
|
|
258
|
+
.section-summary-broken {
|
|
259
|
+
color: var(--ui-link-broken);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.linked-grid {
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: row;
|
|
265
|
+
flex-wrap: wrap;
|
|
266
|
+
align-items: flex-start;
|
|
267
|
+
gap: var(--ui-space-12);
|
|
268
|
+
margin-top: var(--ui-space-16);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Card — vertical stack: label heading · control row · drill-down · chart.
|
|
272
|
+
Chrome is the property's visual envelope. The card grows vertically when
|
|
273
|
+
drilled down; adjacent cards keep their position. */
|
|
274
|
+
.linked-card {
|
|
275
|
+
flex: 0 0 auto;
|
|
276
|
+
display: flex;
|
|
277
|
+
flex-direction: column;
|
|
278
|
+
gap: var(--ui-space-8);
|
|
279
|
+
border: 1px solid var(--ui-border-faint);
|
|
280
|
+
border-radius: var(--ui-radius-lg);
|
|
281
|
+
padding: var(--ui-space-12) var(--ui-space-16);
|
|
282
|
+
min-width: 14rem;
|
|
283
|
+
}
|
|
284
|
+
/* Broken cards adopt the deep amber link-broken color directly — same hue
|
|
285
|
+
as the section-summary text and the inline bracket, so the cue carries
|
|
286
|
+
across all three layers (header → card border → bracket text). */
|
|
287
|
+
.linked-card.broken {
|
|
288
|
+
border-color: var(--ui-link-broken);
|
|
289
|
+
}
|
|
290
|
+
/* Bidirectional hover cue: when the user hovers this card, or the matching
|
|
291
|
+
row in the per-state Properties grid, both surfaces light up so the
|
|
292
|
+
linkage is legible at a glance. Background lift + border bump; broken
|
|
293
|
+
cards keep their amber border so the link-state cue isn't overwritten. */
|
|
294
|
+
.linked-card {
|
|
295
|
+
transition: background var(--ui-transition-fast), border-color var(--ui-transition-fast);
|
|
296
|
+
}
|
|
297
|
+
.linked-card.hovered {
|
|
298
|
+
background: var(--ui-hover-lowest);
|
|
299
|
+
border-color: var(--ui-border-default);
|
|
300
|
+
}
|
|
301
|
+
.linked-card.hovered.broken {
|
|
302
|
+
border-color: var(--ui-link-broken);
|
|
303
|
+
}
|
|
304
|
+
/* Mirror the trigger-bg suppression from TokenLayout: while the card is
|
|
305
|
+
hovered, the inner trigger drops its own surface fill so the card's
|
|
306
|
+
hover band reads as one strip. Direct trigger hover keeps its style. */
|
|
307
|
+
.linked-card.hovered :global(.ui-ts-trigger:not(:hover)) {
|
|
308
|
+
background: transparent;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.property-name {
|
|
312
|
+
margin: 0;
|
|
313
|
+
font-size: var(--ui-font-size-md);
|
|
314
|
+
font-weight: var(--ui-font-weight-medium);
|
|
315
|
+
color: var(--ui-text-primary);
|
|
316
|
+
letter-spacing: 0.005em;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Control row hosts a single TokenLayout-rendered selector + resolved value.
|
|
320
|
+
TokenLayout's inline label is suppressed here (we render our own heading
|
|
321
|
+
above), and the grid is reduced to selector + value columns. */
|
|
322
|
+
.control-row {
|
|
323
|
+
display: flex;
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
}
|
|
326
|
+
.control-row :global(.token-row .token-label) {
|
|
327
|
+
display: none;
|
|
328
|
+
}
|
|
329
|
+
.control-row :global(.token-group .token-grid) {
|
|
330
|
+
/* Inherit --token-selector-w from TokenLayout's default (8rem) so the
|
|
331
|
+
trigger renders at the same fixed width here as in per-variant view.
|
|
332
|
+
The earlier 9rem override existed to give the chain badge breathing
|
|
333
|
+
room inside the trigger; the badge is gone now, so the wider column
|
|
334
|
+
just leaves dead space and visually misaligns the two contexts. */
|
|
335
|
+
grid-template-columns: var(--token-selector-w) max-content;
|
|
336
|
+
column-gap: var(--ui-space-8);
|
|
337
|
+
padding: 0;
|
|
338
|
+
/* Linked-block grid skips the label column (we render the property name
|
|
339
|
+
above as a heading), so padding-single-row should start at col 1 here. */
|
|
340
|
+
--padding-row-start: 1;
|
|
341
|
+
}
|
|
342
|
+
.control-row :global(.token-group .token-grid .ui-token-selector) {
|
|
343
|
+
grid-column: span 2;
|
|
344
|
+
}
|
|
345
|
+
/* Linked-block parent grid only has 2 cols (selector + value), so
|
|
346
|
+
UIPaddingSelector's .padding-sides-block can't subgrid into the
|
|
347
|
+
[side-label][trigger][value] layout it uses in per-variant view —
|
|
348
|
+
auto-placement would push each <UITokenSelector> onto its own row.
|
|
349
|
+
Override to a self-contained 3-col grid so each side row keeps the
|
|
350
|
+
[name][dropdown][value] alignment side by side. The `span 2` from the
|
|
351
|
+
rule above still lands the dropdown in cols 2-3 of this local grid,
|
|
352
|
+
after the side label takes col 1. */
|
|
353
|
+
.control-row :global(.padding-sides-block) {
|
|
354
|
+
grid-template-columns: max-content var(--token-selector-w) max-content;
|
|
355
|
+
column-gap: var(--ui-space-8);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Drill-down row — chevron at the front, "Links" label, optional amber
|
|
359
|
+
bracket of unlinked context names. Whole row is the click target. The
|
|
360
|
+
bracket is hidden when expanded (the chart becomes the source of truth)
|
|
361
|
+
and when nothing is broken (chart still drillable for inspection). */
|
|
362
|
+
.drill-down {
|
|
363
|
+
display: inline-flex;
|
|
364
|
+
align-items: center;
|
|
365
|
+
gap: var(--ui-space-6);
|
|
366
|
+
padding: var(--ui-space-4) 0;
|
|
367
|
+
background: none;
|
|
368
|
+
border: 0;
|
|
369
|
+
color: var(--ui-text-tertiary);
|
|
370
|
+
font: inherit;
|
|
371
|
+
font-size: var(--ui-font-size-sm);
|
|
372
|
+
text-align: left;
|
|
373
|
+
cursor: pointer;
|
|
374
|
+
transition: color var(--ui-transition-fast);
|
|
375
|
+
}
|
|
376
|
+
.drill-down:hover {
|
|
377
|
+
color: var(--ui-text-secondary);
|
|
378
|
+
}
|
|
379
|
+
.drill-down .chevron {
|
|
380
|
+
font-size: 0.625rem;
|
|
381
|
+
color: var(--ui-text-tertiary);
|
|
382
|
+
transition: transform var(--ui-transition-fast);
|
|
383
|
+
width: 0.75rem;
|
|
384
|
+
}
|
|
385
|
+
.drill-down.expanded .chevron {
|
|
386
|
+
transform: rotate(90deg);
|
|
387
|
+
}
|
|
388
|
+
.drill-down .drill-label {
|
|
389
|
+
color: var(--ui-text-secondary);
|
|
390
|
+
}
|
|
391
|
+
.unlinked-bracket {
|
|
392
|
+
color: var(--ui-link-broken);
|
|
393
|
+
font-family: var(--ui-font-mono);
|
|
394
|
+
font-size: var(--ui-font-size-xs);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Chart sits in its own wrapper so the column gap above the chart is the
|
|
398
|
+
drill-down row's natural spacing, not the card's main gap. */
|
|
399
|
+
.chart-wrap {
|
|
400
|
+
display: flex;
|
|
401
|
+
flex-direction: column;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.linked-card :global(.chart .chart-grid-wrap .grid > *) {
|
|
405
|
+
padding: var(--ui-space-4) var(--ui-space-8);
|
|
406
|
+
}
|
|
407
|
+
.linked-card :global(.chart .chart-grid-wrap .grid-1d) {
|
|
408
|
+
grid-template-columns: auto 28px;
|
|
409
|
+
}
|
|
410
|
+
.linked-card :global(.chart) {
|
|
411
|
+
gap: var(--ui-space-6);
|
|
412
|
+
}
|
|
413
|
+
.linked-card :global(.chart .chart-label) {
|
|
414
|
+
font-size: var(--ui-font-size-sm);
|
|
415
|
+
font-weight: 400;
|
|
416
|
+
color: var(--ui-text-tertiary);
|
|
417
|
+
}
|
|
418
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** Section that gathers render-time toggles a component accepts as props
|
|
3
|
+
(e.g. dismissible, action buttons, hover shimmer, show icons). */
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<section class="config-block">
|
|
7
|
+
<header class="config-header">
|
|
8
|
+
<h3 class="config-title">Configuration</h3>
|
|
9
|
+
<p class="config-description">Component accepts multiple properties that can change its layout. Use these to preview.</p>
|
|
10
|
+
</header>
|
|
11
|
+
<div class="config-controls">
|
|
12
|
+
<slot />
|
|
13
|
+
</div>
|
|
14
|
+
</section>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
.config-block {
|
|
18
|
+
margin-top: var(--ui-space-12);
|
|
19
|
+
padding: var(--ui-space-12);
|
|
20
|
+
border: 1px solid var(--ui-border-faint);
|
|
21
|
+
border-radius: var(--ui-radius-md);
|
|
22
|
+
background: var(--ui-surface-low);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.config-header {
|
|
26
|
+
margin-bottom: var(--ui-space-12);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.config-title {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-size: var(--ui-font-size-md);
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
color: var(--ui-text-primary);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.config-description {
|
|
37
|
+
margin: var(--ui-space-2) 0 0;
|
|
38
|
+
font-size: var(--ui-font-size-sm);
|
|
39
|
+
color: var(--ui-text-secondary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.config-controls {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
gap: var(--ui-space-16);
|
|
46
|
+
align-items: center;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.config-controls :global(label) {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--ui-space-6);
|
|
53
|
+
font-size: var(--ui-font-size-sm);
|
|
54
|
+
color: var(--ui-text-secondary);
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher } from 'svelte';
|
|
3
|
+
import type { ComponentConfigMeta } from '../../lib/themeTypes';
|
|
4
|
+
import { sanitizeFileName } from '../../lib/themeService';
|
|
5
|
+
import UIDialog from '../../ui/UIDialog.svelte';
|
|
6
|
+
|
|
7
|
+
/** Two-way bound: parent toggles to open/close. */
|
|
8
|
+
export let show: boolean = false;
|
|
9
|
+
/** Display name to seed the input with when the dialog opens. */
|
|
10
|
+
export let currentDisplayName: string = '';
|
|
11
|
+
/** Existing files used by the increment helper to find the next available `_NN` suffix. */
|
|
12
|
+
export let files: ComponentConfigMeta[] = [];
|
|
13
|
+
|
|
14
|
+
const dispatch = createEventDispatcher<{
|
|
15
|
+
save: { displayName: string; fileName: string };
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
let saveAsName = '';
|
|
19
|
+
let saveAsInput: HTMLInputElement;
|
|
20
|
+
|
|
21
|
+
// Seed and select the input whenever the dialog opens. setTimeout(..., 0)
|
|
22
|
+
// matches the original parent's behaviour: it runs as a macrotask, after
|
|
23
|
+
// UIDialog's microtask-queued focus on the confirm button — so the input
|
|
24
|
+
// ends up focused-and-selected, not the button.
|
|
25
|
+
$: if (show) {
|
|
26
|
+
saveAsName =
|
|
27
|
+
sanitizeFileName(currentDisplayName) === 'default'
|
|
28
|
+
? nextIncrementName(currentDisplayName).displayName
|
|
29
|
+
: currentDisplayName;
|
|
30
|
+
setTimeout(() => saveAsInput?.select(), 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$: saveAsError = (() => {
|
|
34
|
+
const trimmed = saveAsName.trim();
|
|
35
|
+
if (!trimmed) return '';
|
|
36
|
+
if (sanitizeFileName(trimmed) === 'default') {
|
|
37
|
+
return 'The name "default" is reserved for the core component definition.';
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
})();
|
|
41
|
+
|
|
42
|
+
function nextIncrementName(baseDisplay: string): { displayName: string; fileName: string } {
|
|
43
|
+
const baseName = baseDisplay.replace(/_\d+$/, '');
|
|
44
|
+
const baseFileName = sanitizeFileName(baseName);
|
|
45
|
+
const existingNums = files
|
|
46
|
+
.filter(
|
|
47
|
+
(f) =>
|
|
48
|
+
f.fileName === baseFileName ||
|
|
49
|
+
f.fileName.match(new RegExp(`^${baseFileName}_\\d+$`)),
|
|
50
|
+
)
|
|
51
|
+
.map((f) => {
|
|
52
|
+
const m = f.fileName.match(/_(\d+)$/);
|
|
53
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
54
|
+
});
|
|
55
|
+
const next = (existingNums.length > 0 ? Math.max(...existingNums) : 0) + 1;
|
|
56
|
+
const suffix = String(next).padStart(2, '0');
|
|
57
|
+
return { displayName: `${baseName}_${suffix}`, fileName: `${baseFileName}_${suffix}` };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function incrementSaveAsName() {
|
|
61
|
+
saveAsName = nextIncrementName(saveAsName).displayName;
|
|
62
|
+
setTimeout(() => saveAsInput?.select(), 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function confirmSaveAs() {
|
|
66
|
+
const displayName = saveAsName.trim();
|
|
67
|
+
if (!displayName || saveAsError) return;
|
|
68
|
+
const fileName = sanitizeFileName(displayName);
|
|
69
|
+
show = false;
|
|
70
|
+
dispatch('save', { displayName, fileName });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
74
|
+
if (e.key === 'Enter') confirmSaveAs();
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<UIDialog
|
|
79
|
+
bind:show
|
|
80
|
+
title="Save As"
|
|
81
|
+
cancelLabel="Cancel"
|
|
82
|
+
confirmLabel="Save"
|
|
83
|
+
confirmDisabled={!saveAsName.trim() || !!saveAsError}
|
|
84
|
+
on:confirm={confirmSaveAs}
|
|
85
|
+
width="360px"
|
|
86
|
+
>
|
|
87
|
+
<div class="save-as-dialog">
|
|
88
|
+
<div class="save-as-row">
|
|
89
|
+
<input
|
|
90
|
+
class="save-as-input"
|
|
91
|
+
class:invalid={!!saveAsError}
|
|
92
|
+
type="text"
|
|
93
|
+
bind:value={saveAsName}
|
|
94
|
+
bind:this={saveAsInput}
|
|
95
|
+
on:keydown={handleKeydown}
|
|
96
|
+
placeholder="Config name…"
|
|
97
|
+
/>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
class="save-as-increment"
|
|
101
|
+
on:click={incrementSaveAsName}
|
|
102
|
+
title="Increment filename"
|
|
103
|
+
>
|
|
104
|
+
<i class="fas fa-plus"></i>
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
{#if saveAsError}
|
|
108
|
+
<p class="save-as-error" role="alert">{saveAsError}</p>
|
|
109
|
+
{/if}
|
|
110
|
+
</div>
|
|
111
|
+
</UIDialog>
|
|
112
|
+
|
|
113
|
+
<style>
|
|
114
|
+
.save-as-dialog {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: var(--ui-space-8);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.save-as-row {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: stretch;
|
|
123
|
+
gap: var(--ui-space-6);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.save-as-input {
|
|
127
|
+
flex: 1;
|
|
128
|
+
min-width: 0;
|
|
129
|
+
padding: var(--ui-space-8) var(--ui-space-10);
|
|
130
|
+
background: var(--ui-surface-lowest);
|
|
131
|
+
border: 1px solid var(--ui-border-subtle);
|
|
132
|
+
border-radius: var(--ui-radius-md);
|
|
133
|
+
color: var(--ui-text-primary);
|
|
134
|
+
font-size: var(--ui-font-size-md);
|
|
135
|
+
outline: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.save-as-increment {
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
width: 2.25rem;
|
|
143
|
+
padding: 0;
|
|
144
|
+
background: var(--ui-surface-low);
|
|
145
|
+
border: 1px solid var(--ui-border-subtle);
|
|
146
|
+
border-radius: var(--ui-radius-md);
|
|
147
|
+
color: var(--ui-text-secondary);
|
|
148
|
+
font-size: var(--ui-font-size-md);
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
transition: all var(--ui-transition-fast);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.save-as-increment:hover {
|
|
154
|
+
background: var(--ui-surface);
|
|
155
|
+
border-color: var(--ui-border-default);
|
|
156
|
+
color: var(--ui-text-primary);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.save-as-input:focus {
|
|
160
|
+
border-color: var(--ui-border-medium);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.save-as-input.invalid,
|
|
164
|
+
.save-as-input.invalid:focus {
|
|
165
|
+
border-color: var(--ui-highlight);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.save-as-input::placeholder {
|
|
169
|
+
color: var(--ui-text-muted);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.save-as-error {
|
|
173
|
+
margin: 0;
|
|
174
|
+
font-size: var(--ui-font-size-xs);
|
|
175
|
+
color: var(--ui-highlight);
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import newspaperBg from '../../assets/newspaper.webp';
|
|
3
|
+
|
|
4
|
+
export let mode: 'image' | 'color' = 'image';
|
|
5
|
+
/** CSS var name (set by ShadowBackdropControls) the backdrop reads when in color mode. */
|
|
6
|
+
export let colorVariable: string;
|
|
7
|
+
/** Padding around the slotted preview. Set to '0' when the slotted component should cover the full backdrop area (e.g. dialog overlay). */
|
|
8
|
+
export let padding: string = '128px';
|
|
9
|
+
|
|
10
|
+
$: backgroundStyle =
|
|
11
|
+
mode === 'image'
|
|
12
|
+
? `padding: ${padding}; background-image: url(${newspaperBg}); background-size: cover; background-position: center; background-repeat: no-repeat;`
|
|
13
|
+
: `padding: ${padding}; background: var(${colorVariable}, #1a1a1a);`;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="shadow-backdrop" style={backgroundStyle}>
|
|
17
|
+
<slot />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
.shadow-backdrop {
|
|
22
|
+
border-radius: var(--ui-radius-md);
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
</style>
|