mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: add design system components
This commit is contained in:
@@ -160,6 +160,19 @@ button:disabled {
|
|||||||
border-radius: var(--radius-m);
|
border-radius: var(--radius-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
padding: var(--space-1) var(--space-2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
background: var(--surface);
|
||||||
|
max-width: var(--control-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.control[type='number'] {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.error-banner {
|
.error-banner {
|
||||||
padding: var(--space-2) var(--space-3);
|
padding: var(--space-2) var(--space-3);
|
||||||
border: 1px solid var(--danger);
|
border: 1px solid var(--danger);
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
variant?: 'primary' | 'secondary';
|
||||||
|
type?: 'button' | 'submit';
|
||||||
|
disabled?: boolean;
|
||||||
|
busy?: boolean;
|
||||||
|
busyText?: string;
|
||||||
|
onclick?: () => void;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
variant = 'primary',
|
||||||
|
type = 'button',
|
||||||
|
disabled = false,
|
||||||
|
busy = false,
|
||||||
|
busyText = '',
|
||||||
|
onclick,
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{type}
|
||||||
|
class={variant}
|
||||||
|
aria-busy={busy}
|
||||||
|
disabled={disabled || busy}
|
||||||
|
onclick={onclick}
|
||||||
|
>
|
||||||
|
{#if busy && busyText}
|
||||||
|
{busyText}
|
||||||
|
{:else}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: var(--text-m);
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background var(--transition-fast),
|
||||||
|
border-color var(--transition-fast),
|
||||||
|
color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--accent-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary:hover:not(:disabled) {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover:not(:disabled) {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
checked?: boolean;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, checked = $bindable(false), hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="checkbox">
|
||||||
|
<input id={id} type="checkbox" bind:checked />
|
||||||
|
<label for={id}>{label}</label>
|
||||||
|
</div>
|
||||||
|
{#if hint}
|
||||||
|
<p class="hint text-caption text-muted">{hint}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: var(--text-m);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
margin: calc(-1 * var(--space-3)) 0 var(--space-3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from './Field.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value?: string;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, value = $bindable('#000000'), hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {id} {label} {hint}>
|
||||||
|
<input id={id} class="control swatch" type="color" bind:value />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.swatch {
|
||||||
|
height: var(--control-height);
|
||||||
|
width: 3.5rem;
|
||||||
|
padding: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
hint?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, hint, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="empty panel">
|
||||||
|
<p class="title">{title}</p>
|
||||||
|
{#if hint}
|
||||||
|
<p class="text-caption text-muted">{hint}</p>
|
||||||
|
{/if}
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
min-height: 8rem;
|
||||||
|
padding: var(--space-4);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
hint?: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, hint, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="text-caption text-muted" for={id}>{label}</label>
|
||||||
|
{@render children()}
|
||||||
|
{#if hint}
|
||||||
|
<p class="hint text-caption text-muted">{hint}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-1);
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from './Field.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value?: string;
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, value = $bindable(''), options, hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {id} {label} {hint}>
|
||||||
|
<select id={id} class="control" bind:value>
|
||||||
|
{#each options as option (option.value)}
|
||||||
|
<option value={option.value}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from './Field.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, value = $bindable(0), min, max, step, hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {id} {label} {hint}>
|
||||||
|
<div class="row">
|
||||||
|
<input id={id} type="range" min={min} max={max} step={step} bind:value />
|
||||||
|
<output>{value}</output>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
max-width: var(--control-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'] {
|
||||||
|
flex: 1;
|
||||||
|
accent-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
min-width: 3ch;
|
||||||
|
text-align: right;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--text-s);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from './Field.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value?: string | number;
|
||||||
|
type?: 'number' | 'text';
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id, label, value = $bindable(''), type = 'text', min, max, step, hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {id} {label} {hint}>
|
||||||
|
<input class="control" {id} {type} min={min} max={max} step={step} bind:value />
|
||||||
|
</Field>
|
||||||
Reference in New Issue
Block a user