feat(frontend): restyle ui primitives for v3

This commit is contained in:
Elias Schneider
2026-10-27 23:32:53 +01:00
parent bdb3578da5
commit 82df10f80b
144 changed files with 550 additions and 659 deletions
@@ -1,3 +1,14 @@
<script lang="ts" module>
// Shared by inputs, textareas and select triggers
// The dark: classes repeat the focus and invalid borders because the dark variant sorts after every state variant
export const filledFieldClass =
'border border-transparent bg-fill-muted text-fg outline-hidden hover:bg-fill-hover focus-visible:border-ring focus-visible:bg-surface focus-visible:ring-(length:--halo-width) focus-visible:ring-ring-halo aria-invalid:border-danger-solid aria-invalid:ring-danger-solid/20 disabled:cursor-not-allowed disabled:opacity-45 dark:border-fg/10 dark:bg-fg/6 dark:hover:bg-fg/9 dark:focus-visible:border-ring dark:focus-visible:bg-fg/7 dark:aria-invalid:border-danger-solid';
// Read-only fields drop the fill for a hairline so they never look editable
export const readOnlyFieldClass =
'read-only:border-border read-only:bg-transparent! dark:read-only:border-border';
</script>
<script lang="ts">
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import { cn, type WithElementRef } from '#lib/utils/style.js';
@@ -18,31 +29,28 @@
'data-slot': dataSlot = 'input',
...restProps
}: Props = $props();
// Phone inputs use 16px text so iOS does not zoom on focus, and mono values elsewhere use the mono size because mono glyphs run larger than sans ones
const inputClass = $derived(
cn(
filledFieldClass,
readOnlyFieldClass,
'h-(--h-md) w-full min-w-0 rounded-sm px-3.5 text-body placeholder:text-fg-muted autofill:shadow-[inset_0_0_0_100px_var(--fill-muted)] autofill:[-webkit-text-fill-color:var(--fg)] max-sm:text-[16px] sm:[&.font-mono]:text-mono file:inline-flex file:h-full file:border-0 file:bg-transparent file:font-medium file:text-fg',
className
)
);
</script>
{#if type === 'file'}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 file:text-foreground placeholder:text-muted-foreground h-9 w-full min-w-0 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm',
className
)}
class={inputClass}
type="file"
bind:files
bind:value
{...restProps}
/>
{:else}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 file:text-foreground placeholder:text-muted-foreground h-9 w-full min-w-0 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm',
className
)}
{type}
bind:value
{...restProps}
/>
<input bind:this={ref} data-slot={dataSlot} class={inputClass} {type} bind:value {...restProps} />
{/if}