App reorganization
This commit is contained in:
@@ -5,34 +5,26 @@ import type {
|
||||
ButtonPaletteValue,
|
||||
ButtonStateValue,
|
||||
} from "../../../lib/propNormalization";
|
||||
import {
|
||||
normalizeSize,
|
||||
normalizeButtonType,
|
||||
normalizeButtonPalette,
|
||||
} from "../../../lib/propNormalization";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Button type (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Button type (Figma prop).
|
||||
* @default "filled"
|
||||
*/
|
||||
buttonType?: ButtonTypeValue;
|
||||
/**
|
||||
* Button palette (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses "Invert", codebase uses "inverse" - both are supported.
|
||||
* Button palette (Figma prop).
|
||||
* @default "default"
|
||||
*/
|
||||
palette?: ButtonPaletteValue;
|
||||
/**
|
||||
* Button size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Button size.
|
||||
* @default "xsmall"
|
||||
*/
|
||||
size?: SizeValue;
|
||||
/**
|
||||
* Button state (Figma prop). Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Button state (Figma prop).
|
||||
* @default "default"
|
||||
*/
|
||||
state?: ButtonStateValue;
|
||||
@@ -83,12 +75,9 @@ const Button = memo<ButtonProps>(
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props
|
||||
const buttonType = normalizeButtonType(typeProp, "filled");
|
||||
const buttonPalette = normalizeButtonPalette(paletteProp, "default");
|
||||
const size = normalizeSize(sizeProp);
|
||||
// State prop is for Figma alignment - actual state is handled by CSS pseudo-classes
|
||||
// We accept it for API alignment but don't use it for styling (CSS handles states)
|
||||
const buttonType = typeProp ?? "filled";
|
||||
const buttonPalette = paletteProp ?? "default";
|
||||
const size = sizeProp;
|
||||
|
||||
// Map type + palette to variant string for styling (internal use only)
|
||||
const getVariantFromTypeAndPalette = (
|
||||
|
||||
@@ -3,80 +3,57 @@
|
||||
import { memo } from "react";
|
||||
import SectionNumber from "../sections/SectionNumber";
|
||||
|
||||
import { normalizeNumberCardSize } from "../../../lib/propNormalization";
|
||||
|
||||
export type NumberCardSizeValue =
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge"
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge";
|
||||
export type NumberCardSizeValue = "small" | "medium" | "large" | "xlarge";
|
||||
|
||||
interface NumberCardProps {
|
||||
number: number;
|
||||
text: string;
|
||||
/**
|
||||
* Number card size. Accepts both PascalCase (Figma default) and lowercase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses PascalCase - both are supported.
|
||||
*/
|
||||
size?: NumberCardSizeValue;
|
||||
iconShape?: string;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
const NumberCard = memo<NumberCardProps>(({ number, text, size: sizeProp }) => {
|
||||
// Base classes common to all sizes
|
||||
const baseClasses =
|
||||
"bg-[var(--color-surface-inverse-primary)] rounded-[12px] shadow-lg";
|
||||
|
||||
// If size prop is provided, use explicit size classes
|
||||
// Otherwise, use responsive breakpoints for backward compatibility
|
||||
if (sizeProp) {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeNumberCardSize(sizeProp);
|
||||
// Size-specific classes
|
||||
const size = sizeProp;
|
||||
const sizeClasses = {
|
||||
Small: "flex flex-col items-end justify-center gap-4 p-5 relative",
|
||||
Medium: "flex flex-row items-center gap-8 p-8 relative",
|
||||
Large:
|
||||
small: "flex flex-col items-end justify-center gap-4 p-5 relative",
|
||||
medium: "flex flex-row items-center gap-8 p-8 relative",
|
||||
large:
|
||||
"flex flex-col items-start justify-end gap-[22px] h-[238px] p-8 relative",
|
||||
XLarge:
|
||||
xlarge:
|
||||
"flex flex-col items-start justify-end gap-[22px] h-[238px] p-8 relative",
|
||||
};
|
||||
|
||||
// Text size classes
|
||||
const textClasses = {
|
||||
Small:
|
||||
small:
|
||||
"font-bricolage-grotesque font-medium text-[24px] leading-[32px] text-[#141414]",
|
||||
Medium:
|
||||
medium:
|
||||
"font-bricolage-grotesque font-medium text-[24px] leading-[24px] text-[#141414]",
|
||||
Large:
|
||||
large:
|
||||
"font-bricolage-grotesque font-medium text-[24px] leading-[24px] text-[#141414]",
|
||||
XLarge:
|
||||
xlarge:
|
||||
"font-bricolage-grotesque font-medium text-[32px] leading-[32px] text-[#141414]",
|
||||
};
|
||||
|
||||
// Section number wrapper classes - Small doesn't need a wrapper
|
||||
const sectionNumberWrapperClasses = {
|
||||
Small: "relative shrink-0",
|
||||
Medium: "flex justify-start flex-shrink-0",
|
||||
Large: "absolute top-8 right-8",
|
||||
XLarge: "absolute top-8 right-8",
|
||||
small: "relative shrink-0",
|
||||
medium: "flex justify-start flex-shrink-0",
|
||||
large: "absolute top-8 right-8",
|
||||
xlarge: "absolute top-8 right-8",
|
||||
};
|
||||
|
||||
// Content container classes
|
||||
const contentClasses = {
|
||||
Small: "min-w-full relative shrink-0",
|
||||
Medium: "flex-1",
|
||||
Large: "absolute bottom-8 left-8 right-16",
|
||||
XLarge: "absolute bottom-8 left-8 right-16",
|
||||
small: "min-w-full relative shrink-0",
|
||||
medium: "flex-1",
|
||||
large: "absolute bottom-8 left-8 right-16",
|
||||
xlarge: "absolute bottom-8 left-8 right-16",
|
||||
};
|
||||
|
||||
// Small variant has section number as direct child, others need wrapper
|
||||
if (size === "Small") {
|
||||
if (size === "small") {
|
||||
return (
|
||||
<div className={`${baseClasses} ${sizeClasses[size]}`}>
|
||||
{/* Section Number - Direct child for Small */}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import { RuleCardView } from "./RuleCard.view";
|
||||
import type { RuleCardProps } from "./RuleCard.types";
|
||||
import { normalizeRuleCardSize } from "../../../../lib/propNormalization";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -33,8 +32,7 @@ const RuleCardContainer = memo<RuleCardProps>(
|
||||
logoAlt,
|
||||
communityInitials,
|
||||
}) => {
|
||||
// Normalize size prop
|
||||
const size = normalizeRuleCardSize(sizeProp, "L");
|
||||
const size = sizeProp ?? "L";
|
||||
|
||||
const handleClick = () => {
|
||||
// Basic analytics event tracking
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface RuleCardProps {
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
expanded?: boolean;
|
||||
size?: "XS" | "S" | "M" | "L" | "xs" | "s" | "m" | "l";
|
||||
size?: "XS" | "S" | "M" | "L";
|
||||
categories?: Category[];
|
||||
logoUrl?: string;
|
||||
logoAlt?: string;
|
||||
|
||||
@@ -261,8 +261,8 @@ export function RuleCardView({
|
||||
key={categoryIndex}
|
||||
label={category.name}
|
||||
showHelpIcon={false}
|
||||
size="S"
|
||||
palette="Inverse"
|
||||
size="s"
|
||||
palette="inverse"
|
||||
options={category.chipOptions}
|
||||
onChipClick={(chipId) => {
|
||||
category.onChipClick?.(category.name, chipId);
|
||||
|
||||
@@ -4,12 +4,10 @@ import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import ContentContainerView from "./ContentContainer.view";
|
||||
import type { ContentContainerProps } from "./ContentContainer.types";
|
||||
import { normalizeContentContainerSize } from "../../../../lib/propNormalization";
|
||||
|
||||
const ContentContainerContainer = memo<ContentContainerProps>(
|
||||
({ post, width = "200px", size: sizeProp = "responsive" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContentContainerSize(sizeProp);
|
||||
const size = sizeProp;
|
||||
// Get the corresponding icon based on the same logic as background images
|
||||
const getIconImage = (slug: string): string => {
|
||||
const icons = [
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import type { BlogPost } from "../../../../lib/content";
|
||||
|
||||
export type ContentContainerSizeValue =
|
||||
| "xs"
|
||||
| "responsive"
|
||||
| "Xs"
|
||||
| "Responsive";
|
||||
export type ContentContainerSizeValue = "xs" | "responsive";
|
||||
|
||||
export interface ContentContainerProps {
|
||||
post: BlogPost;
|
||||
width?: string;
|
||||
/**
|
||||
* Content container size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Content container size.
|
||||
*/
|
||||
size?: ContentContainerSizeValue;
|
||||
}
|
||||
|
||||
+1
-3
@@ -4,12 +4,10 @@ import { memo } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view";
|
||||
import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types";
|
||||
import { normalizeContentThumbnailVariant } from "../../../../lib/propNormalization";
|
||||
|
||||
const ContentThumbnailTemplateContainer = memo<ContentThumbnailTemplateProps>(
|
||||
({ post, className = "", variant: variantProp = "vertical" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeContentThumbnailVariant(variantProp);
|
||||
const variant = variantProp;
|
||||
// Get article-specific background image from frontmatter
|
||||
const getBackgroundImage = (
|
||||
post: ContentThumbnailTemplateProps["post"],
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import type { BlogPost } from "../../../../lib/content";
|
||||
|
||||
export type ContentThumbnailTemplateVariantValue =
|
||||
| "vertical"
|
||||
| "horizontal"
|
||||
| "Vertical"
|
||||
| "Horizontal";
|
||||
export type ContentThumbnailTemplateVariantValue = "vertical" | "horizontal";
|
||||
|
||||
export interface ContentThumbnailTemplateProps {
|
||||
post: BlogPost;
|
||||
className?: string;
|
||||
/**
|
||||
* Content thumbnail variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Content thumbnail variant.
|
||||
*/
|
||||
variant?: ContentThumbnailTemplateVariantValue;
|
||||
slugOrder?: string[];
|
||||
|
||||
@@ -4,11 +4,11 @@ import { memo } from "react";
|
||||
import { useComponentId } from "../../../hooks";
|
||||
import { CheckboxView } from "./Checkbox.view";
|
||||
import type { CheckboxProps } from "./Checkbox.types";
|
||||
import {
|
||||
normalizeMode,
|
||||
normalizeState,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Checkbox" (TODO(figma)). Single boolean checkbox with
|
||||
* optional label, supporting standard and inverse modes.
|
||||
*/
|
||||
const CheckboxContainer = memo<CheckboxProps>(
|
||||
({
|
||||
checked = false,
|
||||
@@ -24,9 +24,8 @@ const CheckboxContainer = memo<CheckboxProps>(
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
const state = normalizeState(stateProp);
|
||||
const mode = modeProp;
|
||||
const state = stateProp;
|
||||
|
||||
const isInverse = mode === "inverse";
|
||||
const isStandard = mode === "standard";
|
||||
|
||||
@@ -2,15 +2,9 @@ import type { ModeValue, StateValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export interface CheckboxProps {
|
||||
checked?: boolean;
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
/** Mode variant (Figma: Mode). */
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
/** Visual state (Figma: State). */
|
||||
state?: StateValue;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
import { memo, useCallback, useId, useState } from "react";
|
||||
import { CheckboxGroupView } from "./CheckboxGroup.view";
|
||||
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
|
||||
import { normalizeMode } from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / CheckboxGroup" (TODO(figma)). Group of checkboxes sharing
|
||||
* a name that emits the array of currently selected values.
|
||||
*/
|
||||
const CheckboxGroupContainer = ({
|
||||
name,
|
||||
value,
|
||||
@@ -15,8 +18,7 @@ const CheckboxGroupContainer = ({
|
||||
className = "",
|
||||
...props
|
||||
}: CheckboxGroupProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
const mode = modeProp;
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const generatedId = useId();
|
||||
const groupId = name || `checkbox-group-${generatedId}`;
|
||||
|
||||
@@ -12,8 +12,7 @@ export interface CheckboxGroupProps {
|
||||
value?: string[];
|
||||
onChange?: (_data: { value: string[] }) => void;
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Mode variant.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
import { memo, useState, useEffect, useRef } from "react";
|
||||
import ChipView from "./Chip.view";
|
||||
import type { ChipProps } from "./Chip.types";
|
||||
import {
|
||||
normalizeChipPalette,
|
||||
normalizeChipSize,
|
||||
normalizeChipState,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Chip" (TODO(figma)). Compact pill-shaped tag with
|
||||
* selectable, removable, and inline-editable (custom) states.
|
||||
*/
|
||||
const ChipContainer = memo<ChipProps>(
|
||||
({
|
||||
label,
|
||||
state: stateProp = "Unselected",
|
||||
palette: paletteProp = "Default",
|
||||
size: sizeProp = "S",
|
||||
state: stateProp = "unselected",
|
||||
palette: paletteProp = "default",
|
||||
size: sizeProp = "s",
|
||||
className = "",
|
||||
disabled,
|
||||
onClick,
|
||||
@@ -23,9 +22,9 @@ const ChipContainer = memo<ChipProps>(
|
||||
onClose,
|
||||
ariaLabel,
|
||||
}) => {
|
||||
const state = normalizeChipState(stateProp);
|
||||
const palette = normalizeChipPalette(paletteProp);
|
||||
const size = normalizeChipSize(sizeProp);
|
||||
const state = stateProp;
|
||||
const palette = paletteProp;
|
||||
const size = sizeProp;
|
||||
|
||||
const isDisabled = disabled ?? state === "disabled";
|
||||
const isCustom = state === "custom";
|
||||
|
||||
@@ -7,38 +7,32 @@ import type {
|
||||
export interface ChipProps {
|
||||
label: string;
|
||||
/**
|
||||
* Visual state of the chip, aligned with Figma:
|
||||
* - "Unselected"
|
||||
* - "Selected"
|
||||
* - "Disabled"
|
||||
* - "Custom" (editable chips with check/close buttons)
|
||||
*
|
||||
* Accepts both PascalCase (Figma) and lowercase values.
|
||||
* Visual state of the chip:
|
||||
* - "unselected"
|
||||
* - "selected"
|
||||
* - "disabled"
|
||||
* - "custom" (editable chips with check/close buttons)
|
||||
*/
|
||||
state?: ChipStateValue;
|
||||
/**
|
||||
* Palette of the chip, aligned with Figma:
|
||||
* - "Default"
|
||||
* - "Inverse"
|
||||
*
|
||||
* Accepts both PascalCase (Figma) and lowercase values.
|
||||
* Palette of the chip:
|
||||
* - "default"
|
||||
* - "inverse"
|
||||
*/
|
||||
palette?: ChipPaletteValue;
|
||||
/**
|
||||
* Size of the chip, aligned with Figma:
|
||||
* - "S"
|
||||
* - "M"
|
||||
*
|
||||
* Accepts both uppercase (Figma) and lowercase values.
|
||||
* Size of the chip:
|
||||
* - "s"
|
||||
* - "m"
|
||||
*/
|
||||
size?: ChipSizeValue;
|
||||
className?: string;
|
||||
/**
|
||||
* Whether the chip should be non-interactive. Defaults to `true` when
|
||||
* `state === "disabled"` to preserve historical behavior. Pass
|
||||
* `disabled={false}` alongside `state="Disabled"` to render the dimmed
|
||||
* `disabled={false}` alongside `state="disabled"` to render the dimmed
|
||||
* "disabled" visual while keeping the chip clickable — useful for toggle
|
||||
* groups where the unselected state is the disabled Figma visual.
|
||||
* groups where the unselected state is the disabled visual.
|
||||
*/
|
||||
disabled?: boolean;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { InputWithCounterView } from "./InputWithCounter.view";
|
||||
import type { InputWithCounterProps } from "./InputWithCounter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / InputWithCounter" (TODO(figma)).
|
||||
* Single-line text input with a label, optional help glyph, and a live
|
||||
* `value.length / maxLength` counter underneath.
|
||||
*/
|
||||
const InputWithCounterContainer = memo<InputWithCounterProps>((props) => {
|
||||
return <InputWithCounterView {...props} />;
|
||||
});
|
||||
|
||||
InputWithCounterContainer.displayName = "InputWithCounter";
|
||||
|
||||
export default InputWithCounterContainer;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { InputWithCounterView as default } from "./InputWithCounter.view";
|
||||
export { default } from "./InputWithCounter.container";
|
||||
export type { InputWithCounterProps } from "./InputWithCounter.types";
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
import { memo } from "react";
|
||||
import MultiSelectView from "./MultiSelect.view";
|
||||
import type { MultiSelectProps } from "./MultiSelect.types";
|
||||
import {
|
||||
normalizeMultiSelectSize,
|
||||
normalizeChipPalette,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / MultiSelect" (TODO(figma)). Labelled set of chips for
|
||||
* picking multiple values, with an optional add button for custom entries.
|
||||
*/
|
||||
const MultiSelectContainer = memo<MultiSelectProps>(
|
||||
({
|
||||
label,
|
||||
showHelpIcon = true,
|
||||
size: sizeProp = "M",
|
||||
palette: paletteProp = "Default",
|
||||
size: sizeProp = "m",
|
||||
palette: paletteProp = "default",
|
||||
options,
|
||||
onChipClick,
|
||||
onAddClick,
|
||||
@@ -24,8 +24,8 @@ const MultiSelectContainer = memo<MultiSelectProps>(
|
||||
onCustomChipClose,
|
||||
className = "",
|
||||
}) => {
|
||||
const size = normalizeMultiSelectSize(sizeProp);
|
||||
const palette = normalizeChipPalette(paletteProp);
|
||||
const size = sizeProp;
|
||||
const palette = paletteProp;
|
||||
|
||||
return (
|
||||
<MultiSelectView
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface ChipOption {
|
||||
state?: ChipStateValue;
|
||||
}
|
||||
|
||||
export type MultiSelectSizeValue = "S" | "M" | "s" | "m";
|
||||
export type MultiSelectSizeValue = "s" | "m";
|
||||
|
||||
export interface MultiSelectProps {
|
||||
/**
|
||||
@@ -21,13 +21,11 @@ export interface MultiSelectProps {
|
||||
*/
|
||||
showHelpIcon?: boolean;
|
||||
/**
|
||||
* Size variant: "S" (small) or "M" (medium)
|
||||
* Accepts both uppercase (Figma) and lowercase values.
|
||||
* Size variant: "s" (small) or "m" (medium)
|
||||
*/
|
||||
size?: MultiSelectSizeValue;
|
||||
/**
|
||||
* Palette for chips: "Default" or "Inverse"
|
||||
* Accepts both PascalCase (Figma) and lowercase values.
|
||||
* Palette for chips: "default" or "inverse"
|
||||
*/
|
||||
palette?: ChipPaletteValue;
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ function MultiSelectView({
|
||||
? "gap-[var(--measures-spacing-200,8px)]"
|
||||
: "gap-[var(--measures-spacing-300,12px)]";
|
||||
|
||||
const chipSize = isSmall ? "S" : "M";
|
||||
const chipSize = size;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -41,8 +41,8 @@ function MultiSelectView({
|
||||
helpIcon={showHelpIcon}
|
||||
asterisk={false}
|
||||
helperText={false}
|
||||
size={size === "s" ? "S" : "M"}
|
||||
palette={palette === "inverse" ? "Inverse" : "Default"}
|
||||
size={size}
|
||||
palette={palette}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -53,13 +53,12 @@ function MultiSelectView({
|
||||
{options.map((option) => (
|
||||
<Chip
|
||||
key={option.id}
|
||||
label={option.state === "Custom" ? "" : option.label}
|
||||
state={option.state || "Unselected"}
|
||||
palette={palette === "inverse" ? "Inverse" : "Default"}
|
||||
label={option.state === "custom" ? "" : option.label}
|
||||
state={option.state || "unselected"}
|
||||
palette={palette}
|
||||
size={chipSize}
|
||||
onClick={() => {
|
||||
// Only toggle if not in Custom state
|
||||
if (option.state !== "Custom" && onChipClick) {
|
||||
if (option.state !== "custom" && onChipClick) {
|
||||
onChipClick(option.id);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
import { memo, useCallback, useId } from "react";
|
||||
import { RadioButtonView } from "./RadioButton.view";
|
||||
import type { RadioButtonProps } from "./RadioButton.types";
|
||||
import {
|
||||
normalizeMode,
|
||||
normalizeState,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const RadioButtonContainer = ({
|
||||
checked = false,
|
||||
@@ -22,9 +18,8 @@ const RadioButtonContainer = ({
|
||||
ariaLabel,
|
||||
className = "",
|
||||
}: RadioButtonProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
const state = normalizeState(stateProp);
|
||||
const mode = modeProp;
|
||||
const state = stateProp;
|
||||
|
||||
// If state is "selected", it means checked in Figma terms
|
||||
const normalizedState = state === "selected" || checked ? "selected" : state;
|
||||
|
||||
@@ -3,14 +3,12 @@ import type { ModeValue, StateValue } from "../../../../lib/propNormalization";
|
||||
export interface RadioButtonProps {
|
||||
checked?: boolean;
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Mode variant.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus", "selected"/"Selected" (case-insensitive).
|
||||
* Visual state.
|
||||
* Note: "selected" state is represented by the `checked` prop in practice.
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
state?: StateValue;
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { memo, useCallback, useId } from "react";
|
||||
import { RadioGroupView } from "./RadioGroup.view";
|
||||
import type { RadioGroupProps } from "./RadioGroup.types";
|
||||
import {
|
||||
normalizeMode,
|
||||
normalizeState,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / RadioGroup" (TODO(figma)). Group of radio buttons sharing
|
||||
* a name that emits the single currently selected value.
|
||||
*/
|
||||
const RadioGroupContainer = ({
|
||||
name,
|
||||
value,
|
||||
@@ -19,14 +19,11 @@ const RadioGroupContainer = ({
|
||||
className = "",
|
||||
...props
|
||||
}: RadioGroupProps) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const mode = normalizeMode(modeProp);
|
||||
// Normalize state, but handle "With Subtext" separately (it's represented by options with subtext)
|
||||
const state =
|
||||
typeof stateProp === "string" &&
|
||||
(stateProp.toLowerCase() === "with subtext" || stateProp === "With Subtext")
|
||||
? "default" // "With Subtext" is handled via RadioOption.subtext, use default state
|
||||
: normalizeState(stateProp);
|
||||
const mode = modeProp;
|
||||
const state: "default" | "hover" | "focus" | "selected" =
|
||||
stateProp === "With Subtext" || stateProp === "with subtext"
|
||||
? "default"
|
||||
: stateProp;
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const generatedId = useId();
|
||||
const groupId = name || `radio-group-${generatedId}`;
|
||||
|
||||
@@ -12,14 +12,12 @@ export interface RadioGroupProps {
|
||||
value?: string;
|
||||
onChange?: (_data: { value: string }) => void;
|
||||
/**
|
||||
* Mode variant. Accepts both "standard"/"Standard" and "inverse"/"Inverse" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Mode variant.
|
||||
*/
|
||||
mode?: ModeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Visual state.
|
||||
* 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;
|
||||
|
||||
@@ -16,12 +16,11 @@ import React, {
|
||||
import { useClickOutside } from "../../../hooks";
|
||||
import { SelectInputView } from "./SelectInput.view";
|
||||
import type { SelectInputProps } from "./SelectInput.types";
|
||||
import {
|
||||
normalizeState,
|
||||
normalizeSmallMediumLargeSize,
|
||||
normalizeLabelVariant,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / SelectInput" (TODO(figma)). Custom-styled select dropdown
|
||||
* with a labelled trigger button and floating option menu.
|
||||
*/
|
||||
const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||
(
|
||||
{
|
||||
@@ -53,22 +52,14 @@ const SelectInputContainer = forwardRef<HTMLButtonElement, SelectInputProps>(
|
||||
const shouldShowLabel =
|
||||
showLabel !== undefined ? showLabel : labelText !== undefined;
|
||||
|
||||
// Normalize state - handle "state5" as disabled
|
||||
let normalizedState = externalStateProp;
|
||||
if (normalizedState === "state5" || normalizedState === "State5") {
|
||||
normalizedState = "default"; // Map to default, disabled prop handles the disabled state
|
||||
normalizedState = "default";
|
||||
}
|
||||
const externalState = normalizeState(normalizedState);
|
||||
const externalState = normalizedState;
|
||||
|
||||
// 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;
|
||||
// Mark as intentionally unused for future implementation
|
||||
const _labelVariant = labelVariantProp;
|
||||
const _size = sizeProp;
|
||||
void _labelVariant;
|
||||
void _size;
|
||||
|
||||
|
||||
@@ -7,18 +7,8 @@ export interface SelectOptionData {
|
||||
|
||||
import type { StateValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export type SelectInputLabelVariantValue =
|
||||
| "default"
|
||||
| "horizontal"
|
||||
| "Default"
|
||||
| "Horizontal";
|
||||
export type SelectInputSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large";
|
||||
export type SelectInputLabelVariantValue = "default" | "horizontal";
|
||||
export type SelectInputSizeValue = "small" | "medium" | "large";
|
||||
|
||||
export interface SelectInputProps {
|
||||
id?: string;
|
||||
@@ -33,18 +23,15 @@ export interface SelectInputProps {
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
/**
|
||||
* Label variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Label variant.
|
||||
*/
|
||||
labelVariant?: SelectInputLabelVariantValue;
|
||||
/**
|
||||
* Select input size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Select input size.
|
||||
*/
|
||||
size?: SelectInputSizeValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "active"/"Active", "focus"/"Focus", "error"/"Error", "state5"/"State5" (State5 = Disabled).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state. "state5" maps to disabled.
|
||||
*/
|
||||
state?: StateValue | "state5" | "State5";
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Children, type ReactNode } from "react";
|
||||
import { getAssetPath, ASSETS } from "../../../../lib/assetUtils";
|
||||
import SelectDropdown from "./SelectDropdown";
|
||||
import SelectOption from "./SelectOption";
|
||||
import SelectOption from "../SelectOption";
|
||||
import type { SelectOptionData } from "./SelectInput.types";
|
||||
|
||||
export interface SelectInputViewProps {
|
||||
|
||||
+5
-3
@@ -3,8 +3,11 @@
|
||||
import { forwardRef, memo, useCallback } from "react";
|
||||
import { SelectOptionView } from "./SelectOption.view";
|
||||
import type { SelectOptionProps } from "./SelectOption.types";
|
||||
import { normalizeContextMenuItemSize } from "../../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / SelectOption" (TODO(figma)). Single option row rendered
|
||||
* inside `SelectInput`'s dropdown menu.
|
||||
*/
|
||||
const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||
(
|
||||
{
|
||||
@@ -18,8 +21,7 @@ const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContextMenuItemSize(sizeProp);
|
||||
const size = sizeProp;
|
||||
const getTextSize = (): string => {
|
||||
switch (size) {
|
||||
case "small":
|
||||
+2
-9
@@ -1,10 +1,4 @@
|
||||
export type SelectOptionSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large";
|
||||
export type SelectOptionSizeValue = "small" | "medium" | "large";
|
||||
|
||||
export interface SelectOptionProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -15,8 +9,7 @@ export interface SelectOptionProps {
|
||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
/**
|
||||
* Select option size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Select option size.
|
||||
*/
|
||||
size?: SelectOptionSizeValue;
|
||||
}
|
||||
@@ -3,8 +3,11 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { SwitchView } from "./Switch.view";
|
||||
import type { SwitchProps } from "./Switch.types";
|
||||
import { normalizeState } from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Switch" (TODO(figma)). Animated on/off toggle switch,
|
||||
* optionally paired with a trailing text label.
|
||||
*/
|
||||
const SwitchContainer = memo(
|
||||
forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
|
||||
const {
|
||||
@@ -18,8 +21,7 @@ const SwitchContainer = memo(
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const state = normalizeState(stateProp);
|
||||
const state = stateProp;
|
||||
|
||||
const switchId = useId();
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ export interface SwitchProps extends Omit<
|
||||
onFocus?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state.
|
||||
*/
|
||||
state?: StateValue;
|
||||
/**
|
||||
|
||||
@@ -4,13 +4,11 @@ import { memo, forwardRef } from "react";
|
||||
import { useComponentId, useFormField } from "../../../hooks";
|
||||
import { TextAreaView } from "./TextArea.view";
|
||||
import type { TextAreaProps } from "./TextArea.types";
|
||||
import {
|
||||
normalizeInputState,
|
||||
normalizeSmallMediumLargeSize,
|
||||
normalizeLabelVariant,
|
||||
normalizeTextAreaAppearance,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / TextArea" (TODO(figma)). Multi-line text input with size
|
||||
* variants, an embedded appearance, and an optional label and help glyph.
|
||||
*/
|
||||
const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
(
|
||||
{
|
||||
@@ -37,11 +35,10 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSmallMediumLargeSize(sizeProp);
|
||||
const labelVariant = normalizeLabelVariant(labelVariantProp);
|
||||
const state = normalizeInputState(stateProp);
|
||||
const appearance = normalizeTextAreaAppearance(appearanceProp);
|
||||
const size = sizeProp;
|
||||
const labelVariant = labelVariantProp;
|
||||
const state = stateProp;
|
||||
const appearance = appearanceProp;
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const { id: textareaId, labelId } = useComponentId("textarea", id);
|
||||
|
||||
|
||||
@@ -1,41 +1,24 @@
|
||||
import type { InputStateValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export type TextAreaSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large";
|
||||
export type TextAreaLabelVariantValue =
|
||||
| "default"
|
||||
| "horizontal"
|
||||
| "Default"
|
||||
| "Horizontal";
|
||||
export type TextAreaSizeValue = "small" | "medium" | "large";
|
||||
export type TextAreaLabelVariantValue = "default" | "horizontal";
|
||||
|
||||
export type TextAreaAppearanceValue =
|
||||
| "default"
|
||||
| "embedded"
|
||||
| "Default"
|
||||
| "Embedded";
|
||||
export type TextAreaAppearanceValue = "default" | "embedded";
|
||||
|
||||
export interface TextAreaProps extends Omit<
|
||||
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
/**
|
||||
* Text area size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Text area size.
|
||||
*/
|
||||
size?: TextAreaSizeValue;
|
||||
/**
|
||||
* Label variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Label variant.
|
||||
*/
|
||||
labelVariant?: TextAreaLabelVariantValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "active"/"Active", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state.
|
||||
*/
|
||||
state?: InputStateValue;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -4,11 +4,11 @@ import { memo, forwardRef, useState, useRef } from "react";
|
||||
import { useComponentId, useFormField } from "../../../hooks";
|
||||
import { TextInputView } from "./TextInput.view";
|
||||
import type { TextInputProps } from "./TextInput.types";
|
||||
import {
|
||||
normalizeInputState,
|
||||
normalizeTextInputSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / TextInput" (TODO(figma)). Single-line text input with size
|
||||
* variants and managed default/active/focus/error states.
|
||||
*/
|
||||
const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
(
|
||||
{
|
||||
@@ -33,9 +33,8 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const externalState = normalizeInputState(externalStateProp);
|
||||
const inputSize = normalizeTextInputSize(inputSizeProp);
|
||||
const externalState = externalStateProp;
|
||||
const inputSize = inputSizeProp;
|
||||
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const { id: inputId, labelId } = useComponentId("text-input", id);
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import type { InputStateValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export type TextInputSizeValue = "small" | "medium" | "Small" | "Medium";
|
||||
export type TextInputSizeValue = "small" | "medium";
|
||||
|
||||
export interface TextInputProps extends Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "active"/"Active", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state.
|
||||
*/
|
||||
state?: InputStateValue;
|
||||
/**
|
||||
* Size variant. Accepts both PascalCase (Figma) and lowercase (codebase).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Size variant.
|
||||
* @default "medium"
|
||||
*/
|
||||
inputSize?: TextInputSizeValue;
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { ToggleView } from "./Toggle.view";
|
||||
import type { ToggleProps } from "./Toggle.types";
|
||||
import { normalizeState } from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Toggle" (TODO(figma)). Pill-shaped toggle button with
|
||||
* checked/unchecked states and optional leading icon and text.
|
||||
*/
|
||||
const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
(
|
||||
{
|
||||
@@ -24,8 +27,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const state = normalizeState(stateProp);
|
||||
const state = stateProp;
|
||||
const toggleId = useId();
|
||||
const labelId = useId();
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ export interface ToggleProps extends Omit<
|
||||
onBlur?: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state.
|
||||
*/
|
||||
state?: StateValue;
|
||||
showIcon?: boolean;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { memo, useCallback, useId, forwardRef } from "react";
|
||||
import { ToggleGroupView } from "./ToggleGroup.view";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
import {
|
||||
normalizeToggleState,
|
||||
normalizeToggleGroupPosition,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Control / ToggleGroup" (TODO(figma)). Segmented row of `Toggle`
|
||||
* buttons whose corner radii are shared based on position (left/middle/right).
|
||||
*/
|
||||
const ToggleGroupContainer = memo(
|
||||
forwardRef<HTMLButtonElement, ToggleGroupProps>((props, _ref) => {
|
||||
const {
|
||||
@@ -23,9 +23,8 @@ const ToggleGroupContainer = memo(
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const position = normalizeToggleGroupPosition(positionProp);
|
||||
const state = normalizeToggleState(stateProp);
|
||||
const position = positionProp;
|
||||
const state = stateProp;
|
||||
|
||||
const groupId = useId();
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import type { StateValue } from "../../../../lib/propNormalization";
|
||||
|
||||
export type ToggleGroupPositionValue =
|
||||
| "left"
|
||||
| "middle"
|
||||
| "right"
|
||||
| "Left"
|
||||
| "Middle"
|
||||
| "Right";
|
||||
export type ToggleGroupPositionValue = "left" | "middle" | "right";
|
||||
|
||||
export interface ToggleGroupProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
@@ -15,15 +9,13 @@ export interface ToggleGroupProps extends Omit<
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
/**
|
||||
* Toggle group position. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Toggle group position.
|
||||
*/
|
||||
position?: ToggleGroupPositionValue;
|
||||
/**
|
||||
* Visual state. Accepts "default"/"Default", "hover"/"Hover", "focus"/"Focus", "selected"/"Selected" (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Visual state.
|
||||
*/
|
||||
state?: StateValue | "selected" | "Selected";
|
||||
state?: StateValue | "selected";
|
||||
showText?: boolean;
|
||||
ariaLabel?: string;
|
||||
onChange?: (
|
||||
|
||||
@@ -4,6 +4,10 @@ import { memo } from "react";
|
||||
import UploadView from "./Upload.view";
|
||||
import type { UploadProps } from "./Upload.types";
|
||||
|
||||
/**
|
||||
* Figma: "Control / Upload" (TODO(figma)). Click-to-upload tile with a label
|
||||
* and hint text used to add an image from the user's device.
|
||||
*/
|
||||
const UploadContainer = memo<UploadProps>(
|
||||
({
|
||||
active = true,
|
||||
|
||||
@@ -44,8 +44,8 @@ function UploadView({
|
||||
helpIcon={showHelpIcon}
|
||||
asterisk={false}
|
||||
helperText={false}
|
||||
size="S"
|
||||
palette="Default"
|
||||
size="s"
|
||||
palette="default"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
import { memo } from "react";
|
||||
import { normalizeSize } from "../../../lib/propNormalization";
|
||||
|
||||
export type AvatarSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge";
|
||||
export type AvatarSizeValue = "small" | "medium" | "large" | "xlarge";
|
||||
|
||||
interface AvatarProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
src: string;
|
||||
alt: string;
|
||||
/**
|
||||
* Avatar size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: AvatarSizeValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Avatar = memo<AvatarProps>(
|
||||
({ src, alt, size: sizeProp = "small", className = "", ...props }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSize(sizeProp, "small");
|
||||
const size = sizeProp;
|
||||
const sizeStyles: Record<string, string> = {
|
||||
small:
|
||||
"w-[var(--spacing-scale-016)] h-[var(--spacing-scale-016)] border-[1.5px] border-[#FFFFFF4D] border-solid",
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import { AlertView } from "./Alert.view";
|
||||
import type { AlertProps } from "./Alert.types";
|
||||
import {
|
||||
normalizeAlertStatus,
|
||||
normalizeAlertType,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const AlertContainer = memo<AlertProps>(
|
||||
({
|
||||
@@ -19,9 +15,8 @@ const AlertContainer = memo<AlertProps>(
|
||||
onClose,
|
||||
className = "",
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const status = normalizeAlertStatus(statusProp);
|
||||
const type = normalizeAlertType(typeProp);
|
||||
const status = statusProp;
|
||||
const type = typeProp;
|
||||
// Determine background and border colors based on status and type
|
||||
const getStatusStyles = () => {
|
||||
switch (status) {
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
export type AlertStatusValue =
|
||||
| "default"
|
||||
| "positive"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "Default"
|
||||
| "Positive"
|
||||
| "Warning"
|
||||
| "Danger";
|
||||
export type AlertStatusValue = "default" | "positive" | "warning" | "danger";
|
||||
|
||||
export type AlertTypeValue = "toast" | "banner" | "Toast" | "Banner";
|
||||
export type AlertTypeValue = "toast" | "banner";
|
||||
|
||||
export interface AlertProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
/**
|
||||
* Alert status. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Alert status.
|
||||
*/
|
||||
status?: AlertStatusValue;
|
||||
/**
|
||||
* Alert type. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Alert type.
|
||||
*/
|
||||
type?: AlertTypeValue;
|
||||
/**
|
||||
|
||||
+1
-3
@@ -3,7 +3,6 @@
|
||||
import { forwardRef, memo, useCallback } from "react";
|
||||
import { ContextMenuItemView } from "./ContextMenuItem.view";
|
||||
import type { ContextMenuItemProps } from "./ContextMenuItem.types";
|
||||
import { normalizeContextMenuItemSize } from "../../../../lib/propNormalization";
|
||||
|
||||
const ContextMenuItemContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -22,8 +21,7 @@ const ContextMenuItemContainer = forwardRef<
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeContextMenuItemSize(sizeProp);
|
||||
const size = sizeProp;
|
||||
const getTextSize = (): string => {
|
||||
switch (size) {
|
||||
case "small":
|
||||
+2
-9
@@ -1,10 +1,4 @@
|
||||
export type ContextMenuItemSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large";
|
||||
export type ContextMenuItemSizeValue = "small" | "medium" | "large";
|
||||
|
||||
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
@@ -16,8 +10,7 @@ export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElemen
|
||||
_e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
/**
|
||||
* Context menu item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Context menu item size.
|
||||
*/
|
||||
size?: ContextMenuItemSizeValue;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import TextInput from "../../controls/TextInput";
|
||||
import ContentLockup from "../../type/ContentLockup";
|
||||
import { requestMagicLink } from "../../../../lib/create/api";
|
||||
import { safeInternalPath } from "../../../../lib/safeInternalPath";
|
||||
import { setTransferPendingFlag } from "../../../create/utils/anonymousDraftStorage";
|
||||
import { setTransferPendingFlag } from "../../../(app)/create/utils/anonymousDraftStorage";
|
||||
|
||||
/** Mail icon for login modal (inline SVG; same pattern as InfoMessageBox ExclamationIconInline). */
|
||||
function MailIconInline() {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { memo, useState } from "react";
|
||||
import { TooltipView } from "./Tooltip.view";
|
||||
import type { TooltipProps } from "./Tooltip.types";
|
||||
import { normalizeTooltipPosition } from "../../../../lib/propNormalization";
|
||||
|
||||
const TooltipContainer = memo<TooltipProps>(
|
||||
({
|
||||
@@ -13,8 +12,7 @@ const TooltipContainer = memo<TooltipProps>(
|
||||
className = "",
|
||||
disabled = false,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const position = normalizeTooltipPosition(positionProp);
|
||||
const position = positionProp;
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
if (disabled) {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
export type TooltipPositionValue = "top" | "bottom" | "Top" | "Bottom";
|
||||
export type TooltipPositionValue = "top" | "bottom";
|
||||
|
||||
export interface TooltipProps {
|
||||
children: React.ReactNode;
|
||||
text: string;
|
||||
/**
|
||||
* Tooltip position. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Tooltip position.
|
||||
*/
|
||||
position?: TooltipPositionValue;
|
||||
className?: string;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
// Code split Footer - below the fold, can be lazy loaded
|
||||
const Footer = dynamic(() => import("./Footer"), {
|
||||
loading: () => (
|
||||
<footer className="bg-[var(--color-surface-default-primary)] w-full min-h-[200px]" />
|
||||
),
|
||||
ssr: true, // Keep SSR for SEO
|
||||
});
|
||||
|
||||
/**
|
||||
* Conditionally renders Footer based on pathname.
|
||||
* Hides footer for /create/* and /login (full-screen flows; no site chrome).
|
||||
*/
|
||||
const ConditionalFooter = memo(() => {
|
||||
const pathname = usePathname();
|
||||
const isCreateFlow = pathname?.startsWith("/create");
|
||||
const isLogin = pathname === "/login";
|
||||
|
||||
if (isCreateFlow || isLogin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Footer />;
|
||||
});
|
||||
|
||||
ConditionalFooter.displayName = "ConditionalFooter";
|
||||
|
||||
export default ConditionalFooter;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { useTranslation } from "../../contexts/MessagesContext";
|
||||
import { normalizeMenuBarSize } from "../../../lib/propNormalization";
|
||||
|
||||
export type MenuBarSizeValue =
|
||||
| "X Small"
|
||||
@@ -23,7 +22,7 @@ interface MenuBarProps extends React.HTMLAttributes<HTMLElement> {
|
||||
|
||||
const MenuBar = memo<MenuBarProps>(
|
||||
({ children, className = "", size: sizeProp = "X Small", ...props }) => {
|
||||
const size = normalizeMenuBarSize(sizeProp);
|
||||
const size = sizeProp ?? "X Small";
|
||||
const t = useTranslation("menuBar");
|
||||
|
||||
// Size styles based on Figma specifications
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import MenuBarItemView from "./MenuBarItem.view";
|
||||
import type { MenuBarItemProps } from "./MenuBarItem.types";
|
||||
import {
|
||||
normalizeMenuBarItemState,
|
||||
normalizeMenuBarItemMode,
|
||||
normalizeMenuBarItemSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const MenuBarItemContainer = memo<MenuBarItemProps>(
|
||||
({
|
||||
@@ -24,9 +19,9 @@ const MenuBarItemContainer = memo<MenuBarItemProps>(
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
const state = normalizeMenuBarItemState(stateProp, "default");
|
||||
const mode = normalizeMenuBarItemMode(modeProp, "default");
|
||||
const size = normalizeMenuBarItemSize(sizeProp, "X Small");
|
||||
const state = stateProp ?? "default";
|
||||
const mode = modeProp ?? "default";
|
||||
const size = sizeProp ?? "X Small";
|
||||
|
||||
// Size styles based on Figma specifications
|
||||
const sizeStyles: Record<
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import NavigationItemView from "./NavigationItem.view";
|
||||
import type { NavigationItemProps } from "./NavigationItem.types";
|
||||
import {
|
||||
normalizeNavigationItemVariant,
|
||||
normalizeNavigationItemSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const NavigationItemContainer = memo<NavigationItemProps>(
|
||||
({
|
||||
@@ -19,9 +15,8 @@ const NavigationItemContainer = memo<NavigationItemProps>(
|
||||
isActive = false,
|
||||
...props
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeNavigationItemVariant(variantProp);
|
||||
const size = normalizeNavigationItemSize(sizeProp);
|
||||
const variant = variantProp;
|
||||
const size = sizeProp;
|
||||
// Variant styles
|
||||
const variantStyles: Record<string, string> = {
|
||||
default:
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export type NavigationItemVariantValue = "default" | "Default";
|
||||
export type NavigationItemSizeValue =
|
||||
| "default"
|
||||
| "xsmall"
|
||||
| "Default"
|
||||
| "XSmall";
|
||||
export type NavigationItemVariantValue = "default";
|
||||
export type NavigationItemSizeValue = "default" | "xsmall";
|
||||
|
||||
export interface NavigationItemProps extends Omit<
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
@@ -12,13 +8,11 @@ export interface NavigationItemProps extends Omit<
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Navigation item variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Navigation item variant.
|
||||
*/
|
||||
variant?: NavigationItemVariantValue;
|
||||
/**
|
||||
* Navigation item size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Navigation item size.
|
||||
*/
|
||||
size?: NavigationItemSizeValue;
|
||||
className?: string;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { normalizeProportionBarVariant } from "../../../../lib/propNormalization";
|
||||
import { ProportionBarView } from "./ProportionBar.view";
|
||||
import type { ProportionBarProps } from "./ProportionBar.types";
|
||||
|
||||
const ProportionBarContainer = memo<ProportionBarProps>(
|
||||
({ progress = "3-2", className = "", variant: variantProp }) => {
|
||||
const variant = normalizeProportionBarVariant(variantProp);
|
||||
const variant = variantProp ?? "default";
|
||||
const barClasses = `h-[8px] relative w-full`;
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,8 +22,8 @@ export interface ProportionBarProps {
|
||||
className?: string;
|
||||
/**
|
||||
* Kept for backwards compatibility. Both `default` and `segmented` render the
|
||||
* same fill geometry (square leading edges, matching Figma). Future variants
|
||||
* can differentiate here without API changes.
|
||||
* same fill geometry. Future variants can differentiate here without API
|
||||
* changes.
|
||||
*/
|
||||
variant?: ProportionBarVariant;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
AskOrganizerProps,
|
||||
AskOrganizerVariant,
|
||||
} from "./AskOrganizer.types";
|
||||
import { normalizeAskOrganizerVariant } from "../../../../lib/propNormalization";
|
||||
|
||||
const VARIANT_STYLES: Record<
|
||||
"centered" | "left-aligned" | "compact" | "inverse",
|
||||
@@ -43,10 +42,7 @@ const AskOrganizerContainer = memo<AskOrganizerProps>(
|
||||
variant: variantProp = "centered",
|
||||
onContactClick,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeAskOrganizerVariant(
|
||||
variantProp,
|
||||
) as AskOrganizerVariant;
|
||||
const variant = variantProp;
|
||||
const t = useTranslation();
|
||||
const defaultButtonText = buttonText ?? t("askOrganizer.buttonText");
|
||||
const defaultButtonHref = buttonHref ?? t("askOrganizer.buttonHref");
|
||||
|
||||
@@ -4,11 +4,7 @@ export type AskOrganizerVariant =
|
||||
| "centered"
|
||||
| "left-aligned"
|
||||
| "compact"
|
||||
| "inverse"
|
||||
| "Centered"
|
||||
| "Left-Aligned"
|
||||
| "Compact"
|
||||
| "Inverse";
|
||||
| "inverse";
|
||||
|
||||
export interface AskOrganizerProps {
|
||||
title?: string;
|
||||
@@ -18,8 +14,7 @@ export interface AskOrganizerProps {
|
||||
buttonHref?: string;
|
||||
className?: string;
|
||||
/**
|
||||
* Ask organizer variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Ask organizer variant.
|
||||
*/
|
||||
variant?: AskOrganizerVariant;
|
||||
onContactClick?: (_data: {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { memo, useState } from "react";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import QuoteBlockView from "./QuoteBlock.view";
|
||||
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
|
||||
import { normalizeQuoteBlockVariant } from "../../../../lib/propNormalization";
|
||||
|
||||
const QuoteBlockContainer = memo<QuoteBlockProps>(
|
||||
({
|
||||
@@ -18,8 +17,7 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
|
||||
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
|
||||
onError,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeQuoteBlockVariant(variantProp);
|
||||
const variant = variantProp;
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const [imageLoading, setImageLoading] = useState(true);
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
export type QuoteBlockVariantValue =
|
||||
| "compact"
|
||||
| "standard"
|
||||
| "extended"
|
||||
| "Compact"
|
||||
| "Standard"
|
||||
| "Extended";
|
||||
export type QuoteBlockVariantValue = "compact" | "standard" | "extended";
|
||||
|
||||
export interface QuoteBlockProps {
|
||||
/**
|
||||
* Quote block variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Quote block variant.
|
||||
*/
|
||||
variant?: QuoteBlockVariantValue;
|
||||
className?: string;
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { normalizeSectionHeaderVariant } from "../../../lib/propNormalization";
|
||||
|
||||
export type SectionHeaderVariantValue =
|
||||
| "default"
|
||||
| "multi-line"
|
||||
| "Default"
|
||||
| "Multi-Line";
|
||||
export type SectionHeaderVariantValue = "default" | "multi-line";
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
titleLg?: string;
|
||||
/**
|
||||
* Section header variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
variant?: SectionHeaderVariantValue;
|
||||
}
|
||||
|
||||
const SectionHeader = memo<SectionHeaderProps>(
|
||||
({ title, subtitle, titleLg, variant: variantProp = "default" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeSectionHeaderVariant(variantProp);
|
||||
const variant = variantProp;
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { logger } from "../../../lib/logger";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import WebVitalsDashboardView from "./WebVitalsDashboard.view";
|
||||
import type { Metrics, Vitals, VitalData } from "./WebVitalsDashboard.types";
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import ContentLockupView from "./ContentLockup.view";
|
||||
import type { ContentLockupProps, VariantStyle } from "./ContentLockup.types";
|
||||
import {
|
||||
normalizeContentLockupVariant,
|
||||
normalizeAlignment,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
({
|
||||
@@ -21,9 +17,8 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
alignment: alignmentProp = "center",
|
||||
titleId,
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const variant = normalizeContentLockupVariant(variantProp);
|
||||
const alignment = normalizeAlignment(alignmentProp);
|
||||
const variant = variantProp;
|
||||
const alignment = alignmentProp;
|
||||
// Variant-specific styling
|
||||
const variantStyles: Record<string, VariantStyle> = {
|
||||
hero: {
|
||||
|
||||
@@ -5,16 +5,9 @@ export type ContentLockupVariantValue =
|
||||
| "ask"
|
||||
| "ask-inverse"
|
||||
| "modal"
|
||||
| "login"
|
||||
| "Hero"
|
||||
| "Feature"
|
||||
| "Learn"
|
||||
| "Ask"
|
||||
| "Ask-Inverse"
|
||||
| "Modal"
|
||||
| "Login";
|
||||
| "login";
|
||||
|
||||
export type ContentLockupAlignmentValue = "center" | "left" | "Center" | "Left";
|
||||
export type ContentLockupAlignmentValue = "center" | "left";
|
||||
|
||||
export interface ContentLockupProps {
|
||||
title?: string;
|
||||
@@ -24,15 +17,13 @@ export interface ContentLockupProps {
|
||||
ctaHref?: string;
|
||||
buttonClassName?: string;
|
||||
/**
|
||||
* Content lockup variant. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Content lockup variant.
|
||||
*/
|
||||
variant?: ContentLockupVariantValue;
|
||||
linkText?: string;
|
||||
linkHref?: string;
|
||||
/**
|
||||
* Text alignment. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Text alignment.
|
||||
*/
|
||||
alignment?: ContentLockupAlignmentValue;
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
import { memo } from "react";
|
||||
import HeaderLockupView from "./HeaderLockup.view";
|
||||
import type { HeaderLockupProps } from "./HeaderLockup.types";
|
||||
import {
|
||||
normalizeHeaderLockupJustification,
|
||||
normalizeHeaderLockupSize,
|
||||
normalizeHeaderLockupPalette,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
const HeaderLockupContainer = memo<HeaderLockupProps>(
|
||||
({
|
||||
@@ -17,10 +12,9 @@ const HeaderLockupContainer = memo<HeaderLockupProps>(
|
||||
size: sizeProp = "L",
|
||||
palette: paletteProp = "default",
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const justification = normalizeHeaderLockupJustification(justificationProp);
|
||||
const size = normalizeHeaderLockupSize(sizeProp);
|
||||
const palette = normalizeHeaderLockupPalette(paletteProp);
|
||||
const justification = justificationProp;
|
||||
const size = sizeProp;
|
||||
const palette = paletteProp;
|
||||
|
||||
return (
|
||||
<HeaderLockupView
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export type HeaderLockupJustificationValue =
|
||||
| "left"
|
||||
| "center"
|
||||
| "Left"
|
||||
| "Center";
|
||||
export type HeaderLockupSizeValue = "L" | "M" | "l" | "m";
|
||||
export type HeaderLockupPaletteValue =
|
||||
| "default"
|
||||
| "inverse"
|
||||
| "Default"
|
||||
| "Inverse";
|
||||
export type HeaderLockupJustificationValue = "left" | "center";
|
||||
export type HeaderLockupSizeValue = "L" | "M";
|
||||
export type HeaderLockupPaletteValue = "default" | "inverse";
|
||||
|
||||
export interface HeaderLockupProps {
|
||||
/**
|
||||
@@ -22,18 +14,15 @@ export interface HeaderLockupProps {
|
||||
*/
|
||||
description?: ReactNode;
|
||||
/**
|
||||
* Text justification. Accepts both PascalCase (Figma) and lowercase (codebase).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Text justification.
|
||||
*/
|
||||
justification?: HeaderLockupJustificationValue;
|
||||
/**
|
||||
* Size variant. Accepts both PascalCase (Figma) and lowercase (codebase).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Size variant.
|
||||
*/
|
||||
size?: HeaderLockupSizeValue;
|
||||
/**
|
||||
* Palette. Default = light text (dark bg); Inverse = dark text (light bg).
|
||||
* Accepts both PascalCase (Figma) and lowercase (codebase).
|
||||
* Palette. default = light text (dark bg); inverse = dark text (light bg).
|
||||
*/
|
||||
palette?: HeaderLockupPaletteValue;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
import { memo } from "react";
|
||||
import NumberedListView from "./NumberedList.view";
|
||||
import type { NumberedListProps } from "./NumberedList.types";
|
||||
import { normalizeNumberedListSize } from "../../../../lib/propNormalization";
|
||||
|
||||
const NumberedListContainer = memo<NumberedListProps>(
|
||||
({ items, size: sizeProp = "M" }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeNumberedListSize(sizeProp);
|
||||
const size = sizeProp;
|
||||
|
||||
return <NumberedListView items={items} size={size} />;
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type NumberedListSizeValue = "M" | "S" | "m" | "s";
|
||||
export type NumberedListSizeValue = "M" | "S";
|
||||
|
||||
export interface NumberedListItem {
|
||||
title: string;
|
||||
@@ -11,8 +11,7 @@ export interface NumberedListProps {
|
||||
*/
|
||||
items: NumberedListItem[];
|
||||
/**
|
||||
* Size variant. Accepts both PascalCase (Figma) and lowercase (codebase).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
* Size variant.
|
||||
*/
|
||||
size?: NumberedListSizeValue;
|
||||
}
|
||||
|
||||
+2
-16
@@ -1,30 +1,16 @@
|
||||
import { memo } from "react";
|
||||
import { normalizeSize } from "../../../lib/propNormalization";
|
||||
|
||||
export type AvatarContainerSizeValue =
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "xlarge"
|
||||
| "Small"
|
||||
| "Medium"
|
||||
| "Large"
|
||||
| "XLarge";
|
||||
export type AvatarContainerSizeValue = "small" | "medium" | "large" | "xlarge";
|
||||
|
||||
interface AvatarContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Avatar container size. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
size?: AvatarContainerSizeValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AvatarContainer = memo<AvatarContainerProps>(
|
||||
({ children, size: sizeProp = "small", className = "", ...props }) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const size = normalizeSize(sizeProp, "small");
|
||||
const size = sizeProp;
|
||||
const sizeStyles: Record<string, string> = {
|
||||
small: "flex -space-x-[var(--spacing-scale-008)]",
|
||||
medium: "flex -space-x-[9px]",
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./AvatarContainer";
|
||||
export type { AvatarContainerSizeValue } from "./AvatarContainer";
|
||||
@@ -7,6 +7,10 @@ import type { CardStackProps } from "./CardStack.types";
|
||||
const DEFAULT_TOGGLE_LABEL = "See all communication approaches";
|
||||
const DEFAULT_SHOW_LESS_LABEL = "Show less";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CardStack" (TODO(figma)). Selectable stack of cards with
|
||||
* an optional "see all"/"show less" expand toggle.
|
||||
*/
|
||||
const CardStackContainer = memo<CardStackProps>(
|
||||
({
|
||||
cards,
|
||||
|
||||
@@ -4,6 +4,10 @@ import { memo } from "react";
|
||||
import { CreateFlowFooterView } from "./CreateFlowFooter.view";
|
||||
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CreateFlowFooter" (TODO(figma)). Sticky footer for the
|
||||
* create flow with a back action, optional secondary button, and progress bar.
|
||||
*/
|
||||
const CreateFlowFooterContainer = memo<CreateFlowFooterProps>(
|
||||
({
|
||||
secondButton,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { normalizeProportionBarVariant } from "../../../../lib/propNormalization";
|
||||
import ProportionBar from "../../progress/ProportionBar";
|
||||
import Button from "../../buttons/Button";
|
||||
import type { CreateFlowFooterProps } from "./CreateFlowFooter.types";
|
||||
@@ -11,9 +10,7 @@ export function CreateFlowFooterView({
|
||||
onBackClick,
|
||||
className = "",
|
||||
}: CreateFlowFooterProps) {
|
||||
const proportionBarVariant = normalizeProportionBarVariant(
|
||||
proportionBarVariantProp,
|
||||
);
|
||||
const proportionBarVariant = proportionBarVariantProp ?? "default";
|
||||
return (
|
||||
<footer
|
||||
className={`bg-black w-full ${className}`}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { useRouter } from "next/navigation";
|
||||
import { CreateFlowTopNavView } from "./CreateFlowTopNav.view";
|
||||
import type { CreateFlowTopNavProps } from "./CreateFlowTopNav.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / CreateFlowTopNav" (TODO(figma)). Top navigation bar for
|
||||
* the create flow with exit, share, export, and edit actions.
|
||||
*/
|
||||
const CreateFlowTopNavContainer = memo<CreateFlowTopNavProps>(
|
||||
({
|
||||
hasShare = false,
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { memo } from "react";
|
||||
import DecisionMakingSidebarView from "./DecisionMakingSidebar.view";
|
||||
import type { DecisionMakingSidebarProps } from "./DecisionMakingSidebar.types";
|
||||
import {
|
||||
normalizeHeaderLockupJustification,
|
||||
normalizeHeaderLockupSize,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / DecisionMakingSidebar" (TODO(figma)). Sidebar pairing a
|
||||
* header lockup with an `InfoMessageBox` checklist for decision-making screens.
|
||||
*/
|
||||
const DecisionMakingSidebarContainer = memo<DecisionMakingSidebarProps>(
|
||||
({
|
||||
title,
|
||||
@@ -20,8 +20,8 @@ const DecisionMakingSidebarContainer = memo<DecisionMakingSidebarProps>(
|
||||
justification: justificationProp = "left",
|
||||
className = "",
|
||||
}) => {
|
||||
const size = normalizeHeaderLockupSize(sizeProp);
|
||||
const justification = normalizeHeaderLockupJustification(justificationProp);
|
||||
const size = sizeProp;
|
||||
const justification = justificationProp;
|
||||
|
||||
return (
|
||||
<DecisionMakingSidebarView
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { Component, type ReactNode } from "react";
|
||||
import { logger } from "../../../lib/logger";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
// Update state so the next render will show the fallback UI
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
// Log the error to an error reporting service
|
||||
logger.error("ErrorBoundary caught an error:", error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Fallback UI using design tokens
|
||||
return (
|
||||
<div className="min-h-[200px] flex items-center justify-center p-[var(--spacing-scale-016)]">
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-semibold text-[var(--color-content-default-primary)] mb-[var(--spacing-scale-008)]">
|
||||
Something went wrong
|
||||
</h2>
|
||||
<p className="text-[var(--color-content-default-secondary)] mb-[var(--spacing-scale-016)]">
|
||||
We're sorry, but something unexpected happened.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => this.setState({ hasError: false, error: null })}
|
||||
className="px-[var(--spacing-scale-016)] py-[var(--spacing-scale-008)] bg-[var(--color-surface-default-brand-royal)] text-[var(--color-content-inverse-primary)] rounded-[var(--radius-measures-radius-small)] hover:bg-[var(--color-surface-hover-brand-royal)] transition-colors"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -1,70 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { normalizeImagePlaceholderColor } from "../../../lib/propNormalization";
|
||||
|
||||
export type ImagePlaceholderColorValue =
|
||||
| "blue"
|
||||
| "green"
|
||||
| "purple"
|
||||
| "red"
|
||||
| "orange"
|
||||
| "teal"
|
||||
| "Blue"
|
||||
| "Green"
|
||||
| "Purple"
|
||||
| "Red"
|
||||
| "Orange"
|
||||
| "Teal";
|
||||
|
||||
interface ImagePlaceholderProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
text?: string;
|
||||
/**
|
||||
* Image placeholder color. Accepts both lowercase and PascalCase (case-insensitive).
|
||||
* Figma uses PascalCase, codebase uses lowercase - both are supported.
|
||||
*/
|
||||
color?: ImagePlaceholderColorValue;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple image placeholder component for testing
|
||||
* Generates colored backgrounds with text overlays
|
||||
*/
|
||||
const ImagePlaceholder = memo<ImagePlaceholderProps>(
|
||||
({
|
||||
width = 260,
|
||||
height = 390,
|
||||
text = "Blog Image",
|
||||
color: colorProp = "blue",
|
||||
className = "",
|
||||
}) => {
|
||||
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
|
||||
const color = normalizeImagePlaceholderColor(colorProp);
|
||||
const colors: Record<string, string> = {
|
||||
blue: "bg-blue-500",
|
||||
green: "bg-green-500",
|
||||
purple: "bg-purple-500",
|
||||
red: "bg-red-500",
|
||||
orange: "bg-orange-500",
|
||||
teal: "bg-teal-500",
|
||||
};
|
||||
|
||||
const bgColor = colors[color] || colors.blue;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${bgColor} flex items-center justify-center text-white font-bold text-lg ${className}`}
|
||||
style={{ width: `${width}px`, height: `${height}px` }}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ImagePlaceholder.displayName = "ImagePlaceholder";
|
||||
|
||||
export default ImagePlaceholder;
|
||||
@@ -4,6 +4,10 @@ import { memo, useCallback, useState } from "react";
|
||||
import InfoMessageBoxView from "./InfoMessageBox.view";
|
||||
import type { InfoMessageBoxProps } from "./InfoMessageBox.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InfoMessageBox" (TODO(figma)). Bordered message box that
|
||||
* lists checkbox items under a title with an optional leading icon.
|
||||
*/
|
||||
const InfoMessageBoxContainer = memo<InfoMessageBoxProps>(
|
||||
({
|
||||
title,
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
import { memo } from "react";
|
||||
import InputLabelView from "./InputLabel.view";
|
||||
import type { InputLabelProps } from "./InputLabel.types";
|
||||
import {
|
||||
normalizeInputLabelSize,
|
||||
normalizeInputLabelPalette,
|
||||
} from "../../../../lib/propNormalization";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / InputLabel" (TODO(figma)). Reusable form-input label with
|
||||
* optional asterisk, help icon, and helper text.
|
||||
*/
|
||||
const InputLabelContainer = memo<InputLabelProps>(
|
||||
({
|
||||
label,
|
||||
helpIcon = false,
|
||||
asterisk = false,
|
||||
helperText = false,
|
||||
size: sizeProp = "S",
|
||||
palette: paletteProp = "Default",
|
||||
size: sizeProp = "s",
|
||||
palette: paletteProp = "default",
|
||||
className = "",
|
||||
}) => {
|
||||
const size = normalizeInputLabelSize(sizeProp);
|
||||
const palette = normalizeInputLabelPalette(paletteProp);
|
||||
const size = sizeProp;
|
||||
const palette = paletteProp;
|
||||
|
||||
return (
|
||||
<InputLabelView
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export type InputLabelSizeValue = "S" | "M" | "s" | "m";
|
||||
export type InputLabelPaletteValue =
|
||||
| "Default"
|
||||
| "Inverse"
|
||||
| "default"
|
||||
| "inverse";
|
||||
export type InputLabelSizeValue = "s" | "m";
|
||||
export type InputLabelPaletteValue = "default" | "inverse";
|
||||
|
||||
export interface InputLabelProps {
|
||||
/**
|
||||
@@ -25,13 +21,11 @@ export interface InputLabelProps {
|
||||
*/
|
||||
helperText?: boolean | string;
|
||||
/**
|
||||
* Size variant: "S" (small) or "M" (medium)
|
||||
* Accepts both uppercase (Figma) and lowercase values.
|
||||
* Size variant: "s" (small) or "m" (medium)
|
||||
*/
|
||||
size?: InputLabelSizeValue;
|
||||
/**
|
||||
* Palette variant: "Default" or "Inverse"
|
||||
* Accepts both PascalCase (Figma) and lowercase values.
|
||||
* Palette variant: "default" or "inverse"
|
||||
*/
|
||||
palette?: InputLabelPaletteValue;
|
||||
className?: string;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { ModalFooterView } from "./ModalFooter.view";
|
||||
import type { ModalFooterProps } from "./ModalFooter.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / ModalFooter" (TODO(figma)).
|
||||
* Sticky modal footer slot used by the create-flow + login modals to host
|
||||
* primary/secondary actions.
|
||||
*/
|
||||
const ModalFooterContainer = memo<ModalFooterProps>((props) => {
|
||||
return <ModalFooterView {...props} />;
|
||||
});
|
||||
|
||||
ModalFooterContainer.displayName = "ModalFooter";
|
||||
|
||||
export default ModalFooterContainer;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ModalFooterView as default } from "./ModalFooter.view";
|
||||
export { default } from "./ModalFooter.container";
|
||||
export type { ModalFooterProps } from "./ModalFooter.types";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { ModalHeaderView } from "./ModalHeader.view";
|
||||
import type { ModalHeaderProps } from "./ModalHeader.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / ModalHeader" (TODO(figma)).
|
||||
* Sticky 48px modal header with optional close (left) and more-options
|
||||
* (right) icon buttons.
|
||||
*/
|
||||
const ModalHeaderContainer = memo<ModalHeaderProps>((props) => {
|
||||
return <ModalHeaderView {...props} />;
|
||||
});
|
||||
|
||||
ModalHeaderContainer.displayName = "ModalHeader";
|
||||
|
||||
export default ModalHeaderContainer;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ModalHeaderView as default } from "./ModalHeader.view";
|
||||
export { default } from "./ModalHeader.container";
|
||||
export type { ModalHeaderProps } from "./ModalHeader.types";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { ScrollbarView } from "./Scrollbar.view";
|
||||
import type { ScrollbarProps } from "./Scrollbar.types";
|
||||
|
||||
/**
|
||||
* Figma: "Utility / Scrollbar" (TODO(figma)).
|
||||
* Custom-styled scrollable wrapper. Most surfaces should attach
|
||||
* `SCROLLBAR_DESIGN_CLASS` directly instead of nesting through this view.
|
||||
*/
|
||||
const ScrollbarContainer = memo<ScrollbarProps>((props) => {
|
||||
return <ScrollbarView {...props} />;
|
||||
});
|
||||
|
||||
ScrollbarContainer.displayName = "Scrollbar";
|
||||
|
||||
export default ScrollbarContainer;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user