diff --git a/app/components/controls/Chip/Chip.view.tsx b/app/components/controls/Chip/Chip.view.tsx index 378ad54..7b5f7c8 100644 --- a/app/components/controls/Chip/Chip.view.tsx +++ b/app/components/controls/Chip/Chip.view.tsx @@ -1,8 +1,81 @@ "use client"; -import { memo } from "react"; +import { memo, type CSSProperties } from "react"; import type { ChipViewProps } from "./Chip.types"; +function chipSurfaceStyle( + state: ChipViewProps["state"], + palette: ChipViewProps["palette"], + size: ChipViewProps["size"], +): CSSProperties { + const borderWidth = size === "s" ? "1.25px" : "2px"; + const reset: CSSProperties = { + appearance: "none", + WebkitAppearance: "none", + backgroundImage: "none", + }; + + const paint = ( + backgroundColor: string, + color: string, + borderColor?: string, + ): CSSProperties => ({ + ...reset, + backgroundColor, + color, + WebkitTextFillColor: color, + ...(borderColor + ? { borderWidth, borderStyle: "solid", borderColor } + : { borderWidth: 0, borderStyle: "none", borderColor: "transparent" }), + }); + + if (palette === "inverse") { + if (state === "disabled") { + return paint( + "var(--color-surface-inverse-tertiary)", + "var(--color-content-inverse-primary)", + ); + } + if (state === "selected") { + return paint( + "var(--color-surface-default-semi-opaque)", + "var(--color-content-inverse-primary)", + "var(--color-border-default-primary)", + ); + } + return paint( + "transparent", + "var(--color-content-inverse-primary)", + "var(--color-border-default-primary)", + ); + } + + if (state === "custom") { + return paint( + "var(--color-surface-default-secondary)", + "var(--color-content-default-tertiary)", + ); + } + if (state === "disabled") { + return paint( + "var(--color-surface-default-secondary)", + "var(--color-content-invert-tertiary)", + ); + } + if (state === "selected") { + return paint( + "var(--color-surface-invert-brand-primary, #fefcc9)", + "var(--color-content-invert-primary, #000)", + "var(--color-border-default-brand-primary, #fdfaa8)", + ); + } + return paint( + "transparent", + "var(--color-content-default-brand-primary, #fefcc9)", + "var(--color-border-default-tertiary, #464646)", + ); +} + function ChipView({ label, state, @@ -30,10 +103,8 @@ function ChipView({ const isCustom = state === "custom"; const isToggle = !isCustom; - const isInverse = palette === "inverse"; - const isDefault = palette === "default"; - const isSmall = size === "s"; + const surfaceStyle = chipSurfaceStyle(state, palette, size); // Size-based styles from Figma tokens // Custom state has different padding @@ -45,59 +116,8 @@ function ChipView({ ? "h-[30px] px-[var(--measures-spacing-200,8px)] gap-[var(--measures-spacing-050,2px)] text-x-small-label" : "px-[var(--measures-spacing-300,12px)] py-[var(--measures-spacing-300,12px)] gap-[var(--measures-spacing-150,6px)] text-medium-label"; - // Palette + state styling based on Figma examples - // Use consistent border width to prevent layout shift - const borderWidth = isSmall ? "border-[1.25px]" : "border-2"; - - let background = - "bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))]"; - let border = `${borderWidth} border-[var(--color-border-default-tertiary,#464646)]`; - let textColor = - "text-[color:var(--color-content-default-brand-primary,#fefcc9)]"; - - if (isDefault) { - if (state === "custom") { - background = "bg-[var(--color-surface-default-secondary,#141414)]"; // dark background for custom - border = "border-none"; - textColor = "text-[color:var(--color-content-default-tertiary,#b4b4b4)]"; - } else if (state === "disabled") { - background = "bg-[var(--color-surface-default-secondary,#141414)]"; // dark background - border = "border-none"; - // Per Figma (node 19839:13842) disabled uses invert-tertiary for the - // strongly dimmed look, not default-tertiary. - textColor = "text-[color:var(--color-content-invert-tertiary,#2d2d2d)]"; - } else if (isSelected) { - background = "bg-[var(--color-surface-invert-brand-primary,#fefcc9)]"; // yellow selected - border = `${borderWidth} border-[var(--color-border-default-brand-primary,#fdfaa8)]`; - textColor = "text-[color:var(--color-content-invert-primary,black)]"; - } else { - // Unselected default - background = - "bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))]"; - border = `${borderWidth} border-[var(--color-border-default-tertiary,#464646)]`; - textColor = - "text-[color:var(--color-content-default-brand-primary,#fefcc9)]"; - } - } else if (isInverse) { - if (state === "disabled") { - background = "bg-[var(--color-surface-inverse-tertiary,#d2d2d2)]"; - border = "border-none"; - textColor = "text-[color:var(--color-content-inverse-primary,black)]"; - } else if (isSelected) { - background = - "bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.05))]"; - border = `${borderWidth} border-[var(--color-border-default-primary,#141414)]`; - textColor = "text-[color:var(--color-content-inverse-primary,black)]"; - } else { - // Unselected / custom inverse - background = - "bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))]"; - border = `${borderWidth} border-[var(--color-border-default-primary,#141414)]`; - textColor = "text-[color:var(--color-content-inverse-primary,black)]"; - } - } - const baseClasses = ` + appearance-none inline-flex max-w-full items-center @@ -107,7 +127,7 @@ function ChipView({ box-border focus:outline-none focus-visible:ring-2 - focus-visible:ring-[var(--color-border-default-primary,#141414)] + focus-visible:ring-[var(--color-border-default-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-transparent transition-[background,border-color,color,box-shadow] @@ -121,15 +141,7 @@ function ChipView({ ? "cursor-not-allowed opacity-60" : "cursor-pointer"; - const combinedClasses = [ - baseClasses, - sizeClasses, - background, - border, - textColor, - stateClasses, - className, - ] + const combinedClasses = [baseClasses, sizeClasses, stateClasses, className] .filter(Boolean) .join(" "); @@ -153,6 +165,7 @@ function ChipView({ return (
) : null} - {label} + + {label} + {onRemove && !isDisabled && (