Align Prop Names #38
@@ -85,3 +85,6 @@ act_runner
|
|||||||
# OS files
|
# OS files
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Cursor rules (local development)
|
||||||
|
.cursorrules
|
||||||
|
|||||||
@@ -3,16 +3,20 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { AlertView } from "./Alert.view";
|
import { AlertView } from "./Alert.view";
|
||||||
import type { AlertProps } from "./Alert.types";
|
import type { AlertProps } from "./Alert.types";
|
||||||
|
import { normalizeAlertStatus, normalizeAlertType } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const AlertContainer = memo<AlertProps>(
|
const AlertContainer = memo<AlertProps>(
|
||||||
({
|
({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
status = "default",
|
status: statusProp = "default",
|
||||||
type = "toast",
|
type: typeProp = "toast",
|
||||||
onClose,
|
onClose,
|
||||||
className = "",
|
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
|
// Determine background and border colors based on status and type
|
||||||
const getStatusStyles = () => {
|
const getStatusStyles = () => {
|
||||||
switch (status) {
|
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 {
|
export interface AlertProps {
|
||||||
title: string;
|
title: string;
|
||||||
description?: 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;
|
onClose?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
AskOrganizerProps,
|
AskOrganizerProps,
|
||||||
AskOrganizerVariant,
|
AskOrganizerVariant,
|
||||||
} from "./AskOrganizer.types";
|
} from "./AskOrganizer.types";
|
||||||
|
import { normalizeAskOrganizerVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const VARIANT_STYLES: Record<
|
const VARIANT_STYLES: Record<
|
||||||
AskOrganizerVariant,
|
AskOrganizerVariant,
|
||||||
@@ -39,9 +40,11 @@ const AskOrganizerContainer = memo<AskOrganizerProps>(
|
|||||||
buttonText,
|
buttonText,
|
||||||
buttonHref,
|
buttonHref,
|
||||||
className = "",
|
className = "",
|
||||||
variant = "centered",
|
variant: variantProp = "centered",
|
||||||
onContactClick,
|
onContactClick,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const variant = normalizeAskOrganizerVariant(variantProp) as AskOrganizerVariant;
|
||||||
const t = useTranslation();
|
const t = useTranslation();
|
||||||
const defaultButtonText = buttonText ?? t("askOrganizer.buttonText");
|
const defaultButtonText = buttonText ?? t("askOrganizer.buttonText");
|
||||||
const defaultButtonHref = buttonHref ?? t("askOrganizer.buttonHref");
|
const defaultButtonHref = buttonHref ?? t("askOrganizer.buttonHref");
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ export type AskOrganizerVariant =
|
|||||||
| "centered"
|
| "centered"
|
||||||
| "left-aligned"
|
| "left-aligned"
|
||||||
| "compact"
|
| "compact"
|
||||||
| "inverse";
|
| "inverse"
|
||||||
|
| "Centered"
|
||||||
|
| "Left-Aligned"
|
||||||
|
| "Compact"
|
||||||
|
| "Inverse";
|
||||||
|
|
||||||
export interface AskOrganizerProps {
|
export interface AskOrganizerProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -13,6 +17,10 @@ export interface AskOrganizerProps {
|
|||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
buttonHref?: string;
|
buttonHref?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/**
|
||||||
|
* Ask organizer variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
|
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||||
|
*/
|
||||||
variant?: AskOrganizerVariant;
|
variant?: AskOrganizerVariant;
|
||||||
onContactClick?: (_data: {
|
onContactClick?: (_data: {
|
||||||
event: string;
|
event: string;
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import { memo } from "react";
|
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> {
|
interface AvatarProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||||
src: string;
|
src: string;
|
||||||
alt: 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;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Avatar = memo<AvatarProps>(
|
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> = {
|
const sizeStyles: Record<string, string> = {
|
||||||
small: "w-[var(--spacing-scale-016)] h-[var(--spacing-scale-016)]",
|
small: "w-[var(--spacing-scale-016)] h-[var(--spacing-scale-016)]",
|
||||||
medium: "w-[18px] h-[18px]",
|
medium: "w-[18px] h-[18px]",
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import { memo } from "react";
|
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> {
|
interface AvatarContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
children?: React.ReactNode;
|
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;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvatarContainer = memo<AvatarContainerProps>(
|
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> = {
|
const sizeStyles: Record<string, string> = {
|
||||||
small: "flex -space-x-[var(--spacing-scale-008)]",
|
small: "flex -space-x-[var(--spacing-scale-008)]",
|
||||||
medium: "flex -space-x-[9px]",
|
medium: "flex -space-x-[9px]",
|
||||||
|
|||||||
+17
-12
@@ -1,17 +1,19 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
import type { VariantValue, SizeValue } from "../../lib/propNormalization";
|
||||||
|
import { normalizeVariant, normalizeSize } from "../../lib/propNormalization";
|
||||||
|
|
||||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
variant?:
|
/**
|
||||||
| "filled"
|
* Button variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
| "filled-inverse"
|
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||||
| "outline"
|
*/
|
||||||
| "outline-inverse"
|
variant?: VariantValue;
|
||||||
| "ghost"
|
/**
|
||||||
| "ghost-inverse"
|
* Button size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
| "danger"
|
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||||
| "danger-inverse";
|
*/
|
||||||
size?: "xsmall" | "small" | "medium" | "large" | "xlarge";
|
size?: SizeValue;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
type?: "button" | "submit" | "reset";
|
type?: "button" | "submit" | "reset";
|
||||||
@@ -27,8 +29,8 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|||||||
const Button = memo<ButtonProps>(
|
const Button = memo<ButtonProps>(
|
||||||
({
|
({
|
||||||
children,
|
children,
|
||||||
variant = "filled",
|
variant: variantProp = "filled",
|
||||||
size = "xsmall",
|
size: sizeProp = "xsmall",
|
||||||
className = "",
|
className = "",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
type = "button",
|
type = "button",
|
||||||
@@ -39,6 +41,9 @@ const Button = memo<ButtonProps>(
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const variant = normalizeVariant(variantProp);
|
||||||
|
const size = normalizeSize(sizeProp);
|
||||||
const sizeStyles: Record<string, string> = {
|
const sizeStyles: Record<string, string> = {
|
||||||
xsmall:
|
xsmall:
|
||||||
"p-[var(--spacing-scale-006)] gap-[var(--spacing-scale-002)]",
|
"p-[var(--spacing-scale-006)] gap-[var(--spacing-scale-002)]",
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { memo } from "react";
|
|||||||
import { useComponentId } from "../../hooks";
|
import { useComponentId } from "../../hooks";
|
||||||
import { CheckboxView } from "./Checkbox.view";
|
import { CheckboxView } from "./Checkbox.view";
|
||||||
import type { CheckboxProps } from "./Checkbox.types";
|
import type { CheckboxProps } from "./Checkbox.types";
|
||||||
|
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const CheckboxContainer = memo<CheckboxProps>(
|
const CheckboxContainer = memo<CheckboxProps>(
|
||||||
({
|
({
|
||||||
checked = false,
|
checked = false,
|
||||||
mode = "standard",
|
mode: modeProp = "standard",
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
label,
|
label,
|
||||||
className = "",
|
className = "",
|
||||||
@@ -20,6 +21,10 @@ const CheckboxContainer = memo<CheckboxProps>(
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const mode = normalizeMode(modeProp);
|
||||||
|
const state = normalizeState(stateProp);
|
||||||
|
|
||||||
const isInverse = mode === "inverse";
|
const isInverse = mode === "inverse";
|
||||||
const isStandard = mode === "standard";
|
const isStandard = mode === "standard";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface CheckboxProps {
|
export interface CheckboxProps {
|
||||||
checked?: boolean;
|
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;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -3,17 +3,20 @@
|
|||||||
import { memo, useCallback, useId, useState } from "react";
|
import { memo, useCallback, useId, useState } from "react";
|
||||||
import { CheckboxGroupView } from "./CheckboxGroup.view";
|
import { CheckboxGroupView } from "./CheckboxGroup.view";
|
||||||
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
|
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
|
||||||
|
import { normalizeMode } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const CheckboxGroupContainer = ({
|
const CheckboxGroupContainer = ({
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
mode = "standard",
|
mode: modeProp = "standard",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
options = [],
|
options = [],
|
||||||
className = "",
|
className = "",
|
||||||
...props
|
...props
|
||||||
}: CheckboxGroupProps) => {
|
}: CheckboxGroupProps) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const mode = normalizeMode(modeProp);
|
||||||
// Generate unique ID for accessibility if not provided
|
// Generate unique ID for accessibility if not provided
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
const groupId = name || `checkbox-group-${generatedId}`;
|
const groupId = name || `checkbox-group-${generatedId}`;
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ export interface CheckboxOption {
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import type { ModeValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface CheckboxGroupProps {
|
export interface CheckboxGroupProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (_data: { value: string[] }) => void;
|
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;
|
disabled?: boolean;
|
||||||
options?: CheckboxOption[];
|
options?: CheckboxOption[];
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { memo } from "react";
|
|||||||
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
||||||
import ContentContainerView from "./ContentContainer.view";
|
import ContentContainerView from "./ContentContainer.view";
|
||||||
import type { ContentContainerProps } from "./ContentContainer.types";
|
import type { ContentContainerProps } from "./ContentContainer.types";
|
||||||
|
import { normalizeContentContainerSize } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ContentContainerContainer = memo<ContentContainerProps>(
|
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
|
// Get the corresponding icon based on the same logic as background images
|
||||||
const getIconImage = (slug: string): string => {
|
const getIconImage = (slug: string): string => {
|
||||||
const icons = [
|
const icons = [
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import type { BlogPost } from "../../../lib/content";
|
import type { BlogPost } from "../../../lib/content";
|
||||||
|
|
||||||
|
export type ContentContainerSizeValue = "xs" | "responsive" | "Xs" | "Responsive";
|
||||||
|
|
||||||
export interface ContentContainerProps {
|
export interface ContentContainerProps {
|
||||||
post: BlogPost;
|
post: BlogPost;
|
||||||
width?: string;
|
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 {
|
export interface ContentContainerViewProps {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import ContentLockupView from "./ContentLockup.view";
|
import ContentLockupView from "./ContentLockup.view";
|
||||||
import type { ContentLockupProps, VariantStyle } from "./ContentLockup.types";
|
import type { ContentLockupProps, VariantStyle } from "./ContentLockup.types";
|
||||||
|
import { normalizeContentLockupVariant, normalizeAlignment } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ContentLockupContainer = memo<ContentLockupProps>(
|
const ContentLockupContainer = memo<ContentLockupProps>(
|
||||||
({
|
({
|
||||||
@@ -11,12 +12,15 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
|||||||
description,
|
description,
|
||||||
ctaText,
|
ctaText,
|
||||||
buttonClassName = "",
|
buttonClassName = "",
|
||||||
variant = "hero",
|
variant: variantProp = "hero",
|
||||||
linkText,
|
linkText,
|
||||||
linkHref,
|
linkHref,
|
||||||
alignment = "center",
|
alignment: alignmentProp = "center",
|
||||||
titleId,
|
titleId,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const variant = normalizeContentLockupVariant(variantProp);
|
||||||
|
const alignment = normalizeAlignment(alignmentProp);
|
||||||
// Variant-specific styling
|
// Variant-specific styling
|
||||||
const variantStyles: Record<string, VariantStyle> = {
|
const variantStyles: Record<string, VariantStyle> = {
|
||||||
hero: {
|
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 {
|
export interface ContentLockupProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
@@ -5,10 +21,18 @@ export interface ContentLockupProps {
|
|||||||
ctaText?: string;
|
ctaText?: string;
|
||||||
ctaHref?: string;
|
ctaHref?: string;
|
||||||
buttonClassName?: 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;
|
linkText?: string;
|
||||||
linkHref?: 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.
|
* Optional id to attach to the primary title heading.
|
||||||
* Useful when a parent section uses aria-labelledby.
|
* Useful when a parent section uses aria-labelledby.
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { memo } from "react";
|
|||||||
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
||||||
import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view";
|
import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view";
|
||||||
import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types";
|
import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types";
|
||||||
|
import { normalizeContentThumbnailVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
|
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
|
// Get article-specific background image from frontmatter
|
||||||
const getBackgroundImage = (
|
const getBackgroundImage = (
|
||||||
post: ContentThumbnailTemplateProps["post"],
|
post: ContentThumbnailTemplateProps["post"],
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import type { BlogPost } from "../../../lib/content";
|
import type { BlogPost } from "../../../lib/content";
|
||||||
|
|
||||||
|
export type ContentThumbnailTemplateVariantValue = "vertical" | "horizontal" | "Vertical" | "Horizontal";
|
||||||
|
|
||||||
export interface ContentThumbnailTemplateProps {
|
export interface ContentThumbnailTemplateProps {
|
||||||
post: BlogPost;
|
post: BlogPost;
|
||||||
className?: string;
|
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[];
|
slugOrder?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { forwardRef, memo, useCallback } from "react";
|
import { forwardRef, memo, useCallback } from "react";
|
||||||
import { ContextMenuItemView } from "./ContextMenuItem.view";
|
import { ContextMenuItemView } from "./ContextMenuItem.view";
|
||||||
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
||||||
|
import { normalizeContextMenuItemSize } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ContextMenuItemContainer = forwardRef<
|
const ContextMenuItemContainer = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@@ -16,11 +17,13 @@ const ContextMenuItemContainer = forwardRef<
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
className = "",
|
className = "",
|
||||||
onClick,
|
onClick,
|
||||||
size = "medium",
|
size: sizeProp = "medium",
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const size = normalizeContextMenuItemSize(sizeProp);
|
||||||
const getTextSize = (): string => {
|
const getTextSize = (): string => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export type ContextMenuItemSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||||
|
|
||||||
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
@@ -7,7 +9,11 @@ export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElemen
|
|||||||
onClick?: (
|
onClick?: (
|
||||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||||
) => void;
|
) => 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 {
|
export interface ContextMenuItemViewProps {
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo } from "react";
|
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 {
|
interface ImagePlaceholderProps {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
text?: string;
|
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;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,9 +38,11 @@ const ImagePlaceholder = memo<ImagePlaceholderProps>(
|
|||||||
width = 260,
|
width = 260,
|
||||||
height = 390,
|
height = 390,
|
||||||
text = "Blog Image",
|
text = "Blog Image",
|
||||||
color = "blue",
|
color: colorProp = "blue",
|
||||||
className = "",
|
className = "",
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const color = normalizeImagePlaceholderColor(colorProp);
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
blue: "bg-blue-500",
|
blue: "bg-blue-500",
|
||||||
green: "bg-green-500",
|
green: "bg-green-500",
|
||||||
|
|||||||
@@ -2,15 +2,32 @@
|
|||||||
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { useTranslation } from "../contexts/MessagesContext";
|
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> {
|
interface MenuBarProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
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>(
|
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 t = useTranslation("menuBar");
|
||||||
const sizeStyles: Record<string, string> = {
|
const sizeStyles: Record<string, string> = {
|
||||||
xsmall:
|
xsmall:
|
||||||
|
|||||||
@@ -3,19 +3,24 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import MenuBarItemView from "./MenuBarItem.view";
|
import MenuBarItemView from "./MenuBarItem.view";
|
||||||
import type { MenuBarItemProps } from "./MenuBarItem.types";
|
import type { MenuBarItemProps } from "./MenuBarItem.types";
|
||||||
|
import { normalizeMenuBarItemVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const MenuBarItemContainer = memo<MenuBarItemProps>(
|
const MenuBarItemContainer = memo<MenuBarItemProps>(
|
||||||
({
|
({
|
||||||
href = "#",
|
href = "#",
|
||||||
children,
|
children,
|
||||||
variant = "default",
|
variant: variantProp = "default",
|
||||||
size = "default",
|
size: sizeProp = "default",
|
||||||
className = "",
|
className = "",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
...props
|
...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> = {
|
const variantStyles: Record<string, string> = {
|
||||||
default:
|
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",
|
"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> {
|
export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||||
href?: string;
|
href?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
variant?: "default" | "home";
|
/**
|
||||||
size?:
|
* Menu bar item variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
| "default"
|
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||||
| "xsmall"
|
*/
|
||||||
| "xsmallUseCases"
|
variant?: MenuBarItemVariantValue;
|
||||||
| "home"
|
/**
|
||||||
| "homeMd"
|
* Menu bar item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
| "homeUseCases"
|
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||||
| "large"
|
*/
|
||||||
| "largeUseCases"
|
size?: MenuBarItemSizeValue;
|
||||||
| "homeXlarge"
|
|
||||||
| "xlarge";
|
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|||||||
@@ -3,18 +3,22 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import NavigationItemView from "./NavigationItem.view";
|
import NavigationItemView from "./NavigationItem.view";
|
||||||
import type { NavigationItemProps } from "./NavigationItem.types";
|
import type { NavigationItemProps } from "./NavigationItem.types";
|
||||||
|
import { normalizeNavigationItemVariant, normalizeNavigationItemSize } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const NavigationItemContainer = memo<NavigationItemProps>(
|
const NavigationItemContainer = memo<NavigationItemProps>(
|
||||||
({
|
({
|
||||||
href = "#",
|
href = "#",
|
||||||
children,
|
children,
|
||||||
variant = "default",
|
variant: variantProp = "default",
|
||||||
size = "default",
|
size: sizeProp = "default",
|
||||||
className = "",
|
className = "",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const variant = normalizeNavigationItemVariant(variantProp);
|
||||||
|
const size = normalizeNavigationItemSize(sizeProp);
|
||||||
// Variant styles
|
// Variant styles
|
||||||
const variantStyles: Record<string, string> = {
|
const variantStyles: Record<string, string> = {
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
export type NavigationItemVariantValue = "default" | "Default";
|
||||||
|
export type NavigationItemSizeValue = "default" | "xsmall" | "Default" | "XSmall";
|
||||||
|
|
||||||
export interface NavigationItemProps extends Omit<
|
export interface NavigationItemProps extends Omit<
|
||||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||||
"isActive"
|
"isActive"
|
||||||
> {
|
> {
|
||||||
href?: string;
|
href?: string;
|
||||||
children?: React.ReactNode;
|
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;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|||||||
@@ -3,21 +3,39 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import SectionNumber from "./SectionNumber";
|
import SectionNumber from "./SectionNumber";
|
||||||
|
|
||||||
|
import { normalizeNumberCardSize } from "../../lib/propNormalization";
|
||||||
|
|
||||||
|
export type NumberCardSizeValue =
|
||||||
|
| "Small"
|
||||||
|
| "Medium"
|
||||||
|
| "Large"
|
||||||
|
| "XLarge"
|
||||||
|
| "small"
|
||||||
|
| "medium"
|
||||||
|
| "large"
|
||||||
|
| "xlarge";
|
||||||
|
|
||||||
interface NumberCardProps {
|
interface NumberCardProps {
|
||||||
number: number;
|
number: number;
|
||||||
text: string;
|
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;
|
iconShape?: string;
|
||||||
iconColor?: string;
|
iconColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumberCard = memo<NumberCardProps>(({ number, text, size }) => {
|
const NumberCard = memo<NumberCardProps>(({ number, text, size: sizeProp }) => {
|
||||||
// Base classes common to all sizes
|
// Base classes common to all sizes
|
||||||
const baseClasses = "bg-[var(--color-surface-inverse-primary)] rounded-[12px] shadow-lg";
|
const baseClasses = "bg-[var(--color-surface-inverse-primary)] rounded-[12px] shadow-lg";
|
||||||
|
|
||||||
// If size prop is provided, use explicit size classes
|
// If size prop is provided, use explicit size classes
|
||||||
// Otherwise, use responsive breakpoints for backward compatibility
|
// 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
|
// Size-specific classes
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
Small: "flex flex-col items-end justify-center gap-4 p-5 relative",
|
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 { logger } from "../../../lib/logger";
|
||||||
import QuoteBlockView from "./QuoteBlock.view";
|
import QuoteBlockView from "./QuoteBlock.view";
|
||||||
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
|
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
|
||||||
|
import { normalizeQuoteBlockVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const QuoteBlockContainer = memo<QuoteBlockProps>(
|
const QuoteBlockContainer = memo<QuoteBlockProps>(
|
||||||
({
|
({
|
||||||
variant = "standard",
|
variant: variantProp = "standard",
|
||||||
className = "",
|
className = "",
|
||||||
quote = "The rules of decision-making must be open and available to everyone, and this can happen only if they are formalized.",
|
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",
|
author = "Jo Freeman",
|
||||||
@@ -17,6 +18,8 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
|
|||||||
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
|
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
|
||||||
onError,
|
onError,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const variant = normalizeQuoteBlockVariant(variantProp);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
const [imageLoading, setImageLoading] = useState(true);
|
const [imageLoading, setImageLoading] = useState(true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
export type QuoteBlockVariantValue =
|
||||||
|
| "compact"
|
||||||
|
| "standard"
|
||||||
|
| "extended"
|
||||||
|
| "Compact"
|
||||||
|
| "Standard"
|
||||||
|
| "Extended";
|
||||||
|
|
||||||
export interface QuoteBlockProps {
|
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;
|
className?: string;
|
||||||
quote?: string;
|
quote?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
import { memo, useCallback, useId } from "react";
|
import { memo, useCallback, useId } from "react";
|
||||||
import { RadioButtonView } from "./RadioButton.view";
|
import { RadioButtonView } from "./RadioButton.view";
|
||||||
import type { RadioButtonProps } from "./RadioButton.types";
|
import type { RadioButtonProps } from "./RadioButton.types";
|
||||||
|
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const RadioButtonContainer = ({
|
const RadioButtonContainer = ({
|
||||||
checked = false,
|
checked = false,
|
||||||
mode = "standard",
|
mode: modeProp = "standard",
|
||||||
state = "default", // This state prop is now only for static display in Storybook/Preview
|
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,
|
disabled = false,
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -17,6 +19,13 @@ const RadioButtonContainer = ({
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
className = "",
|
className = "",
|
||||||
}: RadioButtonProps) => {
|
}: 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 isInverse = mode === "inverse";
|
||||||
const isStandard = mode === "standard";
|
const isStandard = mode === "standard";
|
||||||
|
|
||||||
@@ -113,7 +122,7 @@ const RadioButtonContainer = ({
|
|||||||
radioId={radioId}
|
radioId={radioId}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
state={state} // Passed for static display in Storybook/Preview
|
state={normalizedState} // Normalized state (handles "selected" from Figma)
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
|
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface RadioButtonProps {
|
export interface RadioButtonProps {
|
||||||
checked?: boolean;
|
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;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange?: (_data: { checked: boolean; value?: string }) => void;
|
onChange?: (_data: { checked: boolean; value?: string }) => void;
|
||||||
@@ -16,7 +31,7 @@ export interface RadioButtonViewProps {
|
|||||||
radioId: string;
|
radioId: string;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
mode: "standard" | "inverse";
|
mode: "standard" | "inverse";
|
||||||
state: "default" | "hover" | "focus";
|
state: "default" | "hover" | "focus" | "selected";
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|||||||
@@ -3,18 +3,26 @@
|
|||||||
import { memo, useCallback, useId } from "react";
|
import { memo, useCallback, useId } from "react";
|
||||||
import { RadioGroupView } from "./RadioGroup.view";
|
import { RadioGroupView } from "./RadioGroup.view";
|
||||||
import type { RadioGroupProps } from "./RadioGroup.types";
|
import type { RadioGroupProps } from "./RadioGroup.types";
|
||||||
|
import { normalizeMode, normalizeState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const RadioGroupContainer = ({
|
const RadioGroupContainer = ({
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
mode = "standard",
|
mode: modeProp = "standard",
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
options = [],
|
options = [],
|
||||||
className = "",
|
className = "",
|
||||||
...props
|
...props
|
||||||
}: RadioGroupProps) => {
|
}: 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
|
// Generate unique ID for accessibility if not provided
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
const groupId = name || `radio-group-${generatedId}`;
|
const groupId = name || `radio-group-${generatedId}`;
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ export interface RadioOption {
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import type { ModeValue, StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface RadioGroupProps {
|
export interface RadioGroupProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (_data: { value: string }) => void;
|
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;
|
disabled?: boolean;
|
||||||
options?: RadioOption[];
|
options?: RadioOption[];
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
import { normalizeSectionHeaderVariant } from "../../lib/propNormalization";
|
||||||
|
|
||||||
|
export type SectionHeaderVariantValue = "default" | "multi-line" | "Default" | "Multi-Line";
|
||||||
|
|
||||||
interface SectionHeaderProps {
|
interface SectionHeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
titleLg?: 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>(
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
|
|||||||
@@ -16,13 +16,16 @@ import React, {
|
|||||||
import { useClickOutside } from "../../hooks";
|
import { useClickOutside } from "../../hooks";
|
||||||
import { SelectInputView } from "./SelectInput.view";
|
import { SelectInputView } from "./SelectInput.view";
|
||||||
import type { SelectInputProps } from "./SelectInput.types";
|
import type { SelectInputProps } from "./SelectInput.types";
|
||||||
|
import { normalizeState, normalizeSmallMediumLargeSize, normalizeLabelVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
state: externalState = "default",
|
labelVariant: labelVariantProp,
|
||||||
|
size: sizeProp,
|
||||||
|
state: externalStateProp = "default",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
error = false,
|
error = false,
|
||||||
placeholder = "Choose an option",
|
placeholder = "Choose an option",
|
||||||
@@ -35,6 +38,12 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
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 generatedId = useId();
|
||||||
const selectId = id || `select-input-${generatedId}`;
|
const selectId = id || `select-input-${generatedId}`;
|
||||||
const labelId = `${selectId}-label`;
|
const labelId = `${selectId}-label`;
|
||||||
|
|||||||
@@ -5,12 +5,29 @@ export interface SelectOptionData {
|
|||||||
label: string;
|
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 {
|
export interface SelectInputProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
labelVariant?: "default" | "horizontal";
|
/**
|
||||||
size?: "small" | "medium" | "large";
|
* Label variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
state?: "default" | "hover" | "focus";
|
* 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;
|
disabled?: boolean;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { forwardRef, memo, useCallback } from "react";
|
import { forwardRef, memo, useCallback } from "react";
|
||||||
import { SelectOptionView } from "./SelectOption.view";
|
import { SelectOptionView } from "./SelectOption.view";
|
||||||
import type { SelectOptionProps } from "./SelectOption.types";
|
import type { SelectOptionProps } from "./SelectOption.types";
|
||||||
|
import { normalizeContextMenuItemSize } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||||
(
|
(
|
||||||
@@ -12,11 +13,13 @@ const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
className = "",
|
className = "",
|
||||||
onClick,
|
onClick,
|
||||||
size = "medium",
|
size: sizeProp = "medium",
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const size = normalizeContextMenuItemSize(sizeProp);
|
||||||
const getTextSize = (): string => {
|
const getTextSize = (): string => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export type SelectOptionSizeValue = "small" | "medium" | "large" | "Small" | "Medium" | "Large";
|
||||||
|
|
||||||
export interface SelectOptionProps {
|
export interface SelectOptionProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
@@ -6,7 +8,11 @@ export interface SelectOptionProps {
|
|||||||
onClick?: (
|
onClick?: (
|
||||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||||
) => void;
|
) => 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 {
|
export interface SelectOptionViewProps {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { memo, useCallback, useId, forwardRef } from "react";
|
import { memo, useCallback, useId, forwardRef } from "react";
|
||||||
import { SwitchView } from "./Switch.view";
|
import { SwitchView } from "./Switch.view";
|
||||||
import type { SwitchProps } from "./Switch.types";
|
import type { SwitchProps } from "./Switch.types";
|
||||||
|
import { normalizeState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const SwitchContainer = memo(
|
const SwitchContainer = memo(
|
||||||
forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
|
forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
|
||||||
@@ -11,12 +12,15 @@ const SwitchContainer = memo(
|
|||||||
onChange,
|
onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
label,
|
label,
|
||||||
className = "",
|
className = "",
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const state = normalizeState(stateProp);
|
||||||
|
|
||||||
const switchId = useId();
|
const switchId = useId();
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface SwitchProps extends Omit<
|
export interface SwitchProps extends Omit<
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
"onChange"
|
"onChange"
|
||||||
@@ -10,7 +12,11 @@ export interface SwitchProps extends Omit<
|
|||||||
) => void;
|
) => void;
|
||||||
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||||
onBlur?: (_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;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import { memo, forwardRef } from "react";
|
|||||||
import { useComponentId, useFormField } from "../../hooks";
|
import { useComponentId, useFormField } from "../../hooks";
|
||||||
import { TextAreaView } from "./TextArea.view";
|
import { TextAreaView } from "./TextArea.view";
|
||||||
import type { TextAreaProps } from "./TextArea.types";
|
import type { TextAreaProps } from "./TextArea.types";
|
||||||
|
import { normalizeInputState, normalizeSmallMediumLargeSize, normalizeLabelVariant } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
size = "medium",
|
size: sizeProp = "medium",
|
||||||
labelVariant = "default",
|
labelVariant: labelVariantProp = "default",
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
error = false,
|
error = false,
|
||||||
label,
|
label,
|
||||||
@@ -27,6 +28,10 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
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
|
// Generate unique ID for accessibility if not provided
|
||||||
const { id: textareaId, labelId } = useComponentId("textarea", id);
|
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<
|
export interface TextAreaProps extends Omit<
|
||||||
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
"size" | "onChange" | "onFocus" | "onBlur"
|
"size" | "onChange" | "onFocus" | "onBlur"
|
||||||
> {
|
> {
|
||||||
size?: "small" | "medium" | "large";
|
/**
|
||||||
labelVariant?: "default" | "horizontal";
|
* Text area size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||||
state?: "default" | "active" | "hover" | "focus";
|
* 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;
|
disabled?: boolean;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { memo, forwardRef, useState, useRef } from "react";
|
|||||||
import { useComponentId, useFormField } from "../../hooks";
|
import { useComponentId, useFormField } from "../../hooks";
|
||||||
import { TextInputView } from "./TextInput.view";
|
import { TextInputView } from "./TextInput.view";
|
||||||
import type { TextInputProps } from "./TextInput.types";
|
import type { TextInputProps } from "./TextInput.types";
|
||||||
|
import { normalizeInputState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
state: externalState = "default",
|
state: externalStateProp = "default",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
error = false,
|
error = false,
|
||||||
label,
|
label,
|
||||||
@@ -26,6 +27,9 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const externalState = normalizeInputState(externalStateProp);
|
||||||
|
|
||||||
// Generate unique ID for accessibility if not provided
|
// Generate unique ID for accessibility if not provided
|
||||||
const { id: inputId, labelId } = useComponentId("text-input", id);
|
const { id: inputId, labelId } = useComponentId("text-input", id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import type { InputStateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface TextInputProps extends Omit<
|
export interface TextInputProps extends Omit<
|
||||||
React.InputHTMLAttributes<HTMLInputElement>,
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
"size" | "onChange" | "onFocus" | "onBlur"
|
"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;
|
disabled?: boolean;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { memo, useCallback, useId, forwardRef } from "react";
|
import { memo, useCallback, useId, forwardRef } from "react";
|
||||||
import { ToggleView } from "./Toggle.view";
|
import { ToggleView } from "./Toggle.view";
|
||||||
import type { ToggleProps } from "./Toggle.types";
|
import type { ToggleProps } from "./Toggle.types";
|
||||||
|
import { normalizeState } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||||
(
|
(
|
||||||
@@ -13,7 +14,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
|||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
showIcon = false,
|
showIcon = false,
|
||||||
showText = false,
|
showText = false,
|
||||||
icon = "I",
|
icon = "I",
|
||||||
@@ -23,6 +24,8 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const state = normalizeState(stateProp);
|
||||||
const toggleId = useId();
|
const toggleId = useId();
|
||||||
const labelId = useId();
|
const labelId = useId();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
export interface ToggleProps extends Omit<
|
export interface ToggleProps extends Omit<
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
"onChange"
|
"onChange"
|
||||||
@@ -12,7 +14,11 @@ export interface ToggleProps extends Omit<
|
|||||||
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||||
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||||
disabled?: boolean;
|
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;
|
showIcon?: boolean;
|
||||||
showText?: boolean;
|
showText?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
import { memo, useCallback, useId, forwardRef } from "react";
|
import { memo, useCallback, useId, forwardRef } from "react";
|
||||||
import { ToggleGroupView } from "./ToggleGroup.view";
|
import { ToggleGroupView } from "./ToggleGroup.view";
|
||||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||||
|
import { normalizeToggleState, normalizeToggleGroupPosition } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const ToggleGroupContainer = memo(
|
const ToggleGroupContainer = memo(
|
||||||
forwardRef<HTMLButtonElement, ToggleGroupProps>((props, _ref) => {
|
forwardRef<HTMLButtonElement, ToggleGroupProps>((props, _ref) => {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
className = "",
|
className = "",
|
||||||
position = "left",
|
position: positionProp = "left",
|
||||||
state = "default",
|
state: stateProp = "default",
|
||||||
showText = true,
|
showText = true,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -19,6 +20,10 @@ const ToggleGroupContainer = memo(
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||||
|
const position = normalizeToggleGroupPosition(positionProp);
|
||||||
|
const state = normalizeToggleState(stateProp);
|
||||||
|
|
||||||
const groupId = useId();
|
const groupId = useId();
|
||||||
|
|
||||||
// Position-based styling for border radius
|
// Position-based styling for border radius
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
|
import type { StateValue } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
|
export type ToggleGroupPositionValue = "left" | "middle" | "right" | "Left" | "Middle" | "Right";
|
||||||
|
|
||||||
export interface ToggleGroupProps extends Omit<
|
export interface ToggleGroupProps extends Omit<
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
"onChange"
|
"onChange"
|
||||||
> {
|
> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
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;
|
showText?: boolean;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
onChange?: (
|
onChange?: (
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
import { memo, useState } from "react";
|
import { memo, useState } from "react";
|
||||||
import { TooltipView } from "./Tooltip.view";
|
import { TooltipView } from "./Tooltip.view";
|
||||||
import type { TooltipProps } from "./Tooltip.types";
|
import type { TooltipProps } from "./Tooltip.types";
|
||||||
|
import { normalizeTooltipPosition } from "../../../lib/propNormalization";
|
||||||
|
|
||||||
const TooltipContainer = memo<TooltipProps>(
|
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);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
|
export type TooltipPositionValue = "top" | "bottom" | "Top" | "Bottom";
|
||||||
|
|
||||||
export interface TooltipProps {
|
export interface TooltipProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
text: string;
|
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;
|
className?: string;
|
||||||
disabled?: boolean;
|
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: {
|
mode: {
|
||||||
control: "select",
|
control: "select",
|
||||||
options: ["standard", "inverse"],
|
options: ["standard", "inverse", "Standard", "Inverse"],
|
||||||
description: "Visual mode of the checkbox",
|
description: "Visual mode of the checkbox (case-insensitive: accepts both lowercase and PascalCase)",
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
control: "select",
|
control: "select",
|
||||||
options: ["default", "hover", "focus"],
|
options: ["default", "hover", "focus", "Default", "Hover", "Focus"],
|
||||||
description: "Interaction state for static display",
|
description: "Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
control: "boolean",
|
control: "boolean",
|
||||||
@@ -206,3 +206,53 @@ export const AllModes = () => {
|
|||||||
</div>
|
</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: {
|
mode: {
|
||||||
control: "select",
|
control: "select",
|
||||||
options: ["standard", "inverse"],
|
options: ["standard", "inverse", "Standard", "Inverse"],
|
||||||
description: "Visual mode of the radio button",
|
description: "Visual mode of the radio button (case-insensitive: accepts both lowercase and PascalCase)",
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
control: "select",
|
control: "select",
|
||||||
options: ["default", "hover", "focus"],
|
options: ["default", "hover", "focus", "selected", "Default", "Hover", "Focus", "Selected"],
|
||||||
description: "Interaction state for static display",
|
description: "Interaction state for static display (case-insensitive: accepts both lowercase and PascalCase)",
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
control: "boolean",
|
control: "boolean",
|
||||||
@@ -247,3 +247,53 @@ export const InverseAllStates = () => {
|
|||||||
</div>
|
</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", () => {
|
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']");
|
const container = document.querySelector("div[class*='relative z-20']");
|
||||||
expect(container).toHaveStyle("width: 300px");
|
expect(container).toHaveStyle("width: 300px");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies default width when not specified", () => {
|
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']");
|
const container = document.querySelector("div[class*='relative z-20']");
|
||||||
expect(container).toHaveStyle("width: 200px");
|
expect(container).toHaveStyle("width: 200px");
|
||||||
@@ -183,8 +183,8 @@ describe("ContentContainer", () => {
|
|||||||
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies correct responsive sizing for sm breakpoint", () => {
|
it("applies correct responsive sizing for xs breakpoint", () => {
|
||||||
render(<ContentContainer post={mockPost} size="sm" />);
|
render(<ContentContainer post={mockPost} size="xs" />);
|
||||||
|
|
||||||
const icon = screen.getByAltText("Icon for Test Article Title");
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
||||||
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
||||||
@@ -196,8 +196,8 @@ describe("ContentContainer", () => {
|
|||||||
expect(description).toHaveClass("text-[12px]", "leading-[16px]");
|
expect(description).toHaveClass("text-[12px]", "leading-[16px]");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies correct responsive sizing for md breakpoint", () => {
|
it("applies correct responsive sizing for responsive breakpoint", () => {
|
||||||
render(<ContentContainer post={mockPost} size="md" />);
|
render(<ContentContainer post={mockPost} size="responsive" />);
|
||||||
|
|
||||||
const icon = screen.getByAltText("Icon for Test Article Title");
|
const icon = screen.getByAltText("Icon for Test Article Title");
|
||||||
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
expect(icon).toHaveClass("w-[60px]", "h-[30px]");
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe("NumberCard Component", () => {
|
|||||||
const card = screen
|
const card = screen
|
||||||
.getByText("Test Card Text")
|
.getByText("Test Card Text")
|
||||||
.closest("div").parentElement;
|
.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", () => {
|
it("applies proper responsive spacing when size is not specified", () => {
|
||||||
@@ -66,7 +66,7 @@ describe("NumberCard Component", () => {
|
|||||||
const card = screen
|
const card = screen
|
||||||
.getByText("Test Card Text")
|
.getByText("Test Card Text")
|
||||||
.closest("div").parentElement;
|
.closest("div").parentElement;
|
||||||
expect(card).toHaveClass("lg:h-[238px]");
|
expect(card).toHaveClass("lg:h-[238px]", "lg:relative");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies proper background and shadow", () => {
|
it("applies proper background and shadow", () => {
|
||||||
@@ -128,8 +128,11 @@ describe("NumberCard Component", () => {
|
|||||||
expect(textElement).toHaveClass(
|
expect(textElement).toHaveClass(
|
||||||
"text-[24px]",
|
"text-[24px]",
|
||||||
"sm:text-[24px]",
|
"sm:text-[24px]",
|
||||||
|
"sm:leading-[24px]",
|
||||||
"lg:text-[24px]",
|
"lg:text-[24px]",
|
||||||
|
"lg:leading-[24px]",
|
||||||
"xl:text-[32px]",
|
"xl:text-[32px]",
|
||||||
|
"xl:leading-[32px]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -156,7 +159,7 @@ describe("NumberCard Component", () => {
|
|||||||
.closest("div").parentElement;
|
.closest("div").parentElement;
|
||||||
|
|
||||||
// Mobile first approach
|
// Mobile first approach
|
||||||
expect(card).toHaveClass("flex-col", "gap-4", "p-5");
|
expect(card).toHaveClass("flex", "flex-col", "gap-4", "p-5");
|
||||||
|
|
||||||
// Small breakpoint
|
// Small breakpoint
|
||||||
expect(card).toHaveClass(
|
expect(card).toHaveClass(
|
||||||
@@ -172,7 +175,9 @@ describe("NumberCard Component", () => {
|
|||||||
"lg:gap-[22px]",
|
"lg:gap-[22px]",
|
||||||
"lg:p-8",
|
"lg:p-8",
|
||||||
"lg:items-start",
|
"lg:items-start",
|
||||||
|
"lg:justify-end",
|
||||||
"lg:relative",
|
"lg:relative",
|
||||||
|
"lg:h-[238px]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user