~james/core

core/web/src/lib/components/ui/Toggle.svelte -rw-r--r-- 2.2 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<script lang="ts">
	import type { Snippet } from "svelte";
	import type { HTMLInputAttributes } from "svelte/elements";
	import { tv } from "tailwind-variants";

	const toggle = tv({
		slots: {
			label: "items-center gap-xs",
			input: [
				"peer absolute inset-0 size-full cursor-pointer appearance-none rounded-full bg-background-muted",
				"checked:bg-foreground-default",
				"enabled:hover:bg-background-strong checked:enabled:hover:bg-foreground-muted",
				"focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:outline-none",
				"disabled:cursor-not-allowed disabled:bg-background-inset checked:disabled:bg-foreground-disabled"
			],
			thumb: [
				"pointer-events-none absolute top-[2px] left-[2px] size-sm rounded-full bg-background-default",
				"transition-transform duration-150 peer-checked:translate-x-[16px]"
			],
			text: ""
		},
		variants: {
			layout: {
				inline: { label: "inline-flex", text: "whitespace-nowrap" },
				block: { label: "flex w-full", text: "min-w-0 flex-1" }
			},
			size: {
				small: { label: "min-h-7 typography-paragraph-small" },
				regular: { label: "" }
			},
			disabled: {
				true: { label: "cursor-not-allowed text-foreground-disabled" },
				false: { label: "cursor-pointer" }
			}
		},
		defaultVariants: {
			layout: "inline",
			size: "regular"
		}
	});

	interface Props extends Omit<
		HTMLInputAttributes,
		"type" | "class" | "checked" | "children" | "size"
	> {
		checked?: boolean;
		disabled?: boolean;
		layout?: "inline" | "block";
		size?: "small" | "regular";
		class?: string;
		children?: Snippet;
	}

	let {
		checked = $bindable(false),
		disabled = false,
		layout = "inline",
		size = "regular",
		class: className,
		children,
		...rest
	}: Props = $props();

	const classes = $derived(toggle({ layout, size, disabled }));
</script>

<label class={classes.label({ class: className })}>
	<span class="relative inline-flex h-md w-2xl shrink-0">
		<input
			type="checkbox"
			role="switch"
			bind:checked
			{disabled}
			class={classes.input()}
			{...rest}
		/>
		<span class={classes.thumb()} aria-hidden="true"></span>
	</span>
	{#if children}
		<span class={classes.text()}>{@render children()}</span>
	{/if}
</label>