Update props in components

This commit is contained in:
adilallo
2026-02-06 17:36:12 -07:00
parent 85ff3b8f01
commit 1ca11a2229
11 changed files with 405 additions and 44 deletions
+133 -9
View File
@@ -1,19 +1,64 @@
import { memo } from "react"; import { memo } from "react";
import type { VariantValue, SizeValue } from "../../../lib/propNormalization"; import type {
import { normalizeVariant, normalizeSize } from "../../../lib/propNormalization"; VariantValue,
SizeValue,
ButtonTypeValue,
ButtonPaletteValue,
ButtonStateValue,
} from "../../../lib/propNormalization";
import {
normalizeVariant,
normalizeSize,
normalizeButtonType,
normalizeButtonPalette,
} from "../../../lib/propNormalization";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode; children: React.ReactNode;
/** /**
* @deprecated Use `type` and `palette` props instead. This prop is maintained for backward compatibility.
* Button variant. Accepts both lowercase and PascalCase (case-insensitive). * Button variant. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported. * Figma uses PascalCase, codebase uses lowercase - both are supported.
*/ */
variant?: VariantValue; variant?: VariantValue;
/**
* Button type (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
* @default "filled"
*/
buttonType?: ButtonTypeValue;
/**
* Button palette (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses "Invert", codebase uses "inverse" - both are supported.
* @default "default"
*/
palette?: ButtonPaletteValue;
/** /**
* Button size. Accepts both lowercase and PascalCase (case-insensitive). * Button size. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported. * Figma uses PascalCase, codebase uses lowercase - both are supported.
* @default "xsmall"
*/ */
size?: SizeValue; size?: SizeValue;
/**
* Button state (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
* @default "default"
*/
state?: ButtonStateValue;
/**
* Whether to show a leading icon (Figma prop).
* @default false
*/
hasIconLeading?: boolean;
/**
* Whether to show a following icon (Figma prop).
* @default false
*/
hasIconFollowing?: boolean;
/**
* Whether to show text (Figma prop).
* @default true
*/
hasText?: boolean;
className?: string; className?: string;
disabled?: boolean; disabled?: boolean;
type?: "button" | "submit" | "reset"; type?: "button" | "submit" | "reset";
@@ -29,11 +74,17 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
const Button = memo<ButtonProps>( const Button = memo<ButtonProps>(
({ ({
children, children,
variant: variantProp = "filled", variant: variantProp,
buttonType: typeProp,
palette: paletteProp,
size: sizeProp = "xsmall", size: sizeProp = "xsmall",
state: _stateProp,
hasIconLeading = false,
hasIconFollowing = false,
hasText = true,
className = "", className = "",
disabled = false, disabled = false,
type = "button", type: htmlType = "button",
onClick, onClick,
href, href,
target, target,
@@ -41,9 +92,68 @@ const Button = memo<ButtonProps>(
ariaLabel, ariaLabel,
...props ...props
}) => { }) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase) // Determine type and palette from either new props or legacy variant prop
const variant = normalizeVariant(variantProp); let buttonType: "filled" | "outline" | "ghost" | "danger";
let buttonPalette: "default" | "inverse";
if (variantProp) {
// Backward compatibility: map old variant to new type/palette
const variant = normalizeVariant(variantProp);
if (variant === "filled") {
buttonType = "filled";
buttonPalette = "default";
} else if (variant === "filled-inverse") {
buttonType = "filled";
buttonPalette = "inverse";
} else if (variant === "outline") {
buttonType = "outline";
buttonPalette = "default";
} else if (variant === "outline-inverse") {
buttonType = "outline";
buttonPalette = "inverse";
} else if (variant === "ghost") {
buttonType = "ghost";
buttonPalette = "default";
} else if (variant === "ghost-inverse") {
buttonType = "ghost";
buttonPalette = "inverse";
} else if (variant === "danger") {
buttonType = "danger";
buttonPalette = "default";
} else {
// danger-inverse
buttonType = "danger";
buttonPalette = "inverse";
}
} else {
// Use new type/palette props
buttonType = normalizeButtonType(typeProp, "filled");
buttonPalette = normalizeButtonPalette(paletteProp, "default");
}
// Normalize other props
const size = normalizeSize(sizeProp); const size = normalizeSize(sizeProp);
// State prop is for Figma alignment - actual state is handled by CSS pseudo-classes
// We accept it for API alignment but don't use it for styling (CSS handles states)
// Map type + palette to legacy variant for styling (maintains existing styles)
const getVariantFromTypeAndPalette = (
type: typeof buttonType,
palette: typeof buttonPalette,
): string => {
if (type === "filled" && palette === "default") return "filled";
if (type === "filled" && palette === "inverse") return "filled-inverse";
if (type === "outline" && palette === "default") return "outline";
if (type === "outline" && palette === "inverse") return "outline-inverse";
if (type === "ghost" && palette === "default") return "ghost";
if (type === "ghost" && palette === "inverse") return "ghost-inverse";
if (type === "danger" && palette === "default") return "danger";
// danger + inverse
return "danger-inverse";
};
const variant = getVariantFromTypeAndPalette(buttonType, buttonPalette);
const sizeStyles: Record<string, string> = { const sizeStyles: Record<string, string> = {
xsmall: xsmall:
"p-[var(--spacing-scale-004)] gap-[var(--spacing-scale-002)]", "p-[var(--spacing-scale-004)] gap-[var(--spacing-scale-002)]",
@@ -102,6 +212,10 @@ const Button = memo<ButtonProps>(
? "" ? ""
: hoverOutlineStyles[size]; : hoverOutlineStyles[size];
// Apply state-based styles if state prop is provided (overrides default hover/focus/active)
// Note: State prop is informational for Figma alignment - actual state is handled by CSS pseudo-classes
// For now, we maintain existing behavior and state prop is for documentation/alignment purposes
const baseStyles = `inline-flex items-center justify-start box-border whitespace-nowrap shrink-0 ${sizeStyles[size]} rounded-[var(--radius-measures-radius-full)] ${fontStyles[size]} transition-all duration-500 ease-in-out cursor-pointer ${variantStyles[variant]} ${outlineStyles}`; const baseStyles = `inline-flex items-center justify-start box-border whitespace-nowrap shrink-0 ${sizeStyles[size]} rounded-[var(--radius-measures-radius-full)] ${fontStyles[size]} transition-all duration-500 ease-in-out cursor-pointer ${variantStyles[variant]} ${outlineStyles}`;
const combinedStyles = `${baseStyles} ${className}`; const combinedStyles = `${baseStyles} ${className}`;
@@ -111,6 +225,16 @@ const Button = memo<ButtonProps>(
tabIndex: disabled ? -1 : 0, tabIndex: disabled ? -1 : 0,
}; };
// Filter children based on hasIconLeading, hasIconFollowing, hasText props
// For now, we render all children but these props are available for future icon support
const renderContent = () => {
if (!hasText && !hasIconLeading && !hasIconFollowing) {
return children; // If all are false, render children as-is (backward compatibility)
}
// TODO: When icon support is added, filter children based on these props
return children;
};
if (href && !disabled) { if (href && !disabled) {
const anchorProps: React.AnchorHTMLAttributes<HTMLAnchorElement> = { const anchorProps: React.AnchorHTMLAttributes<HTMLAnchorElement> = {
href, href,
@@ -121,11 +245,11 @@ const Button = memo<ButtonProps>(
...(rel && { rel }), ...(rel && { rel }),
}; };
return <a {...anchorProps}>{children}</a>; return <a {...anchorProps}>{renderContent()}</a>;
} }
const buttonProps: React.ButtonHTMLAttributes<HTMLButtonElement> = { const buttonProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {
type, type: htmlType,
className: combinedStyles, className: combinedStyles,
disabled, disabled,
onClick, onClick,
@@ -133,7 +257,7 @@ const Button = memo<ButtonProps>(
...props, ...props,
}; };
return <button {...buttonProps}>{children}</button>; return <button {...buttonProps}>{renderContent()}</button>;
}, },
); );
@@ -22,10 +22,18 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
( (
{ {
id, id,
label, label: labelProp,
labelText,
showLabel,
labelVariant: labelVariantProp, labelVariant: labelVariantProp,
size: sizeProp, size: sizeProp,
state: externalStateProp = "default", state: externalStateProp = "default",
asterisk = false,
iconHelp = true,
textOptional = false,
textData = true,
iconRight = true,
textHint = false,
disabled = false, disabled = false,
error = false, error = false,
placeholder = "Choose an option", placeholder = "Choose an option",
@@ -38,6 +46,17 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
}, },
ref, ref,
) => { ) => {
// Handle backward compatibility: if label is string, use it as labelText
const actualLabelText = labelText || labelProp;
const shouldShowLabel = showLabel !== undefined ? showLabel : (actualLabelText !== undefined);
// Normalize state - handle "state5" as disabled
let normalizedState = externalStateProp;
if (normalizedState === "state5" || normalizedState === "State5") {
normalizedState = "default"; // Map to default, disabled prop handles the disabled state
}
const externalState = normalizeState(normalizedState);
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase) // Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
// Note: labelVariant and size are normalized for future use but not yet implemented in the view // Note: labelVariant and size are normalized for future use but not yet implemented in the view
const _labelVariant = labelVariantProp ? normalizeLabelVariant(labelVariantProp) : undefined; const _labelVariant = labelVariantProp ? normalizeLabelVariant(labelVariantProp) : undefined;
@@ -45,7 +64,6 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
// Mark as intentionally unused for future implementation // Mark as intentionally unused for future implementation
void _labelVariant; void _labelVariant;
void _size; void _size;
const externalState = normalizeState(externalStateProp);
const generatedId = useId(); const generatedId = useId();
const selectId = id || `select-input-${generatedId}`; const selectId = id || `select-input-${generatedId}`;
@@ -193,7 +211,7 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
return ( return (
<SelectInputView <SelectInputView
label={label} label={shouldShowLabel ? actualLabelText : undefined}
placeholder={placeholder} placeholder={placeholder}
state={actualState} state={actualState}
disabled={disabled} disabled={disabled}
@@ -214,8 +232,14 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
onOptionClick={handleOptionSelect} onOptionClick={handleOptionSelect}
selectRef={selectRef} selectRef={selectRef}
menuRef={menuRef} menuRef={menuRef}
ariaLabelledby={label ? labelId : undefined} ariaLabelledby={shouldShowLabel ? labelId : undefined}
ariaInvalid={error} ariaInvalid={error}
asterisk={asterisk}
iconHelp={iconHelp}
textOptional={textOptional}
textData={textData}
iconRight={iconRight}
textHint={textHint}
{...props} {...props}
/> />
); );
@@ -12,7 +12,21 @@ export type SelectInputSizeValue = "small" | "medium" | "large" | "Small" | "Med
export interface SelectInputProps { export interface SelectInputProps {
id?: string; id?: string;
/**
* Label text (backward compatibility - if provided, label is shown).
* For Figma alignment, use `labelText` prop instead.
*/
label?: string; label?: string;
/**
* Label text (Figma prop - use this for new code).
*/
labelText?: string;
/**
* Whether to show label above input (Figma prop).
* If `label` or `labelText` is provided, defaults to true.
* @default true
*/
showLabel?: boolean;
/** /**
* Label variant. Accepts both lowercase and PascalCase (case-insensitive). * Label variant. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported. * Figma uses PascalCase, codebase uses lowercase - both are supported.
@@ -24,10 +38,40 @@ export interface SelectInputProps {
*/ */
size?: SelectInputSizeValue; size?: SelectInputSizeValue;
/** /**
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive). * Visual state. Accepts "default"/"Default", "active"/"Active", "focus"/"Focus", "error"/"Error", "state5"/"State5" (State5 = Disabled).
* Figma uses PascalCase, codebase uses lowercase - both are supported. * Figma uses PascalCase, codebase uses lowercase - both are supported.
*/ */
state?: StateValue; state?: StateValue | "state5" | "State5";
/**
* Whether to show asterisk (*) in label (Figma prop).
* @default false
*/
asterisk?: boolean;
/**
* Whether to show help icon in label (Figma prop).
* @default true
*/
iconHelp?: boolean;
/**
* Whether to show "Optional" text in label (Figma prop).
* @default false
*/
textOptional?: boolean;
/**
* Whether to show data text (placeholder/entered text) - internal, always true (Figma prop).
* @default true
*/
textData?: boolean;
/**
* Whether to show dropdown icon on the right (Figma prop).
* @default true
*/
iconRight?: boolean;
/**
* Whether to show hint text below input (Figma prop).
* @default false
*/
textHint?: boolean;
disabled?: boolean; disabled?: boolean;
error?: boolean; error?: boolean;
placeholder?: string; placeholder?: string;
@@ -33,6 +33,13 @@ export interface SelectInputViewProps {
// Additional props // Additional props
ariaLabelledby?: string; ariaLabelledby?: string;
ariaInvalid?: boolean; ariaInvalid?: boolean;
// Figma props
asterisk?: boolean;
iconHelp?: boolean;
textOptional?: boolean;
textData?: boolean;
iconRight?: boolean;
textHint?: boolean;
} }
export function SelectInputView({ export function SelectInputView({
@@ -59,6 +66,12 @@ export function SelectInputView({
menuRef, menuRef,
ariaLabelledby, ariaLabelledby,
ariaInvalid, ariaInvalid,
asterisk = false,
iconHelp = true,
textOptional = false,
textData = true,
iconRight = true,
textHint = false,
}: SelectInputViewProps) { }: SelectInputViewProps) {
// Styles based on Figma design // Styles based on Figma design
const containerClasses = "flex flex-col gap-[8px]"; const containerClasses = "flex flex-col gap-[8px]";
@@ -135,14 +148,26 @@ export function SelectInputView({
> >
{label} {label}
</label> </label>
<div className="relative shrink-0 size-[12px]"> {asterisk && (
<img <span className="text-[var(--color-content-default-negative-primary,#ea4845)] text-[10px] leading-[12px] font-medium">
src={getAssetPath(ASSETS.ICON_HELP)} *
alt="Help" </span>
className="block max-w-none size-full" )}
/> {iconHelp && (
</div> <div className="relative shrink-0 size-[12px]">
<img
src={getAssetPath(ASSETS.ICON_HELP)}
alt="Help"
className="block max-w-none size-full"
/>
</div>
)}
</div> </div>
{textOptional && (
<span className="text-[var(--color-content-default-tertiary,#b4b4b4)] text-[10px] leading-[14px] font-normal">
Optional text
</span>
)}
</div> </div>
)} )}
<div className="relative"> <div className="relative">
@@ -161,24 +186,26 @@ export function SelectInputView({
onFocus={onButtonFocus} onFocus={onButtonFocus}
onBlur={onButtonBlur} onBlur={onButtonBlur}
> >
<span className={`flex-1 text-left pr-[32px] ${textColorClass}`}> <span className={`flex-1 text-left ${iconRight ? "pr-[32px]" : ""} ${textColorClass}`}>
{displayText} {textData ? displayText : placeholder}
</span> </span>
<div className="flex items-center justify-center shrink-0"> {iconRight && (
<svg <div className="flex items-center justify-center shrink-0">
className={chevronClasses} <svg
fill="none" className={chevronClasses}
stroke="currentColor" fill="none"
viewBox="0 0 24 24" stroke="currentColor"
> viewBox="0 0 24 24"
<path >
strokeLinecap="round" <path
strokeLinejoin="round" strokeLinecap="round"
strokeWidth={2} strokeLinejoin="round"
d="M19 9l-7 7-7-7" strokeWidth={2}
/> d="M19 9l-7 7-7-7"
</svg> />
</div> </svg>
</div>
)}
</button> </button>
{state === "focus" && ( {state === "focus" && (
<div <div
@@ -235,6 +262,13 @@ export function SelectInputView({
</div> </div>
)} )}
</div> </div>
{textHint && (
<div className="flex items-start relative shrink-0 w-full">
<p className="flex-[1_0_0] font-inter font-normal leading-[16px] min-h-px min-w-px relative text-[color:var(--color-content-default-tertiary,#b4b4b4)] text-[length:var(--sizing-300,12px)]">
Hint text here
</p>
</div>
)}
</div> </div>
); );
} }
@@ -23,6 +23,8 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
type = "text", type = "text",
className = "", className = "",
showHelpIcon = true, showHelpIcon = true,
textHint = false,
formHeader = true,
...props ...props
}, },
ref, ref,
@@ -220,6 +222,8 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
isFilled={isFilled} isFilled={isFilled}
inputWrapperClasses={stateStyles.inputWrapper} inputWrapperClasses={stateStyles.inputWrapper}
focusRingClasses={stateStyles.focusRing} focusRingClasses={stateStyles.focusRing}
textHint={textHint}
formHeader={formHeader}
{...props} {...props}
/> />
); );
@@ -19,6 +19,16 @@ export interface TextInputProps extends Omit<
onBlur?: (_e: React.FocusEvent<HTMLInputElement>) => void; onBlur?: (_e: React.FocusEvent<HTMLInputElement>) => void;
className?: string; className?: string;
showHelpIcon?: boolean; showHelpIcon?: boolean;
/**
* Whether to show hint text below input (Figma prop).
* @default false
*/
textHint?: boolean;
/**
* Whether to show form header (label and help icon) above input (Figma prop).
* @default true
*/
formHeader?: boolean;
} }
export interface TextInputViewProps { export interface TextInputViewProps {
@@ -45,4 +55,6 @@ export interface TextInputViewProps {
isFilled?: boolean; isFilled?: boolean;
inputWrapperClasses?: string; inputWrapperClasses?: string;
focusRingClasses?: string; focusRingClasses?: string;
textHint?: boolean;
formHeader?: boolean;
} }
@@ -26,12 +26,14 @@ export const TextInputView = forwardRef<HTMLInputElement, TextInputViewProps>(
showHelpIcon = true, showHelpIcon = true,
inputWrapperClasses = "relative", inputWrapperClasses = "relative",
focusRingClasses = "", focusRingClasses = "",
textHint = false,
formHeader = true,
}, },
ref, ref,
) => { ) => {
return ( return (
<div className={containerClasses}> <div className={containerClasses}>
{label && ( {formHeader && label && (
<div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full"> <div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full">
<div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0"> <div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0">
<label <label
@@ -75,6 +77,13 @@ export const TextInputView = forwardRef<HTMLInputElement, TextInputViewProps>(
<div className={focusRingClasses} aria-hidden="true" /> <div className={focusRingClasses} aria-hidden="true" />
)} )}
</div> </div>
{textHint && (
<div className="flex items-start relative shrink-0 w-full">
<p className="flex-[1_0_0] font-inter font-normal leading-[16px] min-h-px min-w-px relative text-[color:var(--color-content-default-tertiary,#b4b4b4)] text-[length:var(--sizing-300,12px)]">
Hint text here
</p>
</div>
)}
</div> </div>
); );
}, },
@@ -11,6 +11,8 @@ const AlertContainer = memo<AlertProps>(
description, description,
status: statusProp = "default", status: statusProp = "default",
type: typeProp = "toast", type: typeProp = "toast",
hasLeadingIcon = true,
hasBodyText = true,
onClose, onClose,
className = "", className = "",
}) => { }) => {
@@ -104,6 +106,8 @@ const AlertContainer = memo<AlertProps>(
description={description} description={description}
status={status} status={status}
type={type} type={type}
hasLeadingIcon={hasLeadingIcon}
hasBodyText={hasBodyText}
className={className} className={className}
containerClasses={containerClasses} containerClasses={containerClasses}
containerStyle={containerStyle} containerStyle={containerStyle}
@@ -23,6 +23,16 @@ export interface AlertProps {
* Figma uses PascalCase, codebase uses lowercase - both are supported. * Figma uses PascalCase, codebase uses lowercase - both are supported.
*/ */
type?: AlertTypeValue; type?: AlertTypeValue;
/**
* Whether to show the leading icon (Figma prop).
* @default true
*/
hasLeadingIcon?: boolean;
/**
* Whether to show body text/description (Figma prop).
* @default true
*/
hasBodyText?: boolean;
onClose?: () => void; onClose?: () => void;
className?: string; className?: string;
} }
@@ -32,6 +42,8 @@ export interface AlertViewProps {
description?: string; description?: string;
status: "default" | "positive" | "warning" | "danger"; status: "default" | "positive" | "warning" | "danger";
type: "toast" | "banner"; type: "toast" | "banner";
hasLeadingIcon: boolean;
hasBodyText: boolean;
className: string; className: string;
containerClasses: string; containerClasses: string;
containerStyle?: React.CSSProperties; containerStyle?: React.CSSProperties;
+10 -4
View File
@@ -6,6 +6,8 @@ export function AlertView({
description, description,
status: _status, status: _status,
type: _type, type: _type,
hasLeadingIcon,
hasBodyText,
className, className,
containerClasses, containerClasses,
containerStyle, containerStyle,
@@ -41,12 +43,16 @@ export function AlertView({
style={containerStyle} style={containerStyle}
role="alert" role="alert"
> >
<div className="shrink-0 w-[24px] h-[24px] flex items-center justify-center"> {hasLeadingIcon && (
{getIcon()} <div className="shrink-0 w-[24px] h-[24px] flex items-center justify-center">
</div> {getIcon()}
</div>
)}
<div className="flex flex-1 flex-col items-start justify-center min-h-0 min-w-0"> <div className="flex flex-1 flex-col items-start justify-center min-h-0 min-w-0">
<p className={titleClasses}>{title}</p> <p className={titleClasses}>{title}</p>
{description && <p className={descriptionClasses}>{description}</p>} {hasBodyText && description && (
<p className={descriptionClasses}>{description}</p>
)}
</div> </div>
<Button <Button
variant="ghost" variant="ghost"
+88
View File
@@ -692,3 +692,91 @@ export function normalizeMenuBarItemSize(
return defaultValue; return defaultValue;
} }
/**
* Normalize button type prop values (Filled/Outline/Ghost/Danger -> filled/outline/ghost/danger)
*/
export function normalizeButtonType(
value: string | undefined,
defaultValue: "filled" = "filled"
): "filled" | "outline" | "ghost" | "danger" {
if (!value) return defaultValue;
const normalized = value.toLowerCase();
const types = ["filled", "outline", "ghost", "danger"];
if (types.includes(normalized)) {
return normalized as typeof defaultValue;
}
return defaultValue;
}
/**
* Normalize button palette prop values (Default/Invert -> default/inverse)
*/
export function normalizeButtonPalette(
value: string | undefined,
defaultValue: "default" = "default"
): "default" | "inverse" {
if (!value) return defaultValue;
const normalized = value.toLowerCase();
// Handle "invert" -> "inverse" mapping
if (normalized === "invert" || normalized === "inverse") {
return "inverse";
}
if (normalized === "default") {
return "default";
}
return defaultValue;
}
/**
* Normalize button state prop values (Default/Focus/Active/Hover/Disabled -> default/focus/active/hover/disabled)
*/
export function normalizeButtonState(
value: string | undefined,
defaultValue: "default" = "default"
): "default" | "focus" | "active" | "hover" | "disabled" {
if (!value) return defaultValue;
const normalized = value.toLowerCase();
const states = ["default", "focus", "active", "hover", "disabled"];
if (states.includes(normalized)) {
return normalized as typeof defaultValue;
}
return defaultValue;
}
/**
* Type helper for case-insensitive button type prop
*/
export type ButtonTypeValue =
| "filled"
| "outline"
| "ghost"
| "danger"
| "Filled"
| "Outline"
| "Ghost"
| "Danger";
/**
* Type helper for case-insensitive button palette prop
*/
export type ButtonPaletteValue =
| "default"
| "inverse"
| "Default"
| "Invert"
| "Inverse";
/**
* Type helper for case-insensitive button state prop
*/
export type ButtonStateValue =
| "default"
| "focus"
| "active"
| "hover"
| "disabled"
| "Default"
| "Focus"
| "Active"
| "Hover"
| "Disabled";