feat: add gradient control component

This commit is contained in:
2026-09-05 17:19:03 +05:00
parent b99ccf5d78
commit cad9bf6cfd
7 changed files with 393 additions and 21 deletions
@@ -17,6 +17,7 @@
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";
interface FieldControlProps {
label: string;
@@ -38,6 +39,7 @@
position9: PositionControl,
"font-style": FontStyleControl,
plate: PlateControl,
gradient: GradientControl,
};
interface Props {
@@ -0,0 +1,107 @@
<script lang="ts">
interface Preset {
value: number;
label: string;
}
interface Props {
label?: string;
value: number;
min?: number;
max?: number;
step?: number;
presets?: Preset[];
onchange: (angle: number) => void;
}
let {
label = "Angle",
value,
min = 0,
max = 360,
step = 1,
presets = [
{ value: 0, label: "0°" },
{ value: 90, label: "90°" },
{ value: 180, label: "180°" },
{ value: 270, label: "270°" },
],
onchange,
}: Props = $props();
</script>
<div class="angle-control">
{#if label}
<span class="angle-label">{label}</span>
{/if}
<div class="angle-row">
<input
type="range"
{min}
{max}
{step}
{value}
oninput={(e) => onchange(Number((e.target as HTMLInputElement).value))}
/>
<output>{value}°</output>
</div>
<div class="angle-presets">
{#each presets as preset (preset.value)}
<button
class:active={preset.value === value}
onclick={() => onchange(preset.value)}>{preset.label}</button
>
{/each}
</div>
</div>
<style>
.angle-control {
display: grid;
gap: var(--space-m);
}
.angle-label {
color: var(--color-text);
font-size: var(--font-size-s);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.angle-row {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: var(--space-m);
}
.angle-row input[type="range"] {
width: 100%;
accent-color: var(--color-main);
}
.angle-row output {
color: var(--color-text);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-m);
}
.angle-presets {
display: grid;
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>
@@ -0,0 +1,96 @@
<script lang="ts">
import AngleControl from "./AngleControl.svelte";
import type { FieldSpec, Gradient, GradientSpec } from "$lib/registry-schema";
interface Props {
label: string;
value: unknown;
spec: FieldSpec;
onchange?: (value: Gradient) => void;
}
let { label, value, spec, onchange }: Props = $props();
const sp = $derived(spec as GradientSpec);
const current = $derived.by(() => {
const v = value as Partial<Gradient> | undefined;
return {
from: typeof v?.from === "string" ? v.from : sp.from,
to: typeof v?.to === "string" ? v.to : sp.to,
angle:
typeof v?.angle === "number" && Number.isFinite(v.angle)
? v.angle
: sp.angle,
};
});
function set<K extends keyof Gradient>(key: K, val: Gradient[K]) {
onchange?.({ ...current, [key]: val });
}
</script>
<div class="control gradient-control">
<span class="gradient-label">{label}</span>
<div class="gradient-colors">
<label class="color-field">
<span>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)}
/>
</label>
<label class="color-field">
<span>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)}
/>
</label>
</div>
<AngleControl value={current.angle} onchange={(a) => set("angle", a)} />
</div>
<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;
gap: var(--space-m) var(--space-l);
}
.color-field {
display: grid;
grid-template-columns: auto 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);
}
.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>