mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: add ui components
This commit is contained in:
@@ -309,9 +309,10 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
- [x] **[C6]** Примитивы batch 2: `Panel`, `PanelHeading`, `SettingGroup`,
|
- [x] **[C6]** Примитивы batch 2: `Panel`, `PanelHeading`, `SettingGroup`,
|
||||||
`SettingsFooter`, `MetaRow`/`MetaList`, `Segmented`, `Badge`, `StepCard`,
|
`SettingsFooter`, `MetaRow`/`MetaList`, `Segmented`, `Badge`, `StepCard`,
|
||||||
`ToolCard`, `ImageCard`/`PreviewTile`.
|
`ToolCard`, `ImageCard`/`PreviewTile`.
|
||||||
- [ ] **[C7]** Контролы: `Icon`, `IconButton`, `Button`, `Field`, `SliderField`,
|
- [x] **[C7]** Контролы: `Icon`, `IconButton`, `Button`, `Field`, `SliderField`,
|
||||||
`ColorField`, `TextField`, `SelectField`, `CheckboxField`, `Toggle`,
|
`ColorField`, `TextField`, `SelectField`, `CheckboxField`, `Toggle`,
|
||||||
`SegmentedControl`, `EmptyState`, `CodeBlock`, `DownloadButton`.
|
`SegmentedControl` (= `Segmented` из C6), `EmptyState`, `CodeBlock`,
|
||||||
|
`DownloadButton`.
|
||||||
|
|
||||||
### Шаг 2. Витрина kit (проверка компонентов)
|
### Шаг 2. Витрина kit (проверка компонентов)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component, Snippet } from "svelte";
|
||||||
|
import Icon from "./Icon.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
onclick?: () => void;
|
||||||
|
type?: "button" | "submit";
|
||||||
|
variant?: "primary" | "ghost" | "accent" | "danger";
|
||||||
|
icon?: Component<{ size?: number; class?: string }>;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
children,
|
||||||
|
onclick,
|
||||||
|
type = "button",
|
||||||
|
variant = "primary",
|
||||||
|
icon,
|
||||||
|
disabled = false,
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{type}
|
||||||
|
class="btn btn--{variant}"
|
||||||
|
{disabled}
|
||||||
|
onclick={() => onclick?.()}
|
||||||
|
>
|
||||||
|
{#if icon}<Icon {icon} size={14} />{/if}
|
||||||
|
<span class="btn-label">{@render children()}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--foreground);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn--primary {
|
||||||
|
background: var(--blue);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.btn--ghost:hover:not(:disabled) {
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.btn--accent {
|
||||||
|
background: var(--cyan);
|
||||||
|
color: #042;
|
||||||
|
border-color: var(--cyan);
|
||||||
|
}
|
||||||
|
.btn--danger {
|
||||||
|
background: var(--danger);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--danger);
|
||||||
|
}
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
checked: boolean;
|
||||||
|
onchange?: (checked: boolean) => void;
|
||||||
|
}
|
||||||
|
let { label, checked = $bindable(), onchange }: Props = $props();
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
checked = (e.target as HTMLInputElement).checked;
|
||||||
|
onchange?.(checked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="checkbox-field">
|
||||||
|
<input type="checkbox" {checked} onchange={handle} />
|
||||||
|
<span class="checkbox-label">{label}</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.checkbox-field {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.checkbox-field input {
|
||||||
|
accent-color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Copy } from "@lucide/svelte";
|
||||||
|
import IconButton from "./IconButton.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
code: string;
|
||||||
|
lang?: string;
|
||||||
|
copyable?: boolean;
|
||||||
|
oncopy?: () => void;
|
||||||
|
}
|
||||||
|
let { code, lang, copyable = false, oncopy }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="code-block">
|
||||||
|
{#if lang}<span class="code-lang">{lang}</span>{/if}
|
||||||
|
<pre class="code-pre"><code>{code}</code></pre>
|
||||||
|
{#if copyable}
|
||||||
|
<div class="code-copy">
|
||||||
|
<IconButton icon={Copy} label="Copy" onclick={() => oncopy?.()} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.code-block {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--background);
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
}
|
||||||
|
.code-lang {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.code-pre {
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
.code-copy {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.4rem;
|
||||||
|
right: 0.4rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from "./Field.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
oninput?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let { label, value = $bindable(), oninput }: Props = $props();
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
value = (e.target as HTMLInputElement).value;
|
||||||
|
oninput?.(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {label}>
|
||||||
|
<div class="color-field">
|
||||||
|
<input type="color" class="color-swatch" {value} oninput={handle} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="color-hex"
|
||||||
|
{value}
|
||||||
|
oninput={handle}
|
||||||
|
spellcheck="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.color-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.color-swatch {
|
||||||
|
width: 2rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.color-hex {
|
||||||
|
flex: 1;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Download } from "@lucide/svelte";
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
size?: string;
|
||||||
|
onclick?: () => void;
|
||||||
|
}
|
||||||
|
let { label, size, onclick }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button icon={Download} variant="primary" onclick={() => onclick?.()}>
|
||||||
|
{label}{#if size}<span class="dl-size">{size}</span>{/if}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dl-size {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component, Snippet } from "svelte";
|
||||||
|
import Icon from "./Icon.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: Component<{ size?: number; class?: string }>;
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
let { title, description, icon, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="empty-state">
|
||||||
|
{#if icon}<span class="empty-icon"><Icon {icon} size={28} /></span>{/if}
|
||||||
|
<strong class="empty-title">{title}</strong>
|
||||||
|
{#if description}<span class="empty-desc">{description}</span>{/if}
|
||||||
|
{#if children}<div class="empty-action">{@render children()}</div>{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.empty-icon {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.empty-title {
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.empty-desc {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
max-width: 32ch;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
import MonoLabel from "./MonoLabel.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
children: Snippet;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
let { label, children, hint }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<MonoLabel>{label}</MonoLabel>
|
||||||
|
<div class="field-control">{@render children()}</div>
|
||||||
|
{#if hint}<span class="field-hint">{hint}</span>{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
.field-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.field-hint {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component } from "svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: Component<{ size?: number; class?: string }>;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
let { icon: IconComp, size = 16 }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<IconComp {size} />
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component } from "svelte";
|
||||||
|
import Icon from "./Icon.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: Component<{ size?: number; class?: string }>;
|
||||||
|
label: string;
|
||||||
|
onclick?: () => void;
|
||||||
|
variant?: "ghost" | "solid" | "accent";
|
||||||
|
size?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
onclick,
|
||||||
|
variant = "ghost",
|
||||||
|
size = 16,
|
||||||
|
disabled = false,
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="icon-btn icon-btn--{variant}"
|
||||||
|
aria-label={label}
|
||||||
|
{disabled}
|
||||||
|
onclick={() => onclick?.()}
|
||||||
|
>
|
||||||
|
<Icon {icon} {size} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.icon-btn:hover:not(:disabled) {
|
||||||
|
color: var(--foreground);
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.icon-btn--solid {
|
||||||
|
background: var(--blue);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.icon-btn--accent {
|
||||||
|
background: var(--cyan);
|
||||||
|
color: #042;
|
||||||
|
border-color: var(--cyan);
|
||||||
|
}
|
||||||
|
.icon-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from "./Field.svelte";
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
options: Option[];
|
||||||
|
onchange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let { label, value = $bindable(), options, onchange }: Props = $props();
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
value = (e.target as HTMLSelectElement).value;
|
||||||
|
onchange?.(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {label}>
|
||||||
|
<select class="select-field" {value} onchange={handle}>
|
||||||
|
{#each options as opt (opt.value)}
|
||||||
|
<option value={opt.value}>{opt.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.select-field {
|
||||||
|
width: 100%;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from "./Field.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
suffix?: string;
|
||||||
|
oninput?: (value: number) => void;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
label,
|
||||||
|
value = $bindable(),
|
||||||
|
min = 0,
|
||||||
|
max = 100,
|
||||||
|
step = 1,
|
||||||
|
suffix = "",
|
||||||
|
oninput,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
value = Number((e.target as HTMLInputElement).value);
|
||||||
|
oninput?.(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {label}>
|
||||||
|
<div class="slider-field">
|
||||||
|
<input type="range" {min} {max} {step} {value} oninput={handle} />
|
||||||
|
<span class="slider-value">{value}{suffix}</span>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.slider-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
flex: 1;
|
||||||
|
accent-color: var(--blue);
|
||||||
|
}
|
||||||
|
.slider-value {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--foreground);
|
||||||
|
min-width: 4ch;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
type?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
readonly?: boolean;
|
||||||
|
oninput?: (value: string) => void;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
value = $bindable(),
|
||||||
|
type = "text",
|
||||||
|
placeholder = "",
|
||||||
|
readonly = false,
|
||||||
|
oninput,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
function handle(e: Event) {
|
||||||
|
value = (e.target as HTMLInputElement).value;
|
||||||
|
oninput?.(value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="text-field"
|
||||||
|
{type}
|
||||||
|
{placeholder}
|
||||||
|
{readonly}
|
||||||
|
{value}
|
||||||
|
oninput={handle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.text-field {
|
||||||
|
width: 100%;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
.text-field:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
checked: boolean;
|
||||||
|
label?: string;
|
||||||
|
onchange?: (checked: boolean) => void;
|
||||||
|
}
|
||||||
|
let { checked = $bindable(), label, onchange }: Props = $props();
|
||||||
|
|
||||||
|
function handle() {
|
||||||
|
checked = !checked;
|
||||||
|
onchange?.(checked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toggle"
|
||||||
|
class:on={checked}
|
||||||
|
role="switch"
|
||||||
|
aria-checked={checked}
|
||||||
|
aria-label={label}
|
||||||
|
onclick={handle}
|
||||||
|
>
|
||||||
|
<span class="toggle-knob"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toggle {
|
||||||
|
position: relative;
|
||||||
|
width: 2.4rem;
|
||||||
|
height: 1.3rem;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--background);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.toggle.on {
|
||||||
|
background: var(--blue);
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.toggle-knob {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
width: calc(1.3rem - 4px);
|
||||||
|
height: calc(1.3rem - 4px);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--foreground);
|
||||||
|
transition: transform 0.12s ease;
|
||||||
|
}
|
||||||
|
.toggle.on .toggle-knob {
|
||||||
|
transform: translateX(1.1rem);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user