<script module lang="ts">
import { tv, type VariantProps } from "tailwind-variants";
export const field = tv({
slots: {
root: "flex flex-col gap-1.5",
label: "text-foreground-default",
subtitle: "typography-paragraph-small text-foreground-muted"
},
variants: {
labelCase: {
upper: { label: "tracking-wide uppercase" },
normal: { label: "" }
}
},
defaultVariants: {
labelCase: "normal"
}
});
export type FieldVariants = VariantProps<typeof field>;
</script>
<script lang="ts">
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> {
label?: string;
subtitle?: string | Snippet;
for?: string;
group?: boolean;
required?: boolean;
labelCase?: FieldVariants["labelCase"];
class?: string;
children: Snippet<[{ id: string }]>;
}
let {
label,
subtitle,
for: forId,
group = false,
required = false,
labelCase,
class: className,
children,
...rest
}: Props = $props();
const generatedId = $props.id();
const id = $derived(forId ?? generatedId);
const labelId = $derived(`${id}-label`);
const slot = $derived(field({ labelCase }));
</script>
{#snippet labelText()}
{label}
{#if required}
<span class="text-foreground-danger">*</span>
{/if}
{/snippet}
<div
class={slot.root({ class: className })}
role={group ? "group" : undefined}
aria-labelledby={group && label ? labelId : undefined}
{...rest}
>
{#if label && group}
<span id={labelId} class={slot.label()}>{@render labelText()}</span>
{:else if label}
<label for={id} class={slot.label()}>{@render labelText()}</label>
{/if}
{@render children({ id })}
{#if typeof subtitle === "function"}
<p class={slot.subtitle()}>{@render subtitle()}</p>
{:else if subtitle}
<p class={slot.subtitle()}>{subtitle}</p>
{/if}
</div>