refactor: separate components

This commit is contained in:
2026-10-04 08:48:14 +05:00
parent f89811c116
commit c4eab38866
63 changed files with 529 additions and 645 deletions
@@ -1,62 +0,0 @@
<script lang="ts">
import { Upload } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import DownloadButton from "./ui/DownloadButton.svelte";
interface Props {
inputMode: "image" | "text" | "none";
canDownload: boolean;
running: boolean;
onupload: (file: File) => void;
ondownload: () => void;
}
let { inputMode, canDownload, running, onupload, ondownload }: Props =
$props();
</script>
<div class="actions">
{#if inputMode === "image"}
<label class="btn upload">
<Upload size={14} />
{t("actions.openImage")}
<input
type="file"
accept="image/*"
onchange={(e) => {
const f = (e.target as HTMLInputElement).files?.[0];
if (f) onupload(f);
}}
/>
</label>
{/if}
{#if canDownload}
<DownloadButton
label={t("actions.downloadResult")}
onclick={ondownload}
disabled={running}
/>
{/if}
</div>
<style>
.actions {
display: flex;
align-items: center;
gap: var(--space-l);
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-m);
padding: var(--space-m) var(--space-l);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-m);
font: var(--font-size-s) var(--font-mono);
color: var(--color-text-muted);
cursor: pointer;
}
.upload input {
display: none;
}
</style>
@@ -1,5 +1,6 @@
<script lang="ts">
import { Funnel, Search } from "@lucide/svelte";
import ChipButton from "$lib/components/ui/ChipButton.svelte";
import { t } from "$lib/i18n/t";
interface Props {
@@ -33,14 +34,11 @@
<div class="catalog-filters">
<Funnel size={15} />
{#each FILTERS as f (f.value)}
<button
type="button"
class:active={category === f.value}
aria-pressed={category === f.value}
<ChipButton
label={f.label}
active={category === f.value}
onclick={() => (category = f.value)}
>
{f.label}
</button>
/>
{/each}
</div>
</div>
@@ -81,20 +79,4 @@
flex-wrap: wrap;
color: var(--color-text-muted);
}
.catalog-filters button {
border: var(--size-border) solid var(--color-border);
background: var(--color-panel);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
text-transform: uppercase;
padding: var(--space-s) var(--space-m);
border-radius: var(--radius-m);
cursor: pointer;
}
.catalog-filters button.active {
background: var(--color-main);
color: var(--color-background);
border-color: var(--color-main);
}
</style>
@@ -2,7 +2,7 @@
import { resolve } from "$app/paths";
import { ArrowUpRight } from "@lucide/svelte";
import type { Component } from "svelte";
import Icon from "./ui/Icon.svelte";
import Icon from "$lib/components/ui/Icon.svelte";
interface Props {
title: string;
@@ -1,7 +1,7 @@
<script lang="ts">
import { Copy } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import IconButton from "./ui/IconButton.svelte";
import IconButton from "$lib/components/ui/IconButton.svelte";
interface Props {
code: string;
@@ -1,5 +1,6 @@
<script lang="ts">
import { Ellipsis, GripVertical, X } from "@lucide/svelte";
import IconButton from "$lib/components/ui/IconButton.svelte";
import { t } from "$lib/i18n/t";
import type { Snippet } from "svelte";
@@ -26,14 +27,13 @@
</div>
<div class="step-tools">
{#if tools}{@render tools()}{/if}
<button
type="button"
class="step-remove"
aria-label={t("chain.removeStepAria")}
<IconButton
icon={X}
label={t("chain.removeStepAria")}
variant="clear"
size="s"
onclick={() => onremove?.()}
>
<X size={16} />
</button>
/>
<Ellipsis size={17} />
</div>
</div>
@@ -81,24 +81,10 @@
align-items: center;
gap: var(--space-l);
}
.step-remove {
border: 0;
background: transparent;
color: var(--color-text-muted);
cursor: pointer;
line-height: inherit;
& :global(svg) {
display: block;
line-height: inherit;
}
}
.step-card :global(.drag) {
margin-top: var(--space-xl);
color: var(--color-border);
}
.step-remove:hover {
color: var(--color-danger);
}
.step-body > :not(.step-heading) {
padding: var(--space-l);
}
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Component, Snippet } from "svelte";
import Icon from "./ui/Icon.svelte";
import Icon from "$lib/components/ui/Icon.svelte";
interface Props {
title: string;
@@ -1,6 +1,6 @@
<script lang="ts">
import StatusDot from "./StatusDot.svelte";
import MonoLabel from "./MonoLabel.svelte";
import MonoLabel from "$lib/components/display/MonoLabel.svelte";
interface Props {
label: string;
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Snippet } from "svelte";
import MonoLabel from "../MonoLabel.svelte";
import MonoLabel from "$lib/components/display/MonoLabel.svelte";
interface Props {
label: string;
@@ -1,4 +1,5 @@
<script lang="ts">
import ChipButton from "$lib/components/ui/ChipButton.svelte";
import { t } from "$lib/i18n/t";
interface Preset {
@@ -49,10 +50,12 @@
</div>
<div class="angle-presets">
{#each presets as preset (preset.value)}
<button
class:active={preset.value === value}
onclick={() => onchange(preset.value)}>{preset.label}</button
>
<ChipButton
label={preset.label}
variant="outline"
active={preset.value === value}
onclick={() => onchange(preset.value)}
/>
{/each}
</div>
</div>
@@ -88,22 +91,4 @@
grid-template-columns: repeat(4, 1fr);
gap: var(--space-m);
}
.angle-presets button {
padding: var(--space-m) 0;
color: var(--color-text-muted);
background: var(--color-background);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
cursor: pointer;
}
.angle-presets button:hover {
color: var(--color-text);
border-color: var(--color-main);
}
.angle-presets button.active {
color: var(--color-main);
border-color: var(--color-main);
}
</style>
@@ -1,5 +1,6 @@
<script lang="ts">
import type { CheckboxSpec, FieldSpec } from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -15,30 +16,16 @@
);
</script>
<label class="control checkbox-control">
<span>{label}</span>
<Control element="label" {label} row>
<input
type="checkbox"
class="checkbox-field"
checked={current}
onchange={(e) => onchange?.((e.target as HTMLInputElement).checked)}
/>
</label>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.checkbox-control {
display: flex;
justify-content: space-between;
align-items: center;
}
.checkbox-field {
width: var(--space-xl);
height: var(--space-xl);
@@ -1,5 +1,7 @@
<script lang="ts">
import type { ColorSpec, FieldSpec } from "$lib/registry-schema";
import ColorSwatchInput from "./ColorSwatchInput.svelte";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -13,47 +15,6 @@
const current = $derived(typeof value === "string" ? value : sp.default);
</script>
<label class="control color-control">
<span>{label}</span>
<span class="color-row">
<span class="swatch" style="background:{current}"></span>
<input
type="color"
value={current}
oninput={(e) => onchange?.((e.target as HTMLInputElement).value)}
/>
</span>
</label>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.color-control > span:first-child {
justify-content: flex-start;
}
.color-row {
display: flex;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
input[type="color"] {
width: 100%;
height: var(--space-xxl);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
background: var(--color-background);
cursor: pointer;
}
</style>
<Control element="label" {label}>
<ColorSwatchInput value={current} onchange={(v) => onchange?.(v)} />
</Control>
@@ -5,6 +5,8 @@
ColorPairSpec,
FieldSpec,
} from "$lib/registry-schema";
import ColorSwatchInput from "./ColorSwatchInput.svelte";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -28,45 +30,26 @@
}
</script>
<div class="control color-pair-control">
<span class="pair-label">{label}</span>
<Control {label} caps>
<div class="pair-field">
<label class="pair-axis">
<span class="swatch" style="background:{current.from}"></span>
<input
type="color"
<ColorSwatchInput
value={current.from}
oninput={(e) => setColor("from", (e.target as HTMLInputElement).value)}
onchange={(v) => setColor("from", v)}
/>
<span class="axis-label">{t("ui.from")}</span>
</label>
<label class="pair-axis">
<span class="swatch" style="background:{current.to}"></span>
<input
type="color"
<ColorSwatchInput
value={current.to}
oninput={(e) => setColor("to", (e.target as HTMLInputElement).value)}
onchange={(v) => setColor("to", v)}
/>
<span class="axis-label">{t("ui.to")}</span>
</label>
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.pair-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.pair-field {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -74,25 +57,6 @@
}
.pair-axis {
display: grid;
grid-template-columns: var(--space-xxl) 1fr;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
.pair-axis input[type="color"] {
width: 100%;
height: var(--space-xxl);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
background: var(--color-background);
cursor: pointer;
}
.axis-label {
grid-column: 1 / -1;
}
</style>
@@ -0,0 +1,44 @@
<script lang="ts">
interface Props {
value: string;
disabled?: boolean;
onchange?: (value: string) => void;
}
let { value, disabled = false, onchange }: Props = $props();
</script>
<span class="swatch-field">
<span class="swatch" style="background:{value}"></span>
<input
type="color"
{value}
{disabled}
oninput={(e) => onchange?.((e.target as HTMLInputElement).value)}
/>
</span>
<style>
.swatch-field {
display: grid;
grid-template-columns: var(--space-xxl) 1fr;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
input[type="color"] {
width: 100%;
height: var(--space-xxl);
padding: 0;
background: var(--color-background);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
cursor: pointer;
}
</style>
@@ -1,10 +1,14 @@
<script lang="ts">
import { X } from "@lucide/svelte";
import Button from "$lib/components/ui/Button.svelte";
import IconButton from "$lib/components/ui/IconButton.svelte";
import { t } from "$lib/i18n/t";
import type {
ColorList,
ColorListSpec,
FieldSpec,
} from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -41,8 +45,7 @@
}
</script>
<div class="control colors-control">
<span class="ctrl-label">{label}</span>
<Control {label} caps>
<div class="chips">
{#each current as hex, i (i)}
<div class="chip">
@@ -55,38 +58,28 @@
/>
</label>
<span class="hex">{hex}</span>
<button
type="button"
class="remove"
<IconButton
icon={X}
label={t("ui.removeColor")}
variant="clear"
size="s"
onclick={() => removeColor(i)}
disabled={current.length <= 1}
aria-label={t("ui.removeColor")}
>
×
</button>
/>
</div>
{/each}
</div>
<button type="button" class="add" onclick={addColor}
>{t("ui.addColor")}</button
>
</div>
<div class="add-row">
<Button
label={t("ui.addColor")}
variant="outline"
size="s"
onclick={addColor}
/>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.ctrl-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.chips {
display: flex;
flex-wrap: wrap;
@@ -121,39 +114,8 @@
color: var(--color-text-muted);
font-size: var(--font-size-s);
}
.remove {
.add-row {
display: flex;
align-items: center;
justify-content: center;
width: var(--space-xl);
height: var(--space-xl);
border: none;
border-radius: var(--radius-s);
background: none;
color: var(--color-text-muted);
font-size: var(--font-size-s);
cursor: pointer;
}
.remove:hover:not(:disabled) {
color: var(--color-text);
background: var(--color-background-muted);
}
.remove:disabled {
opacity: 0.4;
cursor: default;
}
.add {
justify-self: start;
padding: var(--space-s) var(--space-m);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-m);
background: none;
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
cursor: pointer;
}
.add:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
justify-content: flex-start;
}
</style>
@@ -0,0 +1,57 @@
<script lang="ts">
import type { Snippet } from "svelte";
interface Props {
label: string;
element?: "div" | "label";
caps?: boolean;
row?: boolean;
trailing?: Snippet;
children: Snippet;
}
let {
label,
element = "div",
caps = false,
row = false,
trailing,
children,
}: Props = $props();
</script>
<svelte:element this={element} class="control" class:control--row={row}>
<span class="label" class:caps>
{label}
{#if trailing}{@render trailing()}{/if}
</span>
{@render children()}
</svelte:element>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.control--row {
display: flex;
justify-content: space-between;
align-items: center;
}
.label {
display: flex;
justify-content: space-between;
}
.caps {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
</style>
@@ -5,6 +5,7 @@
DimensionSpec,
FieldSpec,
} from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -34,8 +35,7 @@
}
</script>
<div class="control dimension-control">
<span class="dimension-label">{label}</span>
<Control {label} caps>
<div class="dimension-field">
<label class="dimension-axis">
<span>{t("ui.width")}</span>
@@ -60,23 +60,9 @@
/>
</label>
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.dimension-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.dimension-field {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -6,6 +6,8 @@
FontStyle,
FontStyleSpec,
} from "$lib/registry-schema";
import ColorSwatchInput from "./ColorSwatchInput.svelte";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -43,8 +45,7 @@
];
</script>
<div class="control font-style-control">
<span class="fs-label">{label}</span>
<Control {label} caps>
<div class="fs-grid">
<label class="fs-cell">
<span class="fs-sub">{t("ui.font")}</span>
@@ -73,15 +74,10 @@
</label>
<label class="fs-cell">
<span class="fs-sub">{t("ui.color")}</span>
<span class="fs-color">
<span class="swatch" style="background:{current.color}"></span>
<input
class="fs-color-input"
type="color"
value={current.color}
oninput={(e) => set("color", (e.target as HTMLInputElement).value)}
/>
</span>
<ColorSwatchInput
value={current.color}
onchange={(v) => set("color", v)}
/>
</label>
<label class="fs-cell fs-bold">
<span class="fs-sub">{t("ui.bold")}</span>
@@ -92,23 +88,9 @@
/>
</label>
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.fs-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.fs-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -122,8 +104,7 @@
font-size: var(--font-size-s);
}
.fs-select,
.fs-input,
.fs-color-input {
.fs-input {
width: 100%;
padding: var(--space-m) var(--space-l);
background: var(--color-background);
@@ -132,23 +113,6 @@
color: var(--color-text);
font: var(--font-size-s) var(--font-mono);
}
.fs-color {
display: grid;
grid-template-columns: var(--space-xxl) 1fr;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
.fs-color-input {
height: var(--space-xxl);
padding: 0;
cursor: pointer;
}
.fs-bold {
display: flex;
justify-content: space-between;
@@ -1,5 +1,7 @@
<script lang="ts">
import AngleControl from "./AngleControl.svelte";
import ColorSwatchInput from "./ColorSwatchInput.svelte";
import Control from "./Control.svelte";
import { t } from "$lib/i18n/t";
import type { FieldSpec, Gradient, GradientSpec } from "$lib/registry-schema";
@@ -29,46 +31,21 @@
}
</script>
<div class="control gradient-control">
<span class="gradient-label">{label}</span>
<Control {label} caps>
<div class="gradient-colors">
<label class="color-field">
<span>{t("ui.from")}</span>
<span class="swatch" style="background:{current.from}"></span>
<input
type="color"
value={current.from}
oninput={(e) => set("from", (e.target as HTMLInputElement).value)}
/>
<ColorSwatchInput value={current.from} onchange={(v) => set("from", v)} />
</label>
<label class="color-field">
<span>{t("ui.to")}</span>
<span class="swatch" style="background:{current.to}"></span>
<input
type="color"
value={current.to}
oninput={(e) => set("to", (e.target as HTMLInputElement).value)}
/>
<ColorSwatchInput value={current.to} onchange={(v) => set("to", v)} />
</label>
</div>
<AngleControl value={current.angle} onchange={(a) => set("angle", a)} />
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.gradient-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.gradient-colors {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -76,22 +53,8 @@
}
.color-field {
display: grid;
grid-template-columns: auto var(--space-xxl) 1fr;
grid-template-columns: auto 1fr;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
.color-field input[type="color"] {
width: 100%;
height: var(--space-xxl);
padding: 0;
background: var(--color-background);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
}
</style>
@@ -1,5 +1,6 @@
<script lang="ts">
import type { FieldSpec, Offset, OffsetSpec } from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -23,8 +24,7 @@
}
</script>
<div class="control offset-control">
<span class="offset-label">{label}</span>
<Control {label} caps>
<div class="offset-field">
<label class="offset-axis">
<span>X</span>
@@ -49,23 +49,9 @@
/>
</label>
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.offset-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.offset-field {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -1,6 +1,8 @@
<script lang="ts">
import { t } from "$lib/i18n/t";
import type { FieldSpec, Plate, PlateSpec } from "$lib/registry-schema";
import ColorSwatchInput from "./ColorSwatchInput.svelte";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -28,8 +30,7 @@
}
</script>
<div class="control plate-control">
<span class="plate-label">{label}</span>
<Control {label} caps>
<div class="plate-row {current.enabled ? '' : 'off'}">
<label class="plate-enabled">
<span>{t("ui.backingPlate")}</span>
@@ -39,15 +40,11 @@
onchange={(e) => set("enabled", (e.target as HTMLInputElement).checked)}
/>
</label>
<label class="plate-color">
<span class="swatch" style="background:{current.color}"></span>
<input
type="color"
value={current.color}
disabled={!current.enabled}
oninput={(e) => set("color", (e.target as HTMLInputElement).value)}
/>
</label>
<ColorSwatchInput
value={current.color}
disabled={!current.enabled}
onchange={(v) => set("color", v)}
/>
<label class="plate-opacity">
<span>
{t("ui.opacity")}
@@ -65,23 +62,9 @@
/>
</label>
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.plate-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.plate-row {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -101,26 +84,6 @@
height: var(--space-xl);
accent-color: var(--color-main);
}
.plate-color {
display: grid;
grid-template-columns: var(--space-xxl) 1fr;
align-items: center;
gap: var(--space-m);
}
.swatch {
width: var(--space-xxl);
height: var(--space-xxl);
border-radius: var(--radius-s);
border: var(--size-border) solid var(--color-border);
}
.plate-color input[type="color"] {
width: 100%;
height: var(--space-xxl);
padding: 0;
background: var(--color-background);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-s);
}
.plate-opacity {
display: grid;
gap: var(--space-m);
@@ -5,6 +5,7 @@
type FieldSpec,
type Position9Spec,
} from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -23,8 +24,7 @@
);
</script>
<div class="control position-control">
<span class="position-label">{label}</span>
<Control {label} caps>
<div class="grid">
{#each POSITION9_VALUES as position (position)}
<button
@@ -38,23 +38,9 @@
</button>
{/each}
</div>
</div>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.position-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.grid {
display: inline-grid;
grid-template-columns: repeat(3, var(--space-xl));
@@ -1,5 +1,6 @@
<script lang="ts">
import type { FieldSpec, NumberSpec, SliderSpec } from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -19,11 +20,10 @@
}
</script>
<label class="control">
<span>
{label}
<Control element="label" {label}>
{#snippet trailing()}
<output>{current}</output>
</span>
{/snippet}
<input
aria-label={label}
type="range"
@@ -33,22 +33,10 @@
value={current}
oninput={handle}
/>
</label>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.control > span {
display: flex;
justify-content: space-between;
}
.control output {
output {
color: var(--color-text);
}
input[type="range"] {
@@ -1,6 +1,7 @@
<script lang="ts">
import { optionLabel } from "$lib/i18n/schema-tool-strings";
import type { FieldSpec, SelectSpec } from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -20,8 +21,7 @@
);
</script>
<label class="control">
<span>{label}</span>
<Control element="label" {label}>
<select
class="select-field"
value={current}
@@ -33,17 +33,9 @@
</option>
{/each}
</select>
</label>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.select-field {
width: 100%;
padding: var(--space-m) var(--space-l);
@@ -1,5 +1,6 @@
<script lang="ts">
import type { FieldSpec, TextSpec } from "$lib/registry-schema";
import Control from "./Control.svelte";
interface Props {
label: string;
@@ -13,8 +14,7 @@
const current = $derived(typeof value === "string" ? value : sp.default);
</script>
<label class="control">
<span>{label}</span>
<Control element="label" {label}>
<input
class="text-field"
type="text"
@@ -22,17 +22,9 @@
placeholder={sp.placeholder}
oninput={(e) => onchange?.((e.target as HTMLInputElement).value)}
/>
</label>
</Control>
<style>
.control {
display: grid;
gap: var(--space-m);
margin-bottom: var(--space-xl);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.text-field {
width: 100%;
padding: var(--space-m) var(--space-l);
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Snippet } from "svelte";
import PanelHeading from "../PanelHeading.svelte";
import PanelHeading from "./PanelHeading.svelte";
interface Props {
title: string;
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Snippet } from "svelte";
import MonoLabel from "./MonoLabel.svelte";
import MonoLabel from "$lib/components/display/MonoLabel.svelte";
interface Props {
title: string;
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { resolve } from "$app/paths";
import LangToggle from "../ui/LangToggle.svelte";
import ThemeToggle from "../ui/ThemeToggle.svelte";
import TopNav from "../ui/TopNav.svelte";
import LangToggle from "$lib/components/ui/LangToggle.svelte";
import ThemeToggle from "$lib/components/ui/ThemeToggle.svelte";
import TopNav from "./TopNav.svelte";
interface Props {
theme: "light" | "dark";
@@ -0,0 +1,37 @@
<script lang="ts">
import DownloadButton from "$lib/components/ui/DownloadButton.svelte";
import UploadButton from "$lib/components/ui/UploadButton.svelte";
import { t } from "$lib/i18n/t";
interface Props {
inputMode: "image" | "text" | "none";
canDownload: boolean;
running: boolean;
onupload: (file: File) => void;
ondownload: () => void;
}
let { inputMode, canDownload, running, onupload, ondownload }: Props =
$props();
</script>
<div class="actions">
{#if inputMode === "image"}
<UploadButton label={t("actions.openImage")} onfile={onupload} />
{/if}
{#if canDownload}
<DownloadButton
label={t("actions.downloadResult")}
onclick={ondownload}
disabled={running}
/>
{/if}
</div>
<style>
.actions {
display: flex;
align-items: center;
gap: var(--space-l);
flex-wrap: wrap;
}
</style>
@@ -9,19 +9,20 @@
import { t } from "$lib/i18n/t";
import { RotateCcw } from "@lucide/svelte";
import type { Component } from "svelte";
import DimensionField from "./fields/DimensionField.svelte";
import CheckboxControl from "./fields/schema/CheckboxControl.svelte";
import ColorControl from "./fields/schema/ColorControl.svelte";
import ColorPairControl from "./fields/schema/ColorPairControl.svelte";
import ColorsControl from "./fields/schema/ColorsControl.svelte";
import RangeControl from "./fields/schema/RangeControl.svelte";
import SelectControl from "./fields/schema/SelectControl.svelte";
import TextControl from "./fields/schema/TextControl.svelte";
import OffsetControl from "./fields/schema/OffsetControl.svelte";
import PositionControl from "./fields/schema/PositionControl.svelte";
import FontStyleControl from "./fields/schema/FontStyleControl.svelte";
import PlateControl from "./fields/schema/PlateControl.svelte";
import GradientControl from "./fields/schema/GradientControl.svelte";
import Button from "$lib/components/ui/Button.svelte";
import DimensionField from "$lib/components/fields/schema/DimensionField.svelte";
import CheckboxControl from "$lib/components/fields/schema/CheckboxControl.svelte";
import ColorControl from "$lib/components/fields/schema/ColorControl.svelte";
import ColorPairControl from "$lib/components/fields/schema/ColorPairControl.svelte";
import ColorsControl from "$lib/components/fields/schema/ColorsControl.svelte";
import RangeControl from "$lib/components/fields/schema/RangeControl.svelte";
import SelectControl from "$lib/components/fields/schema/SelectControl.svelte";
import TextControl from "$lib/components/fields/schema/TextControl.svelte";
import OffsetControl from "$lib/components/fields/schema/OffsetControl.svelte";
import PositionControl from "$lib/components/fields/schema/PositionControl.svelte";
import FontStyleControl from "$lib/components/fields/schema/FontStyleControl.svelte";
import PlateControl from "$lib/components/fields/schema/PlateControl.svelte";
import GradientControl from "$lib/components/fields/schema/GradientControl.svelte";
interface FieldControlProps {
label: string;
@@ -86,10 +87,13 @@
{/each}
<div class="panel-foot">
<button class="reset-btn" onclick={onreset}>
<RotateCcw size={14} />
{t("ui.reset")}
</button>
<Button
icon={RotateCcw}
label={t("ui.reset")}
variant="clear"
size="s"
onclick={onreset}
/>
<span class="auto-note"><i></i> {t("ui.autoUpdate")}</span>
</div>
@@ -120,19 +124,6 @@
padding-top: var(--space-l);
border-top: var(--size-border) solid var(--color-border);
}
.reset-btn {
display: inline-flex;
align-items: center;
gap: var(--space-m);
background: none;
border: none;
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
cursor: pointer;
}
.reset-btn:hover {
color: var(--color-text);
}
.auto-note i {
width: var(--size-border-thick);
height: var(--size-border-thick);
@@ -1,9 +1,9 @@
<script lang="ts">
import MetaList from "$lib/components/MetaList.svelte";
import { buildSchemaPreviewModel } from "$lib/components/schema-preview-model";
import SchemaActions from "$lib/components/SchemaActions.svelte";
import SchemaResultTile from "$lib/components/SchemaResultTile.svelte";
import SchemaSourceTile from "$lib/components/SchemaSourceTile.svelte";
import MetaList from "$lib/components/display/MetaList.svelte";
import { buildSchemaPreviewModel } from "./schema-preview-model";
import SchemaActions from "./SchemaActions.svelte";
import SchemaResultTile from "./SchemaResultTile.svelte";
import SchemaSourceTile from "./SchemaSourceTile.svelte";
import type { PixelImage } from "$lib/core/types";
import { t } from "$lib/i18n/t";
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
@@ -5,7 +5,7 @@
import type { FileResult, ResultKind } from "$lib/registry";
import { Check } from "@lucide/svelte";
import SchemaTextResult from "./SchemaTextResult.svelte";
import PreviewTile from "./layout/PreviewTile.svelte";
import PreviewTile from "./PreviewTile.svelte";
interface Props {
resultKind?: ResultKind;
@@ -3,7 +3,7 @@
import type { PixelImage } from "$lib/core/types";
import { t } from "$lib/i18n/t";
import type { InputMode } from "$lib/registry";
import PreviewTile from "./layout/PreviewTile.svelte";
import PreviewTile from "./PreviewTile.svelte";
import SchemaTextSource from "./SchemaTextSource.svelte";
interface Props {
@@ -2,7 +2,7 @@
import { verdictText, verdictTone } from "$lib/i18n/schema-tool-strings";
import { t } from "$lib/i18n/t";
import { Copy, Download, FileText } from "@lucide/svelte";
import IconButton from "./ui/IconButton.svelte";
import IconButton from "$lib/components/ui/IconButton.svelte";
interface Props {
value: string;
@@ -1,5 +1,6 @@
<script lang="ts">
import { ArrowUp, Upload } from "@lucide/svelte";
import Button from "$lib/components/ui/Button.svelte";
import { t } from "$lib/i18n/t";
interface Props {
@@ -36,15 +37,21 @@
}}></textarea>
<div class="actions">
{#if onsample}
<button type="button" class="secondary" onclick={onsample}>
<Upload size={14} />
{t("textSource.trySample")}
</button>
<Button
icon={Upload}
label={t("textSource.trySample")}
variant="outline"
size="s"
onclick={onsample}
/>
{/if}
<button type="button" class="render" onclick={onrender} {disabled}>
<ArrowUp size={14} />
{t("textSource.render")}
</button>
<Button
icon={ArrowUp}
label={t("textSource.render")}
size="s"
onclick={onrender}
{disabled}
/>
</div>
</div>
@@ -76,36 +83,4 @@
gap: var(--space-m);
justify-content: flex-end;
}
.render {
display: inline-flex;
align-items: center;
gap: var(--space-m);
padding: var(--space-m) var(--space-l);
border: var(--size-border) solid var(--color-main);
border-radius: var(--radius-m);
background: var(--color-main);
color: var(--color-background);
font: var(--font-size-s) var(--font-mono);
cursor: pointer;
}
.render:disabled {
opacity: 0.6;
cursor: default;
}
.secondary {
display: inline-flex;
align-items: center;
gap: var(--space-m);
padding: var(--space-m) var(--space-l);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-m);
background: none;
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
cursor: pointer;
}
.secondary:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
}
</style>
@@ -1,6 +1,6 @@
<script lang="ts">
import SchemaFields from "$lib/components/SchemaFields.svelte";
import SchemaPreview from "$lib/components/SchemaPreview.svelte";
import SchemaFields from "./SchemaFields.svelte";
import SchemaPreview from "./SchemaPreview.svelte";
import { debounce } from "$lib/core/debounce";
import { ToolError, type ErrorVars } from "$lib/core/errors";
import { decodeFile, downloadBlob, encode } from "$lib/core/io";
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { makeImage } from "../core/test-helpers";
import { makeImage } from "../../core/test-helpers";
import {
buildSchemaPreviewModel,
type PreviewTranslate,
@@ -1,5 +1,5 @@
import type { PixelImage } from "../core/types";
import type { FileResult, InputMode, ResultKind } from "../registry";
import type { PixelImage } from "$lib/core/types";
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
export type PreviewTranslate = (key: string) => string;
+46 -2
View File
@@ -1,6 +1,10 @@
<script lang="ts">
import type { Component } from "svelte";
import { ButtonVariantDefine, type ButtonVariant } from "../define";
import {
ButtonVariantDefine,
type ButtonVariant,
type ButtonSize,
} from "./define";
import Icon from "./Icon.svelte";
interface Props {
@@ -8,21 +12,27 @@
ariaLabel?: string;
onclick?: () => void;
variant?: ButtonVariant;
size?: ButtonSize;
icon?: Component<{ size?: number; class?: string }>;
disabled?: boolean;
type?: "button" | "submit";
}
let {
label,
ariaLabel = label,
onclick,
variant = ButtonVariantDefine.PRIMARY,
size = "m",
icon,
disabled = false,
type = "button",
}: Props = $props();
</script>
<button
class="btn btn--{variant}"
class="btn btn--{variant} btn--{size}"
class:btn--icon={!label}
{type}
{disabled}
aria-label={ariaLabel}
onclick={() => onclick?.()}
@@ -51,12 +61,36 @@
color: var(--color-background);
}
.btn--s {
gap: var(--space-m);
padding: var(--space-m) var(--space-l);
border-width: var(--size-border);
border-radius: var(--radius-m);
font-size: var(--font-size-s);
font-family: var(--font-mono);
font-weight: normal;
}
.btn--s.btn--icon {
padding: var(--space-s);
}
.btn--outline {
background: transparent;
color: var(--color-main-tint);
border-color: var(--color-main-tint);
}
.btn--s.btn--outline {
color: var(--color-text-muted);
border-color: var(--color-border);
}
.btn--s.btn--outline:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
}
.btn--accent {
background: var(--color-accent);
color: var(--color-background);
@@ -69,6 +103,16 @@
border-color: var(--color-danger);
}
.btn--clear {
background: none;
border-color: transparent;
color: var(--color-text-muted);
}
.btn--clear:hover {
color: var(--color-text);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
@@ -0,0 +1,56 @@
<script lang="ts">
interface Props {
label: string;
active?: boolean;
variant?: "filled" | "outline";
onclick?: () => void;
}
let { label, active = false, variant = "filled", onclick }: Props = $props();
</script>
<button
type="button"
class="chip chip--{variant}"
class:active
aria-pressed={active}
onclick={() => onclick?.()}
>
{label}
</button>
<style>
.chip {
border: var(--size-border) solid var(--color-border);
background: var(--color-panel);
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
text-transform: uppercase;
padding: var(--space-s) var(--space-m);
border-radius: var(--radius-m);
cursor: pointer;
}
.chip--filled.active {
background: var(--color-main);
color: var(--color-background);
border-color: var(--color-main);
}
.chip--outline {
padding: var(--space-m) 0;
background: var(--color-background);
border-radius: var(--radius-s);
text-transform: none;
}
.chip--outline:hover {
color: var(--color-text);
border-color: var(--color-main);
}
.chip--outline.active {
color: var(--color-main);
border-color: var(--color-main);
}
</style>
+16 -2
View File
@@ -1,6 +1,10 @@
<script lang="ts">
import type { Component } from "svelte";
import { ButtonVariantDefine, type ButtonVariant } from "../define";
import {
ButtonVariantDefine,
type ButtonVariant,
type ButtonSize,
} from "./define";
import Button from "./Button.svelte";
interface Props {
@@ -8,6 +12,7 @@
label: string;
onclick?: () => void;
variant?: ButtonVariant;
size?: ButtonSize;
disabled?: boolean;
}
@@ -16,8 +21,17 @@
label,
onclick,
variant = ButtonVariantDefine.PRIMARY,
size = "m",
disabled = false,
}: Props = $props();
</script>
<Button label="" {onclick} {variant} {disabled} {icon} ariaLabel={label} />
<Button
label=""
{onclick}
{variant}
{size}
{disabled}
{icon}
ariaLabel={label}
/>
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { Moon, Sun } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import IconButton from "../ui/IconButton.svelte";
import IconButton from "./IconButton.svelte";
interface Props {
theme: "light" | "dark";
@@ -0,0 +1,47 @@
<script lang="ts">
import { Upload } from "@lucide/svelte";
import Icon from "./Icon.svelte";
interface Props {
label: string;
accept?: string;
onfile: (file: File) => void;
}
let { label, accept = "image/*", onfile }: Props = $props();
</script>
<label class="upload">
<Icon icon={Upload} size={14} />
{label}
<input
type="file"
{accept}
onchange={(e) => {
const f = (e.target as HTMLInputElement).files?.[0];
if (f) onfile(f);
}}
/>
</label>
<style>
.upload {
display: inline-flex;
align-items: center;
gap: var(--space-m);
padding: var(--space-m) var(--space-l);
border: var(--size-border) solid var(--color-border);
border-radius: var(--radius-m);
font: var(--font-size-s) var(--font-mono);
color: var(--color-text-muted);
cursor: pointer;
}
.upload:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
}
.upload input {
display: none;
}
</style>
@@ -8,3 +8,11 @@ export const ButtonVariantDefine = {
export type ButtonVariant =
(typeof ButtonVariantDefine)[keyof typeof ButtonVariantDefine];
export const ButtonSizeDefine = {
M: "m",
S: "s",
} as const;
export type ButtonSize =
(typeof ButtonSizeDefine)[keyof typeof ButtonSizeDefine];