refactor: separate schema fields components
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RotateCcw } from "@lucide/svelte";
|
import { RotateCcw } from "@lucide/svelte";
|
||||||
import type { ToolSchema, Dimension } from "$lib/registry-schema";
|
import type { ToolSchema } from "$lib/registry-schema";
|
||||||
|
import CheckboxControl from "./fields/schema/CheckboxControl.svelte";
|
||||||
|
import ColorControl from "./fields/schema/ColorControl.svelte";
|
||||||
|
import RangeControl from "./fields/schema/RangeControl.svelte";
|
||||||
|
import SelectControl from "./fields/schema/SelectControl.svelte";
|
||||||
|
import TextControl from "./fields/schema/TextControl.svelte";
|
||||||
import DimensionField from "./fields/DimensionField.svelte";
|
import DimensionField from "./fields/DimensionField.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -11,6 +16,16 @@
|
|||||||
}
|
}
|
||||||
let { schema, values, onchange, onreset }: Props = $props();
|
let { schema, values, onchange, onreset }: Props = $props();
|
||||||
|
|
||||||
|
const FIELDS = {
|
||||||
|
number: RangeControl,
|
||||||
|
slider: RangeControl,
|
||||||
|
color: ColorControl,
|
||||||
|
select: SelectControl,
|
||||||
|
checkbox: CheckboxControl,
|
||||||
|
text: TextControl,
|
||||||
|
dimension: DimensionField,
|
||||||
|
} as const;
|
||||||
|
|
||||||
function labelOf(id: string): string {
|
function labelOf(id: string): string {
|
||||||
return id
|
return id
|
||||||
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
||||||
@@ -20,73 +35,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each Object.entries(schema.fields) as [id, field] (id)}
|
{#each Object.entries(schema.fields) as [id, field] (id)}
|
||||||
{#if field.spec.kind === "number" || field.spec.kind === "slider"}
|
{@const Control = FIELDS[field.spec.kind]}
|
||||||
<label class="control">
|
<Control
|
||||||
<span>
|
|
||||||
{labelOf(id)}
|
|
||||||
<output>{String(values[id] ?? field.spec.default)}</output>
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min={field.spec.min ?? 0}
|
|
||||||
max={field.spec.max ?? 100}
|
|
||||||
step={field.spec.step ?? 1}
|
|
||||||
value={Number(values[id] ?? field.spec.default)}
|
|
||||||
oninput={(e) =>
|
|
||||||
onchange(id, Number((e.target as HTMLInputElement).value))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{:else if field.spec.kind === "color"}
|
|
||||||
<label class="control color-control">
|
|
||||||
<span>{labelOf(id)}</span>
|
|
||||||
<span class="color-row">
|
|
||||||
<span
|
|
||||||
class="swatch"
|
|
||||||
style="background:{String(values[id] ?? field.spec.default)}"
|
|
||||||
></span>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={String(values[id] ?? field.spec.default)}
|
|
||||||
oninput={(e) => onchange(id, (e.target as HTMLInputElement).value)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
{:else if field.spec.kind === "select"}
|
|
||||||
<label class="control">
|
|
||||||
<span>{labelOf(id)}</span>
|
|
||||||
<select
|
|
||||||
class="select-field"
|
|
||||||
value={String(values[id] ?? field.spec.default)}
|
|
||||||
oninput={(e) => onchange(id, (e.target as HTMLSelectElement).value)}
|
|
||||||
>
|
|
||||||
{#each field.spec.options as option (option.value)}
|
|
||||||
<option value={option.value}>{option.label}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
{:else if field.spec.kind === "checkbox"}
|
|
||||||
<label class="control checkbox-control">
|
|
||||||
<span>{labelOf(id)}</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox-field"
|
|
||||||
checked={Boolean(values[id] ?? field.spec.default)}
|
|
||||||
onchange={(e) => onchange(id, (e.target as HTMLInputElement).checked)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{:else if field.spec.kind === "dimension"}
|
|
||||||
<div class="control">
|
|
||||||
<DimensionField
|
|
||||||
label={labelOf(id)}
|
label={labelOf(id)}
|
||||||
value={(values[id] as Dimension) ?? {
|
value={values[id]}
|
||||||
width: field.spec.width,
|
|
||||||
height: field.spec.height,
|
|
||||||
}}
|
|
||||||
spec={field.spec}
|
spec={field.spec}
|
||||||
oninput={(v) => onchange(id, v)}
|
onchange={(v) => onchange(id, v)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<div class="panel-foot">
|
<div class="panel-foot">
|
||||||
@@ -97,67 +52,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<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 {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
input[type="range"] {
|
|
||||||
width: 100%;
|
|
||||||
accent-color: var(--color-main);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
.select-field {
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--space-m) var(--space-l);
|
|
||||||
background: var(--color-background);
|
|
||||||
border: var(--size-border) solid var(--color-border);
|
|
||||||
border-radius: var(--radius-s);
|
|
||||||
color: var(--color-text);
|
|
||||||
font: var(--font-size-s) var(--font-mono);
|
|
||||||
}
|
|
||||||
.checkbox-control {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.checkbox-field {
|
|
||||||
width: var(--space-xl);
|
|
||||||
height: var(--space-xl);
|
|
||||||
accent-color: var(--color-main);
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
.panel-foot {
|
.panel-foot {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -1,50 +1,97 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Dimension, DimensionSpec } from "$lib/registry-schema";
|
import type {
|
||||||
import NumberField from "./NumberField.svelte";
|
Dimension,
|
||||||
|
DimensionSpec,
|
||||||
|
FieldSpec,
|
||||||
|
} from "$lib/registry-schema";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string;
|
label: string;
|
||||||
value: Dimension;
|
value: unknown;
|
||||||
spec: DimensionSpec;
|
spec: FieldSpec;
|
||||||
oninput?: (value: Dimension) => void;
|
onchange?: (value: Dimension) => void;
|
||||||
}
|
}
|
||||||
let { label, value = $bindable(), spec, oninput }: Props = $props();
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as DimensionSpec);
|
||||||
|
const current = $derived.by(() => {
|
||||||
|
const v = value as Partial<Dimension> | undefined;
|
||||||
|
return {
|
||||||
|
width:
|
||||||
|
typeof v?.width === "number" && Number.isFinite(v.width)
|
||||||
|
? v.width
|
||||||
|
: sp.width,
|
||||||
|
height:
|
||||||
|
typeof v?.height === "number" && Number.isFinite(v.height)
|
||||||
|
? v.height
|
||||||
|
: sp.height,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
function setAxis(axis: "width" | "height", n: number) {
|
function setAxis(axis: "width" | "height", n: number) {
|
||||||
value = { ...value, [axis]: n };
|
onchange?.({ ...current, [axis]: n });
|
||||||
oninput?.(value);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="dimension-field">
|
<div class="control dimension-control">
|
||||||
<span class="dimension-label">{label}</span>
|
<span class="dimension-label">{label}</span>
|
||||||
<NumberField
|
<div class="dimension-field">
|
||||||
label="Width"
|
<label class="dimension-axis">
|
||||||
value={value.width}
|
<span>Width</span>
|
||||||
min={spec.min}
|
<input
|
||||||
max={spec.max}
|
type="number"
|
||||||
oninput={(n) => setAxis("width", n)}
|
min={sp.min}
|
||||||
|
max={sp.max}
|
||||||
|
value={current.width}
|
||||||
|
oninput={(e) =>
|
||||||
|
setAxis("width", Number((e.target as HTMLInputElement).value))}
|
||||||
/>
|
/>
|
||||||
<NumberField
|
</label>
|
||||||
label="Height"
|
<label class="dimension-axis">
|
||||||
value={value.height}
|
<span>Height</span>
|
||||||
min={spec.min}
|
<input
|
||||||
max={spec.max}
|
type="number"
|
||||||
oninput={(n) => setAxis("height", n)}
|
min={sp.min}
|
||||||
|
max={sp.max}
|
||||||
|
value={current.height}
|
||||||
|
oninput={(e) =>
|
||||||
|
setAxis("height", Number((e.target as HTMLInputElement).value))}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dimension-field {
|
.control {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: var(--space-m);
|
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 {
|
.dimension-label {
|
||||||
grid-column: 1 / -1;
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: var(--font-size-s);
|
font-size: var(--font-size-s);
|
||||||
letter-spacing: var(--space-text-l);
|
letter-spacing: var(--space-text-l);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
.dimension-field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
.dimension-axis > span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--space-m);
|
||||||
|
}
|
||||||
|
.dimension-axis input {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
background: var(--color-background);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { CheckboxSpec, FieldSpec } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as CheckboxSpec);
|
||||||
|
const current = $derived(
|
||||||
|
typeof value === "boolean" ? value : Boolean(sp.default),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="control checkbox-control">
|
||||||
|
<span>{label}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox-field"
|
||||||
|
checked={current}
|
||||||
|
onchange={(e) => onchange?.((e.target as HTMLInputElement).checked)}
|
||||||
|
/>
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
.checkbox-control {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.checkbox-field {
|
||||||
|
width: var(--space-xl);
|
||||||
|
height: var(--space-xl);
|
||||||
|
accent-color: var(--color-main);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ColorSpec, FieldSpec } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as ColorSpec);
|
||||||
|
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>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { FieldSpec, NumberSpec, SliderSpec } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: number) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as NumberSpec | SliderSpec);
|
||||||
|
const current = $derived(
|
||||||
|
typeof value === "number" && Number.isFinite(value) ? value : sp.default,
|
||||||
|
);
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
onchange?.(Number((e.target as HTMLInputElement).value));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="control">
|
||||||
|
<span>
|
||||||
|
{label}
|
||||||
|
<output>{current}</output>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={sp.min ?? 0}
|
||||||
|
max={sp.max ?? 100}
|
||||||
|
step={sp.step ?? 1}
|
||||||
|
value={current}
|
||||||
|
oninput={handle}
|
||||||
|
/>
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
.control > span {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.control output {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
accent-color: var(--color-main);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { FieldSpec, SelectSpec } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as SelectSpec);
|
||||||
|
const current = $derived(
|
||||||
|
typeof value === "string" && sp.options.some((o) => o.value === value)
|
||||||
|
? value
|
||||||
|
: sp.default,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="control">
|
||||||
|
<span>{label}</span>
|
||||||
|
<select
|
||||||
|
class="select-field"
|
||||||
|
value={current}
|
||||||
|
oninput={(e) => onchange?.((e.target as HTMLSelectElement).value)}
|
||||||
|
>
|
||||||
|
{#each sp.options as option (option.value)}
|
||||||
|
<option value={option.value}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
.select-field {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
background: var(--color-background);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { FieldSpec, TextSpec } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
spec: FieldSpec;
|
||||||
|
onchange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let { label, value, spec, onchange }: Props = $props();
|
||||||
|
|
||||||
|
const sp = $derived(spec as TextSpec);
|
||||||
|
const current = $derived(typeof value === "string" ? value : sp.default);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="control">
|
||||||
|
<span>{label}</span>
|
||||||
|
<input
|
||||||
|
class="text-field"
|
||||||
|
type="text"
|
||||||
|
value={current}
|
||||||
|
placeholder={sp.placeholder}
|
||||||
|
oninput={(e) => onchange?.((e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
.text-field {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
background: var(--color-background);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user