Create InputLabel component and add RuleCard chips

This commit is contained in:
adilallo
2026-02-05 09:25:42 -07:00
parent 3e935ecd9e
commit 0dedebfaf8
12 changed files with 638 additions and 127 deletions
@@ -3,13 +3,14 @@
import { memo } from "react";
import MultiSelectView from "./MultiSelect.view";
import type { MultiSelectProps } from "./MultiSelect.types";
import { normalizeMultiSelectSize } from "../../../lib/propNormalization";
import { normalizeMultiSelectSize, normalizeChipPalette } from "../../../lib/propNormalization";
const MultiSelectContainer = memo<MultiSelectProps>(
({
label,
showHelpIcon = true,
size: sizeProp = "M",
palette: paletteProp = "Default",
options,
onChipClick,
onAddClick,
@@ -20,12 +21,14 @@ const MultiSelectContainer = memo<MultiSelectProps>(
className = "",
}) => {
const size = normalizeMultiSelectSize(sizeProp);
const palette = normalizeChipPalette(paletteProp);
return (
<MultiSelectView
label={label}
showHelpIcon={showHelpIcon}
size={size}
palette={palette}
options={options}
onChipClick={onChipClick}
onAddClick={onAddClick}
@@ -1,4 +1,4 @@
import type { ChipStateValue, ChipSizeValue } from "../../../lib/propNormalization";
import type { ChipStateValue, ChipSizeValue, ChipPaletteValue } from "../../../lib/propNormalization";
export interface ChipOption {
id: string;
@@ -22,6 +22,11 @@ export interface MultiSelectProps {
* Accepts both uppercase (Figma) and lowercase values.
*/
size?: MultiSelectSizeValue;
/**
* Palette for chips: "Default" or "Inverse"
* Accepts both PascalCase (Figma) and lowercase values.
*/
palette?: ChipPaletteValue;
/**
* Array of chip options to display
*/
@@ -57,6 +62,7 @@ export interface MultiSelectViewProps {
label?: string;
showHelpIcon: boolean;
size: "s" | "m";
palette: "default" | "inverse";
options: ChipOption[];
onChipClick?: (chipId: string) => void;
onAddClick?: () => void;
+29 -37
View File
@@ -1,14 +1,15 @@
"use client";
import { memo } from "react";
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
import Chip from "../Chip";
import InputLabel from "../InputLabel";
import type { MultiSelectViewProps } from "./MultiSelect.types";
function MultiSelectView({
label,
showHelpIcon,
size,
palette,
options,
onChipClick,
onAddClick,
@@ -19,44 +20,27 @@ function MultiSelectView({
className,
}: MultiSelectViewProps) {
const isSmall = size === "s";
const isInverse = palette === "inverse";
// Size-based spacing and typography
// Size-based spacing
const gapClass = isSmall
? "gap-[var(--measures-spacing-200,8px)]"
: "gap-[var(--measures-spacing-300,12px)]";
const labelGapClass = isSmall
? "gap-[var(--measures-spacing-200,4px_8px)]"
: "gap-[4px]";
const labelTextSize = isSmall
? "text-[length:var(--sizing-350,14px)] leading-[20px]"
: "text-[length:var(--sizing-400,16px)] leading-[24px]";
const helpIconSize = isSmall ? "size-[12px]" : "size-[16px]";
const chipSize = isSmall ? "S" : "M";
return (
<div className={`flex flex-col ${isSmall ? "gap-[var(--measures-spacing-200,8px)]" : "gap-[var(--measures-spacing-300,12px)]"} items-start relative w-full ${className}`}>
{/* Label with help icon */}
{/* Label using InputLabel component */}
{label && (
<div className={`flex flex-wrap ${labelGapClass} items-baseline ${isSmall ? "pr-[var(--measures-spacing-100,4px)]" : ""} relative shrink-0 w-full`}>
<div className={`flex ${isSmall ? "gap-[var(--measures-spacing-050,2px)]" : "gap-[var(--measures-spacing-100,4px)]"} items-center relative shrink-0`}>
<label className={`font-inter font-normal ${labelTextSize} text-[color:var(--color-content-default-primary,white)]`}>
{label}
</label>
{showHelpIcon && (
<div className={`relative shrink-0 ${helpIconSize}`}>
<img
src={getAssetPath(ASSETS.ICON_HELP)}
alt="Help"
className="block max-w-none size-full"
/>
</div>
)}
</div>
</div>
<InputLabel
label={label}
helpIcon={showHelpIcon}
asterisk={false}
helperText={false}
size={size === "s" ? "S" : "M"}
palette={palette === "inverse" ? "Inverse" : "Default"}
/>
)}
{/* Chips container */}
@@ -66,7 +50,7 @@ function MultiSelectView({
key={option.id}
label={option.state === "Custom" ? "" : option.label}
state={option.state || "Unselected"}
palette="Default"
palette={palette === "inverse" ? "Inverse" : "Default"}
size={chipSize}
onClick={() => {
// Only toggle if not in Custom state
@@ -89,7 +73,7 @@ function MultiSelectView({
/>
))}
{/* Add button - Ghost button style */}
{/* Add button - Circular button with border (not ghost) when no text, ghost style when text provided */}
{showAddButton && (
<button
type="button"
@@ -97,7 +81,13 @@ function MultiSelectView({
e.stopPropagation();
onAddClick?.();
}}
className={`flex ${isSmall ? "gap-[var(--measures-spacing-050,2px)]" : "gap-[var(--measures-spacing-150,6px)]"} items-center justify-center ${isSmall ? "p-[var(--measures-spacing-200,8px)]" : "p-[var(--measures-spacing-300,12px)]"} rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity`}
className={
!addButtonText
? // Circular button with border (RuleCard style)
`bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary,#141414)]" : "border-[var(--color-border-default-tertiary,#464646)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity`
: // Ghost button style (standalone MultiSelect)
`flex ${isSmall ? "gap-[var(--measures-spacing-050,2px)]" : "gap-[var(--measures-spacing-150,6px)]"} items-center justify-center ${isSmall ? "p-[var(--measures-spacing-200,8px)]" : "p-[var(--measures-spacing-300,12px)]"} rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity`
}
>
{/* Plus icon */}
<svg
@@ -106,7 +96,7 @@ function MultiSelectView({
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="text-[var(--color-content-default-brand-primary,#fefcc9)] shrink-0"
className={`${isInverse ? "text-[var(--color-content-inverse-primary,black)]" : "text-[var(--color-content-default-brand-primary,#fefcc9)]"} shrink-0`}
>
<path
d="M7 3V11M3 7H11"
@@ -116,10 +106,12 @@ function MultiSelectView({
strokeLinejoin="round"
/>
</svg>
{/* Text */}
<span className={`font-inter font-medium ${isSmall ? "text-[length:var(--sizing-300,12px)] leading-[14px]" : "text-[length:var(--sizing-400,16px)] leading-[20px]"} text-[color:var(--color-content-default-brand-primary,#fefcc9)]`}>
{addButtonText}
</span>
{/* Text - only show if addButtonText is provided */}
{addButtonText && (
<span className={`font-inter font-medium ${isSmall ? "text-[length:var(--sizing-300,12px)] leading-[14px]" : "text-[length:var(--sizing-400,16px)] leading-[20px]"} ${isInverse ? "text-[color:var(--color-content-inverse-primary,black)]" : "text-[color:var(--color-content-default-brand-primary,#fefcc9)]"}`}>
{addButtonText}
</span>
)}
</button>
)}
</div>