Align Prop Names #38
@@ -85,3 +85,6 @@ act_runner
|
||||
# OS files
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# Cursor rules (local development)
|
||||
.cursorrules
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
import { memo } from "react";
|
||||
import { AlertView } from "./Alert.view";
|
||||
import type { AlertProps } from "./Alert.types";
|
||||
import { normalizeAlertStatus, normalizeAlertType } from "../../../lib/propNormalization";
|
||||
|
||||
const AlertContainer = memo<AlertProps>(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
status = "default",
|
||||
type = "toast",
|
||||
status: statusProp = "default",
|
||||
type: typeProp = "toast",
|
||||
onClose,
|
||||
className = "",
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const status = normalizeAlertStatus(statusProp);
|
||||
const type = normalizeAlertType(typeProp);
|
||||
// Determine background and border colors based on status and type
|
||||
const getStatusStyles = () => {
|
||||
switch (status) {
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
export type AlertStatusValue =
|
||||
| "default"
|
||||
| "positive"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "Default"
|
||||
| "Positive"
|
||||
| "Warning"
|
||||
| "Danger";
|
||||
|
||||
export type AlertTypeValue = "toast" | "banner" | "Toast" | "Banner";
|
||||
|
||||
export interface AlertProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
status?: "default" | "positive" | "warning" | "danger";
|
||||
type?: "toast" | "banner";
|
||||
/**
|
||||
* Alert status. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
status?: AlertStatusValue;
|
||||
/**
|
||||
* Alert type. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
type?: AlertTypeValue;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
AskOrganizerProps,
|
||||
AskOrganizerVariant,
|
||||
} from "./AskOrganizer.types";
|
||||
import { normalizeAskOrganizerVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const VARIANT_STYLES: Record<
|
||||
AskOrganizerVariant,
|
||||
@@ -39,9 +40,11 @@ const AskOrganizerContainer = memo<AskOrganizerProps>(
|
||||
buttonText,
|
||||
buttonHref,
|
||||
className = "",
|
||||
variant = "centered",
|
||||
variant: variantProp = "centered",
|
||||
onContactClick,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeAskOrganizerVariant(variantProp) as AskOrganizerVariant;
|
||||
const t = useTranslation();
|
||||
const defaultButtonText = buttonText ?? t("askOrganizer.buttonText");
|
||||
const defaultButtonHref = buttonHref ?? t("askOrganizer.buttonHref");
|
||||
|
||||
@@ -4,7 +4,11 @@ export type AskOrganizerVariant =
|
||||
| "centered"
|
||||
| "left-aligned"
|
||||
| "compact"
|
||||
| "inverse";
|
||||
| "inverse"
|
||||
| "Centered"
|
||||
| "Left-Aligned"
|
||||
| "Compact"
|
||||
| "Inverse";
|
||||
|
||||
export interface AskOrganizerProps {
|
||||
title?: string;
|
||||
@@ -13,6 +17,10 @@ export interface AskOrganizerProps {
|
||||
buttonText?: string;
|
||||
buttonHref?: string;
|
||||
className?: string;
|
||||
/**
|
||||
* Ask organizer variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: AskOrganizerVariant;
|
||||
onContactClick?: (_data: {
|
||||
event: string;
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { memo } from "react";
|
||||
import { normalizeSize } from "../../lib/propNormalization";
|
||||
|
||||
export type AvatarSizeValue = "small" | "medium" | "large" | "xlarge" | "Small" | "Medium" | "Large" | "XLarge";
|
||||
|
||||
interface AvatarProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
src: string;
|
||||
alt: string;
|
||||
size?: "small" | "medium" | "large" | "xlarge";
|
||||
/**
|
||||
* Avatar size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: AvatarSizeValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Avatar = memo<AvatarProps>(
|
||||
({ src, alt, size = "small", className = "", ...props }) => {
|
||||
({ src, alt, size: sizeProp = "small", className = "", ...props }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSize(sizeProp, "small");
|
||||
const sizeStyles: Record<string, string> = {
|
||||
small: "w-[var(--spacing-scale-016)] h-[var(--spacing-scale-016)]",
|
||||
medium: "w-[18px] h-[18px]",
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { memo } from "react";
|
||||
import { normalizeSize } from "../../lib/propNormalization";
|
||||
|
||||
export type AvatarContainerSizeValue = "small" | "medium" | "large" | "xlarge" | "Small" | "Medium" | "Large" | "XLarge";
|
||||
|
||||
interface AvatarContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
size?: "small" | "medium" | "large" | "xlarge";
|
||||
/**
|
||||
* Avatar container size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: AvatarContainerSizeValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AvatarContainer = memo<AvatarContainerProps>(
|
||||
({ children, size = "small", className = "", ...props }) => {
|
||||
({ children, size: sizeProp = "small", className = "", ...props }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSize(sizeProp, "small");
|
||||
const sizeStyles: Record<string, string> = {
|
||||
small: "flex -space-x-[var(--spacing-scale-008)]",
|
||||
medium: "flex -space-x-[9px]",
|
||||
|
||||
+17
-12
@@ -1,17 +1,19 @@
|
||||
import { memo } from "react";
|
||||
import type { VariantValue, SizeValue } from "../../lib/propNormalization";
|
||||
import { normalizeVariant, normalizeSize } from "../../lib/propNormalization";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
variant?:
|
||||
| "filled"
|
||||
| "filled-inverse"
|
||||
| "outline"
|
||||
| "outline-inverse"
|
||||
| "ghost"
|
||||
| "ghost-inverse"
|
||||
| "danger"
|
||||
| "danger-inverse";
|
||||
size?: "xsmall" | "small" | "medium" | "large" | "xlarge";
|
||||
/**
|
||||
* Button variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: VariantValue;
|
||||
/**
|
||||
* Button size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: SizeValue;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
type?: "button" | "submit" | "reset";
|
||||
@@ -27,8 +29,8 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
const Button = memo<ButtonProps>(
|
||||
({
|
||||
children,
|
||||
variant = "filled",
|
||||
size = "xsmall",
|
||||
variant: variantProp = "filled",
|
||||
size: sizeProp = "xsmall",
|
||||
className = "",
|
||||
disabled = false,
|
||||
type = "button",
|
||||
@@ -39,6 +41,9 @@ const Button = memo<ButtonProps>(
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeVariant(variantProp);
|
||||
const size = normalizeSize(sizeProp);
|
||||
const sizeStyles: Record<string, string> = {
|
||||
xsmall:
|
||||
"p-[var(--spacing-scale-006)] gap-[var(--spacing-scale-002)]",
|
||||
|
||||
@@ -4,12 +4,13 @@ import { memo } from "react";
|
||||
import { useComponentId } from "../../hooks";
|
||||
import { CheckboxView } from "./Checkbox.view";
|
||||
import type { CheckboxProps } from "./Checkbox.types";
|
||||
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||
|
||||
const CheckboxContainer = memo<CheckboxProps>(
|
||||
({
|
||||
checked = false,
|
||||
mode = "standard",
|
||||
state = "default",
|
||||
mode: modeProp = "standard",
|
||||
state: stateProp = "default",
|
||||
disabled = false,
|
||||
label,
|
||||
className = "",
|
||||
@@ -20,6 +21,10 @@ const CheckboxContainer = memo<CheckboxProps>(
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
const state = normalizeState(stateProp);
|
||||
|
||||
const isInverse = mode === "inverse";
|
||||
const isStandard = mode === "standard";
|
||||
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface CheckboxProps {
|
||||
checked?: boolean;
|
||||
mode?: "standard" | "inverse";
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
className?: string;
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
import { memo, useCallback, useId, useState } from "react";
|
||||
import { CheckboxGroupView } from "./CheckboxGroup.view";
|
||||
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
|
||||
import { normalizeMode } from "../../../lib/propNormalization";
|
||||
|
||||
const CheckboxGroupContainer = ({
|
||||
name,
|
||||
value,
|
||||
onChange,
|
||||
mode = "standard",
|
||||
mode: modeProp = "standard",
|
||||
disabled = false,
|
||||
options = [],
|
||||
className = "",
|
||||
...props
|
||||
}: CheckboxGroupProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const generatedId = useId();
|
||||
const groupId = name || `checkbox-group-${generatedId}`;
|
||||
|
||||
@@ -5,11 +5,17 @@ export interface CheckboxOption {
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
import type { ModeValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface CheckboxGroupProps {
|
||||
name?: string;
|
||||
value?: string[];
|
||||
onChange?: (_data: { value: string[] }) => void;
|
||||
mode?: "standard" | "inverse";
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
disabled?: boolean;
|
||||
options?: CheckboxOption[];
|
||||
className?: string;
|
||||
|
||||
@@ -4,9 +4,12 @@ import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
||||
import ContentContainerView from "./ContentContainer.view";
|
||||
import type { ContentContainerProps } from "./ContentContainer.types";
|
||||
import { normalizeContentContainerSize } from "../../../lib/propNormalization";
|
||||
|
||||
const ContentContainerContainer = memo<ContentContainerProps>(
|
||||
({ post, width = "200px", size = "responsive" }) => {
|
||||
({ post, width = "200px", size: sizeProp = "responsive" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContentContainerSize(sizeProp);
|
||||
// Get the corresponding icon based on the same logic as background images
|
||||
const getIconImage = (slug: string): string => {
|
||||
const icons = [
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { BlogPost } from "../../../lib/content";
|
||||
|
||||
export type ContentContainerSizeValue = "xs" | "responsive" | "Xs" | "Responsive";
|
||||
|
||||
export interface ContentContainerProps {
|
||||
post: BlogPost;
|
||||
width?: string;
|
||||
size?: "xs" | "responsive";
|
||||
/**
|
||||
* Content container size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: ContentContainerSizeValue;
|
||||
}
|
||||
|
||||
export interface ContentContainerViewProps {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { memo } from "react";
|
||||
import ContentLockupView from "./ContentLockup.view";
|
||||
import type { ContentLockupProps, VariantStyle } from "./ContentLockup.types";
|
||||
import { normalizeContentLockupVariant, normalizeAlignment } from "../../../lib/propNormalization";
|
||||
|
||||
const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
({
|
||||
@@ -11,12 +12,15 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
description,
|
||||
ctaText,
|
||||
buttonClassName = "",
|
||||
variant = "hero",
|
||||
variant: variantProp = "hero",
|
||||
linkText,
|
||||
linkHref,
|
||||
alignment = "center",
|
||||
alignment: alignmentProp = "center",
|
||||
titleId,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeContentLockupVariant(variantProp);
|
||||
const alignment = normalizeAlignment(alignmentProp);
|
||||
// Variant-specific styling
|
||||
const variantStyles: Record<string, VariantStyle> = {
|
||||
hero: {
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
export type ContentLockupVariantValue =
|
||||
| "hero"
|
||||
| "feature"
|
||||
| "learn"
|
||||
| "ask"
|
||||
| "ask-inverse"
|
||||
| "modal"
|
||||
| "Hero"
|
||||
| "Feature"
|
||||
| "Learn"
|
||||
| "Ask"
|
||||
| "Ask-Inverse"
|
||||
| "Modal";
|
||||
|
||||
export type ContentLockupAlignmentValue = "center" | "left" | "Center" | "Left";
|
||||
|
||||
export interface ContentLockupProps {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
@@ -5,10 +21,18 @@ export interface ContentLockupProps {
|
||||
ctaText?: string;
|
||||
ctaHref?: string;
|
||||
buttonClassName?: string;
|
||||
variant?: "hero" | "feature" | "learn" | "ask" | "ask-inverse" | "modal";
|
||||
/**
|
||||
* Content lockup variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: ContentLockupVariantValue;
|
||||
linkText?: string;
|
||||
linkHref?: string;
|
||||
alignment?: "center" | "left";
|
||||
/**
|
||||
* Text alignment. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
alignment?: ContentLockupAlignmentValue;
|
||||
/**
|
||||
* Optional id to attach to the primary title heading.
|
||||
* Useful when a parent section uses aria-labelledby.
|
||||
|
||||
@@ -4,9 +4,12 @@ import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
||||
import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view";
|
||||
import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types";
|
||||
import { normalizeContentThumbnailVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
|
||||
({ post, className = "", variant = "vertical" }) => {
|
||||
({ post, className = "", variant: variantProp = "vertical" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeContentThumbnailVariant(variantProp);
|
||||
// Get article-specific background image from frontmatter
|
||||
const getBackgroundImage = (
|
||||
post: ContentThumbnailTemplateProps["post"],
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { BlogPost } from "../../../lib/content";
|
||||
|
||||
export type ContentThumbnailTemplateVariantValue = "vertical" | "horizontal" | "Vertical" | "Horizontal";
|
||||
|
||||
export interface ContentThumbnailTemplateProps {
|
||||
post: BlogPost;
|
||||
className?: string;
|
||||
variant?: "vertical" | "horizontal";
|
||||
/**
|
||||
* Content thumbnail variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: ContentThumbnailTemplateVariantValue;
|
||||
slugOrder?: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { forwardRef, memo, useCallback } from "react";
|
||||
import { ContextMenuItemView } from "./ContextMenuItem.view";
|
||||
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
||||
import { normalizeContextMenuItemSize } from "../../../lib/propNormalization";
|
||||
|
||||
const ContextMenuItemContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -16,11 +17,13 @@ const ContextMenuItemContainer = forwardRef<
|
||||
disabled = false,
|
||||
className = "",
|
||||
onClick,
|
||||
size = "medium",
|
||||
size: sizeProp = "medium",
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContextMenuItemSize(sizeProp);
|
||||
const getTextSize = (): string => {
|
||||
switch (size) {
|
||||
case "small":
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type ContextMenuItemSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||
|
||||
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
selected?: boolean;
|
||||
@@ -7,7 +9,11 @@ export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElemen
|
||||
onClick?: (
|
||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
size?: "small" | "medium" | "large";
|
||||
/**
|
||||
* Context menu item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: ContextMenuItemSizeValue;
|
||||
}
|
||||
|
||||
export interface ContextMenuItemViewProps {
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { normalizeImagePlaceholderColor } from "../../lib/propNormalization";
|
||||
|
||||
export type ImagePlaceholderColorValue =
|
||||
| "blue"
|
||||
| "green"
|
||||
| "purple"
|
||||
| "red"
|
||||
| "orange"
|
||||
| "teal"
|
||||
| "Blue"
|
||||
| "Green"
|
||||
| "Purple"
|
||||
| "Red"
|
||||
| "Orange"
|
||||
| "Teal";
|
||||
|
||||
interface ImagePlaceholderProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
text?: string;
|
||||
color?: "blue" | "green" | "purple" | "red" | "orange" | "teal";
|
||||
/**
|
||||
* Image placeholder color. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
color?: ImagePlaceholderColorValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -19,9 +38,11 @@ const ImagePlaceholder = memo<ImagePlaceholderProps>(
|
||||
width = 260,
|
||||
height = 390,
|
||||
text = "Blog Image",
|
||||
color = "blue",
|
||||
color: colorProp = "blue",
|
||||
className = "",
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const color = normalizeImagePlaceholderColor(colorProp);
|
||||
const colors: Record<string, string> = {
|
||||
blue: "bg-blue-500",
|
||||
green: "bg-green-500",
|
||||
|
||||
@@ -2,15 +2,32 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { useTranslation } from "../contexts/MessagesContext";
|
||||
import { normalizeMenuBarSize } from "../../lib/propNormalization";
|
||||
|
||||
export type MenuBarSizeValue =
|
||||
| "xsmall"
|
||||
| "default"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "XSmall"
|
||||
| "Default"
|
||||
| "Medium"
|
||||
| "Large";
|
||||
|
||||
interface MenuBarProps extends React.HTMLAttributes<HTMLElement> {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
size?: "xsmall" | "default" | "medium" | "large";
|
||||
/**
|
||||
* Menu bar size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: MenuBarSizeValue;
|
||||
}
|
||||
|
||||
const MenuBar = memo<MenuBarProps>(
|
||||
({ children, className = "", size = "default", ...props }) => {
|
||||
({ children, className = "", size: sizeProp = "default", ...props }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeMenuBarSize(sizeProp);
|
||||
const t = useTranslation("menuBar");
|
||||
const sizeStyles: Record<string, string> = {
|
||||
xsmall:
|
||||
|
||||
@@ -3,19 +3,24 @@
|
||||
import { memo } from "react";
|
||||
import MenuBarItemView from "./MenuBarItem.view";
|
||||
import type { MenuBarItemProps } from "./MenuBarItem.types";
|
||||
import { normalizeMenuBarItemVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const MenuBarItemContainer = memo<MenuBarItemProps>(
|
||||
({
|
||||
href = "#",
|
||||
children,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
variant: variantProp = "default",
|
||||
size: sizeProp = "default",
|
||||
className = "",
|
||||
disabled = false,
|
||||
isActive = false,
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeMenuBarItemVariant(variantProp);
|
||||
// Size has many values, normalize by lowercasing
|
||||
const size = (sizeProp?.toLowerCase() || "default") as typeof sizeProp;
|
||||
const variantStyles: Record<string, string> = {
|
||||
default:
|
||||
"bg-transparent text-[var(--color-content-default-brand-primary)] hover:bg-[var(--color-surface-default-tertiary)] hover:text-[var(--color-content-default-brand-primary)] hover:scale-[1.02] active:bg-transparent active:text-[var(--color-content-default-brand-primary)] active:scale-[0.98] disabled:bg-[var(--color-surface-default-tertiary)] disabled:text-[var(--color-content-default-tertiary)] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100",
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
export type MenuBarItemSizeValue =
|
||||
| "default"
|
||||
| "xsmall"
|
||||
| "xsmallUseCases"
|
||||
| "home"
|
||||
| "homeMd"
|
||||
| "homeUseCases"
|
||||
| "large"
|
||||
| "largeUseCases"
|
||||
| "homeXlarge"
|
||||
| "xlarge"
|
||||
| "Default"
|
||||
| "XSmall"
|
||||
| "XSmallUseCases"
|
||||
| "Home"
|
||||
| "HomeMd"
|
||||
| "HomeUseCases"
|
||||
| "Large"
|
||||
| "LargeUseCases"
|
||||
| "HomeXlarge"
|
||||
| "XLarge";
|
||||
|
||||
export type MenuBarItemVariantValue = "default" | "home" | "Default" | "Home";
|
||||
|
||||
export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
variant?: "default" | "home";
|
||||
size?:
|
||||
| "default"
|
||||
| "xsmall"
|
||||
| "xsmallUseCases"
|
||||
| "home"
|
||||
| "homeMd"
|
||||
| "homeUseCases"
|
||||
| "large"
|
||||
| "largeUseCases"
|
||||
| "homeXlarge"
|
||||
| "xlarge";
|
||||
/**
|
||||
* Menu bar item variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: MenuBarItemVariantValue;
|
||||
/**
|
||||
* Menu bar item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: MenuBarItemSizeValue;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
isActive?: boolean;
|
||||
|
||||
@@ -3,18 +3,22 @@
|
||||
import { memo } from "react";
|
||||
import NavigationItemView from "./NavigationItem.view";
|
||||
import type { NavigationItemProps } from "./NavigationItem.types";
|
||||
import { normalizeNavigationItemVariant, normalizeNavigationItemSize } from "../../../lib/propNormalization";
|
||||
|
||||
const NavigationItemContainer = memo<NavigationItemProps>(
|
||||
({
|
||||
href = "#",
|
||||
children,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
variant: variantProp = "default",
|
||||
size: sizeProp = "default",
|
||||
className = "",
|
||||
disabled = false,
|
||||
isActive = false,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeNavigationItemVariant(variantProp);
|
||||
const size = normalizeNavigationItemSize(sizeProp);
|
||||
// Variant styles
|
||||
const variantStyles: Record<string, string> = {
|
||||
default:
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
export type NavigationItemVariantValue = "default" | "Default";
|
||||
export type NavigationItemSizeValue = "default" | "xsmall" | "Default" | "XSmall";
|
||||
|
||||
export interface NavigationItemProps extends Omit<
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
"isActive"
|
||||
> {
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
variant?: "default";
|
||||
size?: "default" | "xsmall";
|
||||
/**
|
||||
* Navigation item variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: NavigationItemVariantValue;
|
||||
/**
|
||||
* Navigation item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: NavigationItemSizeValue;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
isActive?: boolean;
|
||||
|
||||
@@ -3,21 +3,39 @@
|
||||
import { memo } from "react";
|
||||
import SectionNumber from "./SectionNumber";
|
||||
|
||||
import { normalizeNumberCardSize } from "../../lib/propNormalization";
|
||||
|
||||
export type NumberCardSizeValue =
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge"
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge";
|
||||
|
||||
interface NumberCardProps {
|
||||
number: number;
|
||||
text: string;
|
||||
size?: "Small" | "Medium" | "Large" | "XLarge";
|
||||
/**
|
||||
* Number card size. Accepts both PascalCase (Figma default) and lowercase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses PascalCase - both are supported.
|
||||
*/
|
||||
size?: NumberCardSizeValue;
|
||||
iconShape?: string;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
const NumberCard = memo<NumberCardProps>(({ number, text, size }) => {
|
||||
const NumberCard = memo<NumberCardProps>(({ number, text, size: sizeProp }) => {
|
||||
// Base classes common to all sizes
|
||||
const baseClasses = "bg-[var(--color-surface-inverse-primary)] rounded-[12px] shadow-lg";
|
||||
|
||||
// If size prop is provided, use explicit size classes
|
||||
// Otherwise, use responsive breakpoints for backward compatibility
|
||||
if (size) {
|
||||
if (sizeProp) {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeNumberCardSize(sizeProp);
|
||||
// Size-specific classes
|
||||
const sizeClasses = {
|
||||
Small: "flex flex-col items-end justify-center gap-4 p-5 relative",
|
||||
|
||||
@@ -4,10 +4,11 @@ import { memo, useState } from "react";
|
||||
import { logger } from "../../../lib/logger";
|
||||
import QuoteBlockView from "./QuoteBlock.view";
|
||||
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
|
||||
import { normalizeQuoteBlockVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const QuoteBlockContainer = memo<QuoteBlockProps>(
|
||||
({
|
||||
variant = "standard",
|
||||
variant: variantProp = "standard",
|
||||
className = "",
|
||||
quote = "The rules of decision-making must be open and available to everyone, and this can happen only if they are formalized.",
|
||||
author = "Jo Freeman",
|
||||
@@ -17,6 +18,8 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
|
||||
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
|
||||
onError,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeQuoteBlockVariant(variantProp);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const [imageLoading, setImageLoading] = useState(true);
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
export type QuoteBlockVariantValue =
|
||||
| "compact"
|
||||
| "standard"
|
||||
| "extended"
|
||||
| "Compact"
|
||||
| "Standard"
|
||||
| "Extended";
|
||||
|
||||
export interface QuoteBlockProps {
|
||||
variant?: "compact" | "standard" | "extended";
|
||||
/**
|
||||
* Quote block variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: QuoteBlockVariantValue;
|
||||
className?: string;
|
||||
quote?: string;
|
||||
author?: string;
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
import { memo, useCallback, useId } from "react";
|
||||
import { RadioButtonView } from "./RadioButton.view";
|
||||
import type { RadioButtonProps } from "./RadioButton.types";
|
||||
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||
|
||||
const RadioButtonContainer = ({
|
||||
checked = false,
|
||||
mode = "standard",
|
||||
state = "default", // This state prop is now only for static display in Storybook/Preview
|
||||
mode: modeProp = "standard",
|
||||
state: stateProp = "default", // This state prop is now only for static display in Storybook/Preview
|
||||
indicator: _indicator = true, // From Figma: whether to show the indicator dot (currently not used in view)
|
||||
disabled = false,
|
||||
label,
|
||||
onChange,
|
||||
@@ -17,6 +19,13 @@ const RadioButtonContainer = ({
|
||||
ariaLabel,
|
||||
className = "",
|
||||
}: RadioButtonProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
const state = normalizeState(stateProp);
|
||||
|
||||
// If state is "selected", it means checked in Figma terms
|
||||
const normalizedState = state === "selected" || checked ? "selected" : state;
|
||||
|
||||
const isInverse = mode === "inverse";
|
||||
const isStandard = mode === "standard";
|
||||
|
||||
@@ -113,7 +122,7 @@ const RadioButtonContainer = ({
|
||||
radioId={radioId}
|
||||
checked={checked}
|
||||
mode={mode}
|
||||
state={state} // Passed for static display in Storybook/Preview
|
||||
state={normalizedState} // Normalized state (handles "selected" from Figma)
|
||||
disabled={disabled}
|
||||
label={label}
|
||||
name={name}
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface RadioButtonProps {
|
||||
checked?: boolean;
|
||||
mode?: "standard" | "inverse";
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus", "selected"/"Selected" (case-insensitive).
|
||||
* Note: "selected" state is represented by the `checked` prop in practice.
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
/**
|
||||
* Whether to show the indicator dot. From Figma specification.
|
||||
*/
|
||||
indicator?: boolean;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
onChange?: (_data: { checked: boolean; value?: string }) => void;
|
||||
@@ -16,7 +31,7 @@ export interface RadioButtonViewProps {
|
||||
radioId: string;
|
||||
checked: boolean;
|
||||
mode: "standard" | "inverse";
|
||||
state: "default" | "hover" | "focus";
|
||||
state: "default" | "hover" | "focus" | "selected";
|
||||
disabled: boolean;
|
||||
label?: string;
|
||||
name?: string;
|
||||
|
||||
@@ -3,18 +3,26 @@
|
||||
import { memo, useCallback, useId } from "react";
|
||||
import { RadioGroupView } from "./RadioGroup.view";
|
||||
import type { RadioGroupProps } from "./RadioGroup.types";
|
||||
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||
|
||||
const RadioGroupContainer = ({
|
||||
name,
|
||||
value,
|
||||
onChange,
|
||||
mode = "standard",
|
||||
state = "default",
|
||||
mode: modeProp = "standard",
|
||||
state: stateProp = "default",
|
||||
disabled = false,
|
||||
options = [],
|
||||
className = "",
|
||||
...props
|
||||
}: RadioGroupProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
// Normalize state, but handle "With Subtext" separately (it's represented by options with subtext)
|
||||
const state = typeof stateProp === "string" &&
|
||||
(stateProp.toLowerCase() === "with subtext" || stateProp === "With Subtext")
|
||||
? "default" // "With Subtext" is handled via RadioOption.subtext, use default state
|
||||
: normalizeState(stateProp);
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const generatedId = useId();
|
||||
const groupId = name || `radio-group-${generatedId}`;
|
||||
|
||||
@@ -5,12 +5,23 @@ export interface RadioOption {
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface RadioGroupProps {
|
||||
name?: string;
|
||||
value?: string;
|
||||
onChange?: (_data: { value: string }) => void;
|
||||
mode?: "standard" | "inverse";
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma also supports "With Subtext" state, which is handled via RadioOption.subtext.
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue | "With Subtext" | "with subtext";
|
||||
disabled?: boolean;
|
||||
options?: RadioOption[];
|
||||
className?: string;
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { normalizeSectionHeaderVariant } from "../../lib/propNormalization";
|
||||
|
||||
export type SectionHeaderVariantValue = "default" | "multi-line" | "Default" | "Multi-Line";
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
titleLg?: string;
|
||||
variant?: "default" | "multi-line";
|
||||
/**
|
||||
* Section header variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: SectionHeaderVariantValue;
|
||||
}
|
||||
|
||||
const SectionHeader = memo<SectionHeaderProps>(
|
||||
({ title, subtitle, titleLg, variant = "default" }) => {
|
||||
({ title, subtitle, titleLg, variant: variantProp = "default" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeSectionHeaderVariant(variantProp);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
||||
@@ -16,13 +16,16 @@ import React, {
|
||||
import { useClickOutside } from "../../hooks";
|
||||
import { SelectInputView } from "./SelectInput.view";
|
||||
import type { SelectInputProps } from "./SelectInput.types";
|
||||
import { normalizeState, normalizeSmallMediumLargeSize, normalizeLabelVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||
(
|
||||
{
|
||||
id,
|
||||
label,
|
||||
state: externalState = "default",
|
||||
labelVariant: labelVariantProp,
|
||||
size: sizeProp,
|
||||
state: externalStateProp = "default",
|
||||
disabled = false,
|
||||
error = false,
|
||||
placeholder = "Choose an option",
|
||||
@@ -35,6 +38,12 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// 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
|
||||
const _labelVariant = labelVariantProp ? normalizeLabelVariant(labelVariantProp) : undefined;
|
||||
const _size = sizeProp ? normalizeSmallMediumLargeSize(sizeProp) : undefined;
|
||||
const externalState = normalizeState(externalStateProp);
|
||||
|
||||
const generatedId = useId();
|
||||
const selectId = id || `select-input-${generatedId}`;
|
||||
const labelId = `${selectId}-label`;
|
||||
|
||||
@@ -5,12 +5,29 @@ export interface SelectOptionData {
|
||||
label: string;
|
||||
}
|
||||
|
||||
import type { StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export type SelectInputLabelVariantValue = "default" | "horizontal" | "Default" | "Horizontal";
|
||||
export type SelectInputSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||
|
||||
export interface SelectInputProps {
|
||||
id?: string;
|
||||
label?: string;
|
||||
labelVariant?: "default" | "horizontal";
|
||||
size?: "small" | "medium" | "large";
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Label variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
labelVariant?: SelectInputLabelVariantValue;
|
||||
/**
|
||||
* Select input size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: SelectInputSizeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
disabled?: boolean;
|
||||
error?: boolean;
|
||||
placeholder?: string;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { forwardRef, memo, useCallback } from "react";
|
||||
import { SelectOptionView } from "./SelectOption.view";
|
||||
import type { SelectOptionProps } from "./SelectOption.types";
|
||||
import { normalizeContextMenuItemSize } from "../../../lib/propNormalization";
|
||||
|
||||
const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||
(
|
||||
@@ -12,11 +13,13 @@ const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||
disabled = false,
|
||||
className = "",
|
||||
onClick,
|
||||
size = "medium",
|
||||
size: sizeProp = "medium",
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContextMenuItemSize(sizeProp);
|
||||
const getTextSize = (): string => {
|
||||
switch (size) {
|
||||
case "small":
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type SelectOptionSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||
|
||||
export interface SelectOptionProps {
|
||||
children?: React.ReactNode;
|
||||
selected?: boolean;
|
||||
@@ -6,7 +8,11 @@ export interface SelectOptionProps {
|
||||
onClick?: (
|
||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
size?: "small" | "medium" | "large";
|
||||
/**
|
||||
* Select option size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: SelectOptionSizeValue;
|
||||
}
|
||||
|
||||
export interface SelectOptionViewProps {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { SwitchView } from "./Switch.view";
|
||||
import type { SwitchProps } from "./Switch.types";
|
||||
import { normalizeState } from "../../../lib/propNormalization";
|
||||
|
||||
const SwitchContainer = memo(
|
||||
forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
|
||||
@@ -11,11 +12,14 @@ const SwitchContainer = memo(
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
state = "default",
|
||||
state: stateProp = "default",
|
||||
label,
|
||||
className = "",
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const state = normalizeState(stateProp);
|
||||
|
||||
const switchId = useId();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface SwitchProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
@@ -10,7 +12,11 @@ export interface SwitchProps extends Omit<
|
||||
) => void;
|
||||
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ import { memo, forwardRef } from "react";
|
||||
import { useComponentId, useFormField } from "../../hooks";
|
||||
import { TextAreaView } from "./TextArea.view";
|
||||
import type { TextAreaProps } from "./TextArea.types";
|
||||
import { normalizeInputState, normalizeSmallMediumLargeSize, normalizeLabelVariant } from "../../../lib/propNormalization";
|
||||
|
||||
const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
(
|
||||
{
|
||||
size = "medium",
|
||||
labelVariant = "default",
|
||||
state = "default",
|
||||
size: sizeProp = "medium",
|
||||
labelVariant: labelVariantProp = "default",
|
||||
state: stateProp = "default",
|
||||
disabled = false,
|
||||
error = false,
|
||||
label,
|
||||
@@ -27,6 +28,10 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSmallMediumLargeSize(sizeProp);
|
||||
const labelVariant = normalizeLabelVariant(labelVariantProp);
|
||||
const state = normalizeInputState(stateProp);
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const { id: textareaId, labelId } = useComponentId("textarea", id);
|
||||
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import type { InputStateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export type TextAreaSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||
export type TextAreaLabelVariantValue = "default" | "horizontal" | "Default" | "Horizontal";
|
||||
|
||||
export interface TextAreaProps extends Omit<
|
||||
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
size?: "small" | "medium" | "large";
|
||||
labelVariant?: "default" | "horizontal";
|
||||
state?: "default" | "active" | "hover" | "focus";
|
||||
/**
|
||||
* Text area size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: TextAreaSizeValue;
|
||||
/**
|
||||
* Label variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
labelVariant?: TextAreaLabelVariantValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "active"/"Active", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: InputStateValue;
|
||||
disabled?: boolean;
|
||||
error?: boolean;
|
||||
label?: string;
|
||||
|
||||
@@ -4,11 +4,12 @@ import { memo, forwardRef, useState, useRef } from "react";
|
||||
import { useComponentId, useFormField } from "../../hooks";
|
||||
import { TextInputView } from "./TextInput.view";
|
||||
import type { TextInputProps } from "./TextInput.types";
|
||||
import { normalizeInputState } from "../../../lib/propNormalization";
|
||||
|
||||
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
(
|
||||
{
|
||||
state: externalState = "default",
|
||||
state: externalStateProp = "default",
|
||||
disabled = false,
|
||||
error = false,
|
||||
label,
|
||||
@@ -26,6 +27,9 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const externalState = normalizeInputState(externalStateProp);
|
||||
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const { id: inputId, labelId } = useComponentId("text-input", id);
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { InputStateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface TextInputProps extends Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
state?: "default" | "active" | "hover" | "focus";
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "active"/"Active", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: InputStateValue;
|
||||
disabled?: boolean;
|
||||
error?: boolean;
|
||||
label?: string;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { ToggleView } from "./Toggle.view";
|
||||
import type { ToggleProps } from "./Toggle.types";
|
||||
import { normalizeState } from "../../../lib/propNormalization";
|
||||
|
||||
const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
(
|
||||
@@ -13,7 +14,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
onFocus,
|
||||
onBlur,
|
||||
disabled = false,
|
||||
state = "default",
|
||||
state: stateProp = "default",
|
||||
showIcon = false,
|
||||
showText = false,
|
||||
icon = "I",
|
||||
@@ -23,6 +24,8 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const state = normalizeState(stateProp);
|
||||
const toggleId = useId();
|
||||
const labelId = useId();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export interface ToggleProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
@@ -12,7 +14,11 @@ export interface ToggleProps extends Omit<
|
||||
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
disabled?: boolean;
|
||||
state?: "default" | "hover" | "focus";
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
showIcon?: boolean;
|
||||
showText?: boolean;
|
||||
icon?: string;
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { ToggleGroupView } from "./ToggleGroup.view";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
import { normalizeToggleState, normalizeToggleGroupPosition } from "../../../lib/propNormalization";
|
||||
|
||||
const ToggleGroupContainer = memo(
|
||||
forwardRef<HTMLButtonElement, ToggleGroupProps>((props, _ref) => {
|
||||
const {
|
||||
children,
|
||||
className = "",
|
||||
position = "left",
|
||||
state = "default",
|
||||
position: positionProp = "left",
|
||||
state: stateProp = "default",
|
||||
showText = true,
|
||||
ariaLabel,
|
||||
onChange,
|
||||
@@ -18,6 +19,10 @@ const ToggleGroupContainer = memo(
|
||||
onBlur,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const position = normalizeToggleGroupPosition(positionProp);
|
||||
const state = normalizeToggleState(stateProp);
|
||||
|
||||
const groupId = useId();
|
||||
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import type { StateValue } from "../../../lib/propNormalization";
|
||||
|
||||
export type ToggleGroupPositionValue = "left" | "middle" | "right" | "Left" | "Middle" | "Right";
|
||||
|
||||
export interface ToggleGroupProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
> {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
position?: "left" | "middle" | "right";
|
||||
state?: "default" | "hover" | "focus" | "selected";
|
||||
/**
|
||||
* Toggle group position. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
position?: ToggleGroupPositionValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus", "selected"/"Selected" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue | "selected" | "Selected";
|
||||
showText?: boolean;
|
||||
ariaLabel?: string;
|
||||
onChange?: (
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
import { memo, useState } from "react";
|
||||
import { TooltipView } from "./Tooltip.view";
|
||||
import type { TooltipProps } from "./Tooltip.types";
|
||||
import { normalizeTooltipPosition } from "../../../lib/propNormalization";
|
||||
|
||||
const TooltipContainer = memo<TooltipProps>(
|
||||
({ children, text, position = "top", className = "", disabled = false }) => {
|
||||
({ children, text, position: positionProp = "top", className = "", disabled = false }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const position = normalizeTooltipPosition(positionProp);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
if (disabled) {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
export type TooltipPositionValue = "top" | "bottom" | "Top" | "Bottom";
|
||||
|
||||
export interface TooltipProps {
|
||||
children: React.ReactNode;
|
||||
text: string;
|
||||
position?: "top" | "bottom";
|
||||
/**
|
||||
* Tooltip position. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
position?: TooltipPositionValue;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
/**
|
||||
* Utility functions for normalizing component props to match Figma specifications
|
||||
* while maintaining backward compatibility with existing lowercase usage.
|
||||
*
|
||||
* Figma uses PascalCase (e.g., "Standard", "Inverse") but codebase uses lowercase.
|
||||
* These helpers accept both formats and normalize to lowercase for internal use.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Normalize mode prop values (Standard/Inverse -> standard/inverse)
|
||||
*/
|
||||
export function normalizeMode(
|
||||
value: string | undefined,
|
||||
defaultValue: "standard" | "inverse" = "standard"
|
||||
): "standard" | "inverse" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
if (normalized === "standard" || normalized === "inverse") {
|
||||
return normalized;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize state prop values (Default/Hover/Focus/Selected -> default/hover/focus/selected)
|
||||
*/
|
||||
export function normalizeState(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" | "hover" | "focus" | "selected" = "default"
|
||||
): "default" | "hover" | "focus" | "selected" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
if (
|
||||
normalized === "default" ||
|
||||
normalized === "hover" ||
|
||||
normalized === "focus" ||
|
||||
normalized === "selected"
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize state prop values for form inputs (Default/Active/Hover/Focus)
|
||||
*/
|
||||
export function normalizeInputState(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" | "active" | "hover" | "focus" = "default"
|
||||
): "default" | "active" | "hover" | "focus" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
if (
|
||||
normalized === "default" ||
|
||||
normalized === "active" ||
|
||||
normalized === "hover" ||
|
||||
normalized === "focus"
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize toggle state prop values (Default/Hover/Focus/Selected)
|
||||
*/
|
||||
export function normalizeToggleState(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" | "hover" | "focus" | "selected" = "default"
|
||||
): "default" | "hover" | "focus" | "selected" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
if (
|
||||
normalized === "default" ||
|
||||
normalized === "hover" ||
|
||||
normalized === "focus" ||
|
||||
normalized === "selected"
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type helper for case-insensitive mode prop
|
||||
*/
|
||||
export type ModeValue = "standard" | "inverse" | "Standard" | "Inverse";
|
||||
|
||||
/**
|
||||
* Type helper for case-insensitive state prop
|
||||
*/
|
||||
export type StateValue =
|
||||
| "default"
|
||||
| "hover"
|
||||
| "focus"
|
||||
| "selected"
|
||||
| "Default"
|
||||
| "Hover"
|
||||
| "Focus"
|
||||
| "Selected";
|
||||
|
||||
/**
|
||||
* Type helper for case-insensitive input state prop
|
||||
*/
|
||||
export type InputStateValue =
|
||||
| "default"
|
||||
| "active"
|
||||
| "hover"
|
||||
| "focus"
|
||||
| "Default"
|
||||
| "Active"
|
||||
| "Hover"
|
||||
| "Focus";
|
||||
|
||||
/**
|
||||
* Normalize button variant prop values
|
||||
*/
|
||||
export function normalizeVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "filled" = "filled"
|
||||
): "filled" | "filled-inverse" | "outline" | "outline-inverse" | "ghost" | "ghost-inverse" | "danger" | "danger-inverse" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = [
|
||||
"filled",
|
||||
"filled-inverse",
|
||||
"outline",
|
||||
"outline-inverse",
|
||||
"ghost",
|
||||
"ghost-inverse",
|
||||
"danger",
|
||||
"danger-inverse",
|
||||
];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize button size prop values
|
||||
*/
|
||||
export function normalizeSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "xsmall" = "xsmall"
|
||||
): "xsmall" | "small" | "medium" | "large" | "xlarge" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["xsmall", "small", "medium", "large", "xlarge"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize alert status prop values
|
||||
*/
|
||||
export function normalizeAlertStatus(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" | "positive" | "warning" | "danger" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const statuses = ["default", "positive", "warning", "danger"];
|
||||
if (statuses.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize alert type prop values
|
||||
*/
|
||||
export function normalizeAlertType(
|
||||
value: string | undefined,
|
||||
defaultValue: "toast" = "toast"
|
||||
): "toast" | "banner" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const types = ["toast", "banner"];
|
||||
if (types.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize tooltip position prop values
|
||||
*/
|
||||
export function normalizeTooltipPosition(
|
||||
value: string | undefined,
|
||||
defaultValue: "top" = "top"
|
||||
): "top" | "bottom" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const positions = ["top", "bottom"];
|
||||
if (positions.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type helper for case-insensitive variant prop
|
||||
*/
|
||||
export type VariantValue =
|
||||
| "filled"
|
||||
| "filled-inverse"
|
||||
| "outline"
|
||||
| "outline-inverse"
|
||||
| "ghost"
|
||||
| "ghost-inverse"
|
||||
| "danger"
|
||||
| "danger-inverse"
|
||||
| "Filled"
|
||||
| "Filled-Inverse"
|
||||
| "Outline"
|
||||
| "Outline-Inverse"
|
||||
| "Ghost"
|
||||
| "Ghost-Inverse"
|
||||
| "Danger"
|
||||
| "Danger-Inverse";
|
||||
|
||||
/**
|
||||
* Type helper for case-insensitive size prop
|
||||
*/
|
||||
export type SizeValue =
|
||||
| "xsmall"
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge"
|
||||
| "XSmall"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge";
|
||||
|
||||
/**
|
||||
* Normalize menu bar size prop values
|
||||
*/
|
||||
export function normalizeMenuBarSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "xsmall" | "default" | "medium" | "large" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["xsmall", "default", "medium", "large"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize menu bar item variant prop values
|
||||
*/
|
||||
export function normalizeMenuBarItemVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" | "home" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["default", "home"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize navigation item variant prop values
|
||||
*/
|
||||
export function normalizeNavigationItemVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
if (normalized === "default") {
|
||||
return "default";
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize navigation item size prop values
|
||||
*/
|
||||
export function normalizeNavigationItemSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" | "xsmall" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["default", "xsmall"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize content lockup variant prop values
|
||||
*/
|
||||
export function normalizeContentLockupVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "hero" = "hero"
|
||||
): "hero" | "feature" | "learn" | "ask" | "ask-inverse" | "modal" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["hero", "feature", "learn", "ask", "ask-inverse", "modal"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize alignment prop values
|
||||
*/
|
||||
export function normalizeAlignment(
|
||||
value: string | undefined,
|
||||
defaultValue: "center" = "center"
|
||||
): "center" | "left" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const alignments = ["center", "left"];
|
||||
if (alignments.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize content container size prop values
|
||||
*/
|
||||
export function normalizeContentContainerSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "responsive" = "responsive"
|
||||
): "xs" | "responsive" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["xs", "responsive"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize content thumbnail variant prop values
|
||||
*/
|
||||
export function normalizeContentThumbnailVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "vertical" = "vertical"
|
||||
): "vertical" | "horizontal" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["vertical", "horizontal"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize section header variant prop values
|
||||
*/
|
||||
export function normalizeSectionHeaderVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" | "multi-line" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["default", "multi-line"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize quote block variant prop values
|
||||
*/
|
||||
export function normalizeQuoteBlockVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "standard" = "standard"
|
||||
): "compact" | "standard" | "extended" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["compact", "standard", "extended"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize number card size prop values (already PascalCase in codebase, supports both)
|
||||
*/
|
||||
export function normalizeNumberCardSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "Medium" = "Medium"
|
||||
): "Small" | "Medium" | "Large" | "XLarge" {
|
||||
if (!value) return defaultValue;
|
||||
// Check if already PascalCase
|
||||
if (value === "Small" || value === "Medium" || value === "Large" || value === "XLarge") {
|
||||
return value;
|
||||
}
|
||||
// Normalize lowercase to PascalCase
|
||||
const normalized = value.toLowerCase();
|
||||
if (normalized === "small") return "Small";
|
||||
if (normalized === "medium") return "Medium";
|
||||
if (normalized === "large") return "Large";
|
||||
if (normalized === "xlarge") return "XLarge";
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize ask organizer variant prop values
|
||||
*/
|
||||
export function normalizeAskOrganizerVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "centered" = "centered"
|
||||
): "centered" | "left-aligned" | "compact" | "inverse" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["centered", "left-aligned", "compact", "inverse"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize context menu item size prop values
|
||||
*/
|
||||
export function normalizeContextMenuItemSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "medium" = "medium"
|
||||
): "small" | "medium" | "large" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["small", "medium", "large"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize image placeholder color prop values
|
||||
*/
|
||||
export function normalizeImagePlaceholderColor(
|
||||
value: string | undefined,
|
||||
defaultValue: "blue" = "blue"
|
||||
): "blue" | "green" | "purple" | "red" | "orange" | "teal" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const colors = ["blue", "green", "purple", "red", "orange", "teal"];
|
||||
if (colors.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize toggle group position prop values
|
||||
*/
|
||||
export function normalizeToggleGroupPosition(
|
||||
value: string | undefined,
|
||||
defaultValue: "left" = "left"
|
||||
): "left" | "middle" | "right" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const positions = ["left", "middle", "right"];
|
||||
if (positions.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize label variant prop values
|
||||
*/
|
||||
export function normalizeLabelVariant(
|
||||
value: string | undefined,
|
||||
defaultValue: "default" = "default"
|
||||
): "default" | "horizontal" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const variants = ["default", "horizontal"];
|
||||
if (variants.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize small/medium/large size prop values (for SelectInput, TextArea, etc.)
|
||||
*/
|
||||
export function normalizeSmallMediumLargeSize(
|
||||
value: string | undefined,
|
||||
defaultValue: "medium" = "medium"
|
||||
): "small" | "medium" | "large" {
|
||||
if (!value) return defaultValue;
|
||||
const normalized = value.toLowerCase();
|
||||
const sizes = ["small", "medium", "large"];
|
||||
if (sizes.includes(normalized)) {
|
||||
return normalized as typeof defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -46,13 +46,13 @@ export default {
|
||||
},
|
||||
mode: {
|
||||
control: "select",
|
||||
options: ["standard", "inverse"],
|
||||
description: "Visual mode of the checkbox",
|
||||
options: ["standard", "inverse", "Standard", "Inverse"],
|
||||
description: "Visual mode of the checkbox (case-insensitive: accepts both lowercase and PascalCase)",
|
||||
},
|
||||
state: {
|
||||
control: "select",
|
||||
options: ["default", "hover", "focus"],
|
||||
description: "Interaction state for static display",
|
||||
options: ["default", "hover", "focus", "Default", "Hover", "Focus"],
|
||||
description: "Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean",
|
||||
@@ -206,3 +206,53 @@ export const AllModes = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Test PascalCase props from Figma
|
||||
export const FigmaPascalCase = () => {
|
||||
const [standardChecked, setStandardChecked] = React.useState(false);
|
||||
const [inverseChecked, setInverseChecked] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 text-white">Figma PascalCase Props (Standard/Inverse)</h3>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
These components accept both PascalCase (from Figma) and lowercase (from codebase) prop values.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<Checkbox
|
||||
label="Standard Mode (PascalCase)"
|
||||
checked={standardChecked}
|
||||
mode="Standard"
|
||||
state="Default"
|
||||
onChange={({ checked }) => setStandardChecked(checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Inverse Mode (PascalCase)"
|
||||
checked={inverseChecked}
|
||||
mode="Inverse"
|
||||
state="Default"
|
||||
onChange={({ checked }) => setInverseChecked(checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 text-white">Mixed Case (backward compatibility)</h3>
|
||||
<div className="space-y-4">
|
||||
<Checkbox
|
||||
label="Standard mode (lowercase) - still works"
|
||||
checked={false}
|
||||
mode="standard"
|
||||
state="default"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Inverse Mode (mixed) - still works"
|
||||
checked={false}
|
||||
mode="inverse"
|
||||
state="Default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,13 +21,13 @@ export default {
|
||||
},
|
||||
mode: {
|
||||
control: "select",
|
||||
options: ["standard", "inverse"],
|
||||
description: "Visual mode of the radio button",
|
||||
options: ["standard", "inverse", "Standard", "Inverse"],
|
||||
description: "Visual mode of the radio button (case-insensitive: accepts both lowercase and PascalCase)",
|
||||
},
|
||||
state: {
|
||||
control: "select",
|
||||
options: ["default", "hover", "focus"],
|
||||
description: "Interaction state for static display",
|
||||
options: ["default", "hover", "focus", "selected", "Default", "Hover", "Focus", "Selected"],
|
||||
description: "Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean",
|
||||
@@ -247,3 +247,53 @@ export const InverseAllStates = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Test PascalCase props from Figma
|
||||
export const FigmaPascalCase = () => {
|
||||
const [standardChecked, setStandardChecked] = React.useState(false);
|
||||
const [inverseChecked, setInverseChecked] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 text-white">Figma PascalCase Props (Standard/Inverse)</h3>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
These components accept both PascalCase (from Figma) and lowercase (from codebase) prop values.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<RadioButton
|
||||
label="Standard Mode (PascalCase)"
|
||||
checked={standardChecked}
|
||||
mode="Standard"
|
||||
state="Default"
|
||||
onChange={({ checked }) => setStandardChecked(checked)}
|
||||
/>
|
||||
<RadioButton
|
||||
label="Inverse Mode (PascalCase)"
|
||||
checked={inverseChecked}
|
||||
mode="Inverse"
|
||||
state="Default"
|
||||
onChange={({ checked }) => setInverseChecked(checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 text-white">Mixed Case (backward compatibility)</h3>
|
||||
<div className="space-y-4">
|
||||
<RadioButton
|
||||
label="Standard mode (lowercase) - still works"
|
||||
checked={false}
|
||||
mode="standard"
|
||||
state="default"
|
||||
/>
|
||||
<RadioButton
|
||||
label="Inverse Mode (mixed) - still works"
|
||||
checked={false}
|
||||
mode="inverse"
|
||||
state="Default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -85,14 +85,14 @@ describe("ContentContainer", () => {
|
||||
});
|
||||
|
||||
it("applies correct width when specified", () => {
|
||||
render(<ContentContainer post={mockPost} width="300px" size="sm" />);
|
||||
render(<ContentContainer post={mockPost} width="300px" size="xs" />);
|
||||
|
||||
const container = document.querySelector("div[class*='relative z-20']");
|
||||
expect(container).toHaveStyle("width: 300px");
|
||||
});
|
||||
|
||||
it("applies default width when not specified", () => {
|
||||
render(<ContentContainer post={mockPost} size="sm" />);
|
||||
render(<ContentContainer post={mockPost} size="xs" />);
|
||||
|
||||
const container = document.querySelector("div[class*='relative z-20']");
|
||||
expect(container).toHaveStyle("width: 200px");
|
||||
@@ -183,8 +183,8 @@ describe("ContentContainer", () => {
|
||||
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies correct responsive sizing for sm breakpoint", () => {
|
||||
render(<ContentContainer post={mockPost} size="sm" />);
|
||||
it("applies correct responsive sizing for xs breakpoint", () => {
|
||||
render(<ContentContainer post={mockPost} size="xs" />);
|
||||
|
||||
const icon = screen.getByAltText("Icon for Test Article Title");
|
||||
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
||||
@@ -196,8 +196,8 @@ describe("ContentContainer", () => {
|
||||
expect(description).toHaveClass("text-[12px]", "leading-[16px]");
|
||||
});
|
||||
|
||||
it("applies correct responsive sizing for md breakpoint", () => {
|
||||
render(<ContentContainer post={mockPost} size="md" />);
|
||||
it("applies correct responsive sizing for responsive breakpoint", () => {
|
||||
render(<ContentContainer post={mockPost} size="responsive" />);
|
||||
|
||||
const icon = screen.getByAltText("Icon for Test Article Title");
|
||||
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
||||
|
||||
@@ -39,7 +39,7 @@ describe("NumberCard Component", () => {
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("flex", "flex-col", "sm:flex-row", "lg:flex-col");
|
||||
expect(card).toHaveClass("flex", "flex-col", "sm:flex-row", "sm:items-center", "lg:flex-col", "lg:items-start", "lg:justify-end", "lg:relative");
|
||||
});
|
||||
|
||||
it("applies proper responsive spacing when size is not specified", () => {
|
||||
@@ -66,7 +66,7 @@ describe("NumberCard Component", () => {
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("lg:h-[238px]");
|
||||
expect(card).toHaveClass("lg:h-[238px]", "lg:relative");
|
||||
});
|
||||
|
||||
it("applies proper background and shadow", () => {
|
||||
@@ -128,8 +128,11 @@ describe("NumberCard Component", () => {
|
||||
expect(textElement).toHaveClass(
|
||||
"text-[24px]",
|
||||
"sm:text-[24px]",
|
||||
"sm:leading-[24px]",
|
||||
"lg:text-[24px]",
|
||||
"lg:leading-[24px]",
|
||||
"xl:text-[32px]",
|
||||
"xl:leading-[32px]",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -156,7 +159,7 @@ describe("NumberCard Component", () => {
|
||||
.closest("div").parentElement;
|
||||
|
||||
// Mobile first approach
|
||||
expect(card).toHaveClass("flex-col", "gap-4", "p-5");
|
||||
expect(card).toHaveClass("flex", "flex-col", "gap-4", "p-5");
|
||||
|
||||
// Small breakpoint
|
||||
expect(card).toHaveClass(
|
||||
@@ -172,7 +175,9 @@ describe("NumberCard Component", () => {
|
||||
"lg:gap-[22px]",
|
||||
"lg:p-8",
|
||||
"lg:items-start",
|
||||
"lg:justify-end",
|
||||
"lg:relative",
|
||||
"lg:h-[238px]",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user