diff --git a/web/src/lib/components/ui/Input.stories.svelte b/web/src/lib/components/ui/Input.stories.svelte --- a/web/src/lib/components/ui/Input.stories.svelte +++ b/web/src/lib/components/ui/Input.stories.svelte @@ -2,6 +2,7 @@ import { defineMeta } from "@storybook/addon-svelte-csf"; import Search from "$icon/search"; import Mail from "$icon/mail"; + import Button from "./Button.svelte"; import Input from "./Input.svelte"; const { Story } = defineMeta({ @@ -9,12 +10,17 @@ component: Input, tags: ["autodocs"], argTypes: { + size: { + control: { type: "inline-radio" }, + options: ["sm", "md", "lg"] + }, error: { control: "boolean" }, disabled: { control: "boolean" }, loading: { control: "boolean" }, placeholder: { control: "text" } }, args: { + size: "md", error: false, disabled: false, loading: false, @@ -31,3 +37,33 @@ + + + + + + + + + + + + + + + + + + + + + + + {#each ["sm", "md", "lg"] as const as s (s)} + + + Create + + {/each} + + diff --git a/web/src/lib/components/ui/Input.svelte b/web/src/lib/components/ui/Input.svelte --- a/web/src/lib/components/ui/Input.svelte +++ b/web/src/lib/components/ui/Input.svelte @@ -2,8 +2,18 @@ import { tv, type VariantProps } from "tailwind-variants"; export const inputField = tv({ - base: "flex min-h-9 items-center gap-2 rounded-sm border bg-background-default px-2 py-1 transition-colors", + base: "flex items-center rounded-sm border bg-background-default transition-colors", variants: { + // same 32/36/40 heights as Button's sm/md/lg, so an input and a button of the same + // size line up in a row. line-height + py + border stays under each min-h, so the + // field sits exactly on that height whatever it holds. horizontal padding runs one + // step tighter than Button's — a button centres its label and needs the room, a + // field's text is left-aligned against the border. + size: { + sm: "min-h-8 gap-1.5 px-1.5 py-1 typography-paragraph-small", + md: "min-h-9 gap-2 px-2 py-1 typography-paragraph-regular", + lg: "min-h-10 gap-2.5 px-3 py-1 typography-paragraph-large" + }, error: { false: "border-border-default focus-within:border-border-strong focus-within:ring-1 focus-within:ring-border-strong", @@ -15,6 +25,7 @@ } }, defaultVariants: { + size: "md", error: false, disabled: false } @@ -28,8 +39,11 @@ import type { HTMLInputAttributes, SvelteHTMLElements } from "svelte/elements"; import Spinner from "./Spinner.svelte"; - interface Props extends Omit { + // `size` shadows the native character-width attribute, which nothing here uses and which + // no design would reach for over a width class + interface Props extends Omit { value?: string; + size?: InputFieldVariants["size"]; error?: boolean; disabled?: boolean; loading?: boolean; @@ -44,6 +58,7 @@ let { value = $bindable(""), + size = "md", error = false, disabled = false, loading = false, @@ -55,13 +70,15 @@ ...rest }: Props = $props(); - const classes = $derived(inputField({ error, disabled, class: className })); + const classes = $derived(inputField({ size, error, disabled, class: className })); + // matches Button: the icon only steps up on lg + const iconSize = $derived(size === "lg" ? "size-5" : "size-4"); {#if iconLeft} {@const IconLeft = iconLeft} - + {/if} {#if suffix} {suffix} {/if} {#if loading} - + {:else if iconRight} {@const IconRight = iconRight} - + {/if}