From 502fb080547a32682f41663f63d7f860f8d40e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 30 Jun 2026 14:24:00 +0200 Subject: [PATCH] Add v2 Select and TextField UI kit components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compliance-portal Subprocessors toolbar needs a dropdown filter and a search input, which the v2 kit did not provide. Add Select (styled over Base UI's headless select) and TextField (over Base UI's input) following the v2 conventions: Base UI primitives, tailwind-variants/lite slots, flat folders, bundle-safe variants, and paired skeletons. Both map the Figma semantic colors to the Radix sand/gold token scales. Signed-off-by: Émile Ré --- packages/ui/src/v2/Select/Select.stories.tsx | 74 +++++++++++++++++++ packages/ui/src/v2/Select/Select.tsx | 27 +++++++ packages/ui/src/v2/Select/SelectItem.tsx | 41 ++++++++++ packages/ui/src/v2/Select/SelectPopup.tsx | 47 ++++++++++++ packages/ui/src/v2/Select/SelectSkeleton.tsx | 28 +++++++ packages/ui/src/v2/Select/SelectTrigger.tsx | 48 ++++++++++++ packages/ui/src/v2/Select/variants.ts | 72 ++++++++++++++++++ packages/ui/src/v2/form/TextField.stories.tsx | 56 ++++++++++++++ packages/ui/src/v2/form/TextField.tsx | 43 +++++++++++ packages/ui/src/v2/form/TextFieldSkeleton.tsx | 28 +++++++ packages/ui/src/v2/form/variants.ts | 51 +++++++++++++ 11 files changed, 515 insertions(+) create mode 100644 packages/ui/src/v2/Select/Select.stories.tsx create mode 100644 packages/ui/src/v2/Select/Select.tsx create mode 100644 packages/ui/src/v2/Select/SelectItem.tsx create mode 100644 packages/ui/src/v2/Select/SelectPopup.tsx create mode 100644 packages/ui/src/v2/Select/SelectSkeleton.tsx create mode 100644 packages/ui/src/v2/Select/SelectTrigger.tsx create mode 100644 packages/ui/src/v2/Select/variants.ts create mode 100644 packages/ui/src/v2/form/TextField.stories.tsx create mode 100644 packages/ui/src/v2/form/TextField.tsx create mode 100644 packages/ui/src/v2/form/TextFieldSkeleton.tsx create mode 100644 packages/ui/src/v2/form/variants.ts diff --git a/packages/ui/src/v2/Select/Select.stories.tsx b/packages/ui/src/v2/Select/Select.stories.tsx new file mode 100644 index 000000000..4b25f52ea --- /dev/null +++ b/packages/ui/src/v2/Select/Select.stories.tsx @@ -0,0 +1,74 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { useState } from "react"; + +import { Select } from "./Select"; +import { SelectItem } from "./SelectItem"; +import { SelectPopup } from "./SelectPopup"; +import { SelectSkeleton } from "./SelectSkeleton"; +import { SelectTrigger } from "./SelectTrigger"; + +export default { + title: "v2/Select", + component: Select, +}; + +const fruits: Record = { + apple: "Apple", + banana: "Banana", + cherry: "Cherry", +}; + +export function Default() { + return ( +
+ +
+ ); +} + +export function Controlled() { + const [value, setValue] = useState(null); + return ( +
+
+ +
+ Selected: {value ?? "none"} +
+ ); +} + +export function Skeleton() { + return ; +} diff --git a/packages/ui/src/v2/Select/Select.tsx b/packages/ui/src/v2/Select/Select.tsx new file mode 100644 index 000000000..9fc5a9030 --- /dev/null +++ b/packages/ui/src/v2/Select/Select.tsx @@ -0,0 +1,27 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Select as BaseSelect } from "@base-ui/react/select"; + +// Root of the select (Radix "Select"). Controlled the same way as Base UI's +// Select: `value` / `onValueChange`, or left uncontrolled. Generic over the +// option value type. See contrib/claude/ui.md. +export type SelectProps + = BaseSelect.Root.Props; + +export function Select( + props: SelectProps, +) { + return ; +} diff --git a/packages/ui/src/v2/Select/SelectItem.tsx b/packages/ui/src/v2/Select/SelectItem.tsx new file mode 100644 index 000000000..b1cb55543 --- /dev/null +++ b/packages/ui/src/v2/Select/SelectItem.tsx @@ -0,0 +1,41 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Select as BaseSelect } from "@base-ui/react/select"; +import { CheckIcon } from "@phosphor-icons/react"; +import type { ComponentProps } from "react"; + +import { selectItem } from "./variants"; + +export type SelectItemProps + = & Omit, "className"> + & { className?: string }; + +// A single option. Shows a trailing check when selected. Inherits highlight +// styling from the popup surface. +export function SelectItem(props: SelectItemProps) { + const { className, children, ...rest } = props; + const { item, label, indicator } = selectItem(); + + return ( + + + {children} + + + + + + ); +} diff --git a/packages/ui/src/v2/Select/SelectPopup.tsx b/packages/ui/src/v2/Select/SelectPopup.tsx new file mode 100644 index 000000000..c2708c00f --- /dev/null +++ b/packages/ui/src/v2/Select/SelectPopup.tsx @@ -0,0 +1,47 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Select as BaseSelect } from "@base-ui/react/select"; +import type { ComponentProps } from "react"; + +import { selectPopup } from "./variants"; + +export type SelectPopupProps + = & Omit, "className"> + & { + className?: string; + // Positioner placement passthrough. + side?: ComponentProps["side"]; + align?: ComponentProps["align"]; + sideOffset?: ComponentProps["sideOffset"]; + }; + +// Portal + positioner + styled popup holding the select items. +export function SelectPopup(props: SelectPopupProps) { + const { + className, children, + side = "bottom", align = "start", sideOffset = 4, + ...popupProps + } = props; + + return ( + + + + {children} + + + + ); +} diff --git a/packages/ui/src/v2/Select/SelectSkeleton.tsx b/packages/ui/src/v2/Select/SelectSkeleton.tsx new file mode 100644 index 000000000..45d2d060d --- /dev/null +++ b/packages/ui/src/v2/Select/SelectSkeleton.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import type { VariantProps } from "tailwind-variants/lite"; + +import { selectSkeleton } from "./variants"; + +export type SelectSkeletonProps = Omit, "children"> & VariantProps; + +// Loading placeholder paired with the Select trigger: a pulse block matching its +// size and radius. +export function SelectSkeleton(props: SelectSkeletonProps) { + const { size, className, ...rest } = props; + + return ; +} diff --git a/packages/ui/src/v2/Select/SelectTrigger.tsx b/packages/ui/src/v2/Select/SelectTrigger.tsx new file mode 100644 index 000000000..12fbe1161 --- /dev/null +++ b/packages/ui/src/v2/Select/SelectTrigger.tsx @@ -0,0 +1,48 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Select as BaseSelect } from "@base-ui/react/select"; +import { CaretDownIcon } from "@phosphor-icons/react"; +import type { ComponentProps, ReactNode } from "react"; + +import { selectTrigger } from "./variants"; + +export type SelectTriggerProps + = & Omit, "className" | "children"> + & { + className?: string; + size?: 1 | 2; + // Shown when no value is selected. + placeholder?: ReactNode; + // Renders the selected value; pass a function to map a value to its label. + children?: BaseSelect.Value.Props["children"]; + }; + +// The bordered surface button that opens the popup, showing the selected value +// (or placeholder) and a trailing chevron. +export function SelectTrigger(props: SelectTriggerProps) { + const { className, size, placeholder, children, ...rest } = props; + const { trigger, value, icon } = selectTrigger({ size }); + + return ( + + + {children} + + + + + + ); +} diff --git a/packages/ui/src/v2/Select/variants.ts b/packages/ui/src/v2/Select/variants.ts new file mode 100644 index 000000000..774cc6c96 --- /dev/null +++ b/packages/ui/src/v2/Select/variants.ts @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Select (Radix "Select" over Base UI's Select). A bordered surface trigger on +// the neutral background, with an accent-highlighted popup of items. + +export const selectTrigger = tv({ + slots: { + trigger: [ + "flex w-full items-center justify-between gap-2 rounded-2 border border-sand-a7 bg-sand-1 text-2 text-sand-12", + "cursor-pointer outline-none transition-colors hover:bg-sand-2", + "focus-visible:ring-2 focus-visible:ring-sand-8 focus-visible:ring-offset-1 focus-visible:ring-offset-sand-1", + "data-disabled:pointer-events-none data-disabled:opacity-50 data-placeholder:text-sand-a10", + ], + value: "min-w-0 flex-1 truncate text-left", + icon: "flex size-4 shrink-0 items-center justify-center text-sand-a10 [&_svg]:size-4", + }, + variants: { + size: { + 1: { trigger: "h-7 px-2" }, + 2: { trigger: "h-8 px-3" }, + }, + }, + defaultVariants: { + size: 2, + }, +}); + +export const selectPopup = tv({ + base: [ + "max-h-(--available-height) min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto rounded-3 bg-sand-1 p-1 shadow-3 outline-none", + "transition-[scale,opacity] duration-150 ease-out data-starting-style:scale-95 data-starting-style:opacity-0", + "data-ending-style:scale-95 data-ending-style:opacity-0", + ], +}); + +export const selectItem = tv({ + slots: { + item: [ + "flex h-8 cursor-pointer items-center justify-between gap-2 rounded-2 px-3 text-2 text-sand-12 outline-none select-none", + "data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-sand-3", + ], + label: "min-w-0 flex-1 truncate", + indicator: "flex size-4 shrink-0 items-center justify-center text-sand-12 [&_svg]:size-4", + }, +}); + +export const selectSkeleton = tv({ + base: "inline-block animate-pulse rounded-2 bg-sand-3 align-middle", + variants: { + size: { + 1: "h-7 w-40", + 2: "h-8 w-40", + }, + }, + defaultVariants: { + size: 2, + }, +}); diff --git a/packages/ui/src/v2/form/TextField.stories.tsx b/packages/ui/src/v2/form/TextField.stories.tsx new file mode 100644 index 000000000..7a94b9e33 --- /dev/null +++ b/packages/ui/src/v2/form/TextField.stories.tsx @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { MagnifyingGlassIcon } from "@phosphor-icons/react"; +import { useState } from "react"; + +import { TextField } from "./TextField"; +import { TextFieldSkeleton } from "./TextFieldSkeleton"; + +export default { + title: "v2/form/TextField", + component: TextField, +}; + +export function Default() { + return ( +
+ +
+ ); +} + +export function WithIcon() { + return ( +
+ } placeholder="Search..." /> +
+ ); +} + +export function Controlled() { + const [value, setValue] = useState(""); + return ( +
+
+ +
+ Value: {value || "(empty)"} +
+ ); +} + +export function Skeleton() { + return ; +} diff --git a/packages/ui/src/v2/form/TextField.tsx b/packages/ui/src/v2/form/TextField.tsx new file mode 100644 index 000000000..0f01c71f3 --- /dev/null +++ b/packages/ui/src/v2/form/TextField.tsx @@ -0,0 +1,43 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Input } from "@base-ui/react/input"; +import type { ComponentProps, ReactNode } from "react"; + +import { textField } from "./variants"; + +export type TextFieldProps + = & Omit, "className"> + & { + // Applied to the bordered container (the top-level element). + className?: string; + size?: 1 | 2; + // Leading icon slot (e.g. a MagnifyingGlassIcon for search). + icon?: ReactNode; + }; + +// Single-line text input on a bordered surface. The container is the top-level +// node; all native input props spread onto the inner control. Controlled with +// Base UI's `value` / `onValueChange` (or native `value` / `onChange`). +export function TextField(props: TextFieldProps) { + const { className, size, icon, ...inputProps } = props; + const { root, icon: iconSlot, input } = textField({ size }); + + return ( +
+ {icon != null && {icon}} + +
+ ); +} diff --git a/packages/ui/src/v2/form/TextFieldSkeleton.tsx b/packages/ui/src/v2/form/TextFieldSkeleton.tsx new file mode 100644 index 000000000..f952014a6 --- /dev/null +++ b/packages/ui/src/v2/form/TextFieldSkeleton.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import type { VariantProps } from "tailwind-variants/lite"; + +import { textFieldSkeleton } from "./variants"; + +export type TextFieldSkeletonProps = Omit, "children"> & VariantProps; + +// Loading placeholder paired with TextField: a pulse block matching its size and +// radius. +export function TextFieldSkeleton(props: TextFieldSkeletonProps) { + const { size, className, ...rest } = props; + + return ; +} diff --git a/packages/ui/src/v2/form/variants.ts b/packages/ui/src/v2/form/variants.ts new file mode 100644 index 000000000..8f9fa0cd0 --- /dev/null +++ b/packages/ui/src/v2/form/variants.ts @@ -0,0 +1,51 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Text input (Radix "Text Field" over Base UI's Input). A bordered surface on +// the neutral background with an optional leading icon slot. +export const textField = tv({ + slots: { + root: [ + "flex items-center gap-2 rounded-2 border border-sand-a5 bg-sand-1 text-2 text-sand-12 transition-colors", + "focus-within:ring-2 focus-within:ring-sand-8 focus-within:ring-offset-1 focus-within:ring-offset-sand-1", + "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50", + ], + icon: "flex size-4 shrink-0 items-center justify-center text-sand-a9 [&_svg]:size-4", + input: "min-w-0 flex-1 bg-transparent text-sand-12 outline-none placeholder:text-sand-a9", + }, + variants: { + size: { + 1: { root: "h-7 px-2" }, + 2: { root: "h-8 px-2" }, + }, + }, + defaultVariants: { + size: 2, + }, +}); + +export const textFieldSkeleton = tv({ + base: "inline-block animate-pulse rounded-2 bg-sand-3 align-middle", + variants: { + size: { + 1: "h-7 w-60", + 2: "h-8 w-60", + }, + }, + defaultVariants: { + size: 2, + }, +});