This repository has no description
1<script module lang="ts">
2 import { tv, type VariantProps } from "tailwind-variants";
3
4 export const field = tv({
5 slots: {
6 root: "flex flex-col gap-1.5",
7 label: "typography-paragraph-small font-medium text-foreground-default",
8 subtitle: "typography-paragraph-small text-foreground-muted"
9 },
10 variants: {
11 // settings forms label their fields in small caps; the onboarding steps use
12 // sentence case, so the casing is a variant rather than baked into the slot
13 labelCase: {
14 upper: { label: "tracking-wide uppercase" },
15 normal: { label: "" }
16 }
17 },
18 defaultVariants: {
19 labelCase: "upper"
20 }
21 });
22
23 export type FieldVariants = VariantProps<typeof field>;
24</script>
25
26<script lang="ts">
27 import type { Snippet } from "svelte";
28 import type { HTMLAttributes } from "svelte/elements";
29
30 interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> {
31 label?: string;
32 subtitle?: string;
33 for?: string;
34 required?: boolean;
35 labelCase?: FieldVariants["labelCase"];
36 class?: string;
37 children: Snippet<[{ id: string }]>;
38 }
39
40 let {
41 label,
42 subtitle,
43 for: forId,
44 required = false,
45 labelCase = "upper",
46 class: className,
47 children,
48 ...rest
49 }: Props = $props();
50
51 const generatedId = $props.id();
52 const id = $derived(forId ?? generatedId);
53 const slot = $derived(field({ labelCase }));
54</script>
55
56<div class={slot.root({ class: className })} {...rest}>
57 {#if label}
58 <label for={id} class={slot.label()}>
59 {label}
60 {#if required}
61 <span class="text-foreground-danger">*</span>
62 {/if}
63 </label>
64 {/if}
65 {@render children({ id })}
66 {#if subtitle}
67 <p class={slot.subtitle()}>{subtitle}</p>
68 {/if}
69</div>