App reorganization

This commit is contained in:
adilallo
2026-04-18 14:12:49 -06:00
parent f866d11ff8
commit e9dab04b34
288 changed files with 2698 additions and 5029 deletions
@@ -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;
+10 -11
View File
@@ -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";
+13 -19
View File
@@ -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 {
@@ -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":
@@ -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"
/>
)}