Point color, radius, stroke, focus, and type tokens at the Figma scale so later sections use the same values.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-11-02 17:03:43 -07:00
1 parent 63d5d21ae0
commit e808fe70bf
162 files changed
+1385 -1377

No files matched your search

@@ -51,9 +51,9 @@ function AddCustomFieldViewComponent({
<button
type="button"
onClick={onPressAdd}
className={`flex h-[88px] w-full shrink-0 cursor-pointer items-center justify-center rounded-[var(--measures-radius-medium,8px)] bg-[var(--color-surface-default-secondary)] px-6 text-medium-label text-[var(--color-content-default-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] ${className ?? ""}`.trim()}
className={`flex h-[88px] w-full shrink-0 cursor-pointer items-center justify-center rounded-[var(--radius-400)] bg-[var(--color-surface-default-secondary)] px-6 text-medium-label text-[var(--color-content-default-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] ${className ?? ""}`.trim()}
>
<span className="flex items-center gap-[var(--spacing-scale-006)] rounded-full px-4 py-3">
<span className="flex items-center gap-[var(--space-150)] rounded-full px-4 py-3">
<svg
width="20"
height="20"
@@ -56,7 +56,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Default state: tertiary border, with hover and focus states via CSS
// Hover changes border to brand primary color
// Focus removes border and shows shadow (double ring: 2px white inner, 4px dark outer)
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid border-[var(--color-border-default-tertiary,#464646)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:border-transparent focus:shadow-[0px_0px_0px_2px_var(--color-border-invert-primary,white),0px_0px_0px_4px_var(--color-border-default-primary,#141414)] focus:outline-none`;
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid border-[var(--color-border-default-control)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:border-transparent focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`;
}
// Inverse mode styles per Figma
@@ -64,7 +64,7 @@ const CheckboxContainer = memo<CheckboxProps>(
// Inverse: transparent background, white border
// Hover changes border to brand primary color
// Focus shows shadow (2px dark inner, 4px white outer) - note: reversed from standard
return `${baseBox} bg-transparent border border-solid border-[var(--color-border-invert-primary,white)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:shadow-[0px_0px_0px_2px_var(--color-border-default-primary,#141414),0px_0px_0px_4px_var(--color-border-invert-primary,white)] focus:outline-none`;
return `${baseBox} bg-transparent border border-solid border-[var(--color-border-invert-primary,white)] hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] focus:shadow-[var(--shadow-focus-default)] focus:outline-none`;
}
return baseBox;
@@ -76,12 +76,12 @@ const CheckboxContainer = memo<CheckboxProps>(
const checkGlyphColor = checked
? isStandard
? "var(--color-content-default-brand-primary, #fefcc9)" // Light yellow/cream for standard mode
: "var(--color-content-inverse-primary, #000000)" // Black for inverse mode
: "var(--color-content-invert-primary, #000000)" // Black for inverse mode
: "transparent";
// Label color
const labelColor = isInverse
? "var(--color-content-inverse-primary)"
? "var(--color-content-invert-primary)"
: "var(--color-content-default-primary)";
const handleToggle = (e: React.MouseEvent | React.KeyboardEvent) => {
@@ -39,7 +39,7 @@ export function CheckboxGroupView({
<span
className={`text-small-paragraph ${
mode === "inverse"
? "text-[var(--color-content-inverse-primary)]"
? "text-[var(--color-content-invert-primary)]"
: "text-[var(--color-content-default-primary)]"
}`}
>
@@ -48,7 +48,7 @@ export function CheckboxGroupView({
<span
className={`text-small-paragraph ${
mode === "inverse"
? "text-[var(--color-content-inverse-secondary,#1f1f1f)]"
? "text-[var(--color-content-invert-secondary,#1f1f1f)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]"
}`}
>
+15 -15
View File
@@ -32,20 +32,20 @@ function chipSurfaceStyle(
if (palette === "inverse") {
if (state === "disabled") {
return paint(
"var(--color-surface-inverse-tertiary)",
"var(--color-content-inverse-primary)",
"var(--color-surface-invert-tertiary)",
"var(--color-content-invert-primary)",
);
}
if (state === "selected") {
return paint(
"var(--color-surface-default-semi-opaque)",
"var(--color-content-inverse-primary)",
"var(--color-content-invert-primary)",
"var(--color-border-default-primary)",
);
}
return paint(
"transparent",
"var(--color-content-inverse-primary)",
"var(--color-content-invert-primary)",
"var(--color-border-default-primary)",
);
}
@@ -72,7 +72,7 @@ function chipSurfaceStyle(
return paint(
"transparent",
"var(--color-content-default-brand-primary, #fefcc9)",
"var(--color-border-default-tertiary, #464646)",
"var(--color-border-default-control)",
);
}
@@ -110,11 +110,11 @@ function ChipView({
// Custom state has different padding
const sizeClasses = isCustom
? isSmall
? "px-[var(--measures-spacing-100,4px)] py-[3px] text-x-small-paragraph"
: "px-[var(--measures-spacing-150,6px)] py-[10px] text-medium-paragraph"
? "px-[var(--space-100)] py-[3px] text-x-small-paragraph"
: "px-[var(--space-150)] py-[10px] text-medium-paragraph"
: isSmall
? "h-[30px] px-[var(--measures-spacing-200,8px)] gap-[var(--measures-spacing-050,2px)] text-x-small-label"
: "px-[var(--measures-spacing-300,12px)] py-[var(--measures-spacing-300,12px)] gap-[var(--measures-spacing-150,6px)] text-medium-label";
? "h-[30px] px-[var(--space-200)] gap-[var(--space-050)] text-x-small-label"
: "px-[var(--space-300)] py-[var(--space-300)] gap-[var(--space-150)] text-medium-label";
const baseClasses = `
appearance-none
@@ -122,7 +122,7 @@ function ChipView({
max-w-full
items-center
justify-center
rounded-[var(--measures-radius-full,9999px)]
rounded-[var(--radius-full)]
overflow-clip
box-border
focus:outline-none
@@ -185,7 +185,7 @@ function ChipView({
{onCheck && (
<button
type="button"
className="flex items-center justify-center p-[var(--measures-spacing-150,6px)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
aria-label={confirmAriaLabel}
disabled={!inputValue || !inputValue.trim()}
onClick={(event) => {
@@ -227,8 +227,8 @@ function ChipView({
className="bg-transparent border-none outline-none flex-1 min-w-0 font-normal text-[color:var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[color:var(--color-content-default-tertiary,#b4b4b4)]"
style={{
fontSize: isSmall
? "var(--sizing-300,12px)"
: "var(--sizing-400,16px)",
? "var(--sizing-300)"
: "var(--sizing-400)",
lineHeight: isSmall ? "16px" : "24px",
}}
onClick={(e) => e.stopPropagation()}
@@ -240,7 +240,7 @@ function ChipView({
{onClose && (
<button
type="button"
className="flex items-center justify-center p-[var(--measures-spacing-150,6px)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors"
className="flex items-center justify-center p-[var(--space-150)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.1))] transition-colors"
aria-label={closeAriaLabel}
onClick={(event) => {
event.stopPropagation();
@@ -309,7 +309,7 @@ function ChipView({
{onRemove && !isDisabled && (
<button
type="button"
className="ml-[var(--measures-spacing-050,2px)] p-[var(--measures-spacing-050,2px)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.05))]"
className="ml-[var(--space-050)] p-[var(--space-050)] rounded-full hover:bg-[var(--color-surface-default-semi-opaque,rgba(0,0,0,0.05))]"
aria-label={`Remove ${label}`}
onClick={(event) => {
event.stopPropagation();
@@ -4,7 +4,7 @@ import { memo } from "react";
import type { IncrementerViewProps } from "./Incrementer.types";
const STEP_BUTTON_CLASSES =
"bg-[var(--color-surface-default-secondary,#141414)] text-[var(--color-content-default-primary,#fff)] inline-flex shrink-0 items-center justify-center overflow-clip rounded-[var(--measures-radius-full,9999px)] px-[var(--space-200,8px)] py-[var(--measures-spacing-150,6px)] transition-[background,color,opacity] duration-200 ease-in-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary,#fff)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary,#000)] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:opacity-50";
"bg-[var(--color-surface-default-secondary,#141414)] text-[var(--color-content-default-primary,#fff)] inline-flex shrink-0 items-center justify-center overflow-clip rounded-[var(--radius-full)] px-[var(--space-200)] py-[var(--space-150)] transition-[background,color,opacity] duration-200 ease-in-out hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary,#fff)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary,#000)] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:opacity-50";
function MinusIcon() {
return (
@@ -75,7 +75,7 @@ function IncrementerView({
</button>
<span
aria-live="polite"
className={`shrink-0 whitespace-nowrap text-[length:var(--sizing-350,14px)] font-medium leading-[18px] ${valueColor}`}
className={`shrink-0 whitespace-nowrap text-[length:var(--sizing-350)] font-medium leading-[18px] ${valueColor}`}
>
{displayValue}
</span>
@@ -17,8 +17,11 @@ const IncrementerBlockContainer = ({
labelSize = "s",
palette = "default",
blockClassName = "",
state = "default",
disabled,
...incrementerProps
}: IncrementerBlockProps) => {
const isDisabled = state === "disabled" || Boolean(disabled);
return (
<IncrementerBlockView
label={label}
@@ -28,6 +31,8 @@ const IncrementerBlockContainer = ({
labelSize={labelSize}
palette={palette}
blockClassName={blockClassName}
state={state}
disabled={isDisabled}
{...incrementerProps}
/>
);
@@ -1,3 +1,4 @@
import type { IncrementerBlockStateValue } from "../../../../lib/propNormalization";
import type { IncrementerProps } from "../Incrementer/Incrementer.types";
import type {
InputLabelPaletteValue,
@@ -5,6 +6,11 @@ import type {
} from "../../type/InputLabel/InputLabel.types";
export interface IncrementerBlockProps extends IncrementerProps {
/**
* One state axis. Disabled dims the block and turns off both buttons.
* @default "default"
*/
state?: IncrementerBlockStateValue;
/** Label text displayed above the incrementer. */
label: string;
/** Show the help "?" icon next to the label. Defaults to `true`. */
@@ -31,6 +37,7 @@ export interface IncrementerBlockProps extends IncrementerProps {
}
export interface IncrementerBlockViewProps extends IncrementerProps {
state: IncrementerBlockStateValue;
label: string;
helpIcon: boolean;
helperText: boolean | string | undefined;
@@ -13,12 +13,14 @@ function IncrementerBlockView({
labelSize,
palette,
blockClassName,
state: _state,
disabled,
className,
...incrementerProps
}: IncrementerBlockViewProps) {
return (
<div
className={`flex flex-col items-start gap-[var(--measures-spacing-300,12px)] py-[8px] ${blockClassName}`.trim()}
className={`flex flex-col items-start gap-[var(--space-300)] py-[8px] ${disabled ? "opacity-60" : ""} ${blockClassName}`.trim()}
data-figma-node="19883:13283"
>
<InputLabel
@@ -29,7 +31,11 @@ function IncrementerBlockView({
size={labelSize}
palette={palette}
/>
<Incrementer {...incrementerProps} className={className} />
<Incrementer
{...incrementerProps}
disabled={disabled}
className={className}
/>
</div>
);
}
@@ -45,7 +45,7 @@ function InfoMessageBoxView({
return (
<div
className={`flex w-full min-w-0 items-start gap-[var(--measures-spacing-200,8px)] p-[var(--spacing-measures-spacing-500,20px)] rounded-[var(--measures-radius-300,12px)] border-l-2 border-solid border-[var(--color-border-default-secondary,#1f1f1f)] bg-[var(--color-content-inverse-secondary,#1f1f1f)] ${className}`}
className={`flex w-full min-w-0 items-start gap-[var(--space-200)] p-[var(--space-500)] rounded-[var(--radius-300)] border-l-2 border-solid border-[var(--color-border-default-secondary,#1f1f1f)] bg-[var(--color-content-invert-secondary,#1f1f1f)] ${className}`}
role="region"
aria-label={title}
>
@@ -15,10 +15,10 @@ export function InputWithCounterView({
}: InputWithCounterProps) {
const inputId = useId();
return (
<div className={`space-y-[var(--spacing-scale-008)] ${className}`}>
<div className={`space-y-[var(--space-200)] ${className}`}>
{/* Label with help icon */}
{label && (
<div className="flex items-center gap-[var(--spacing-scale-002)]">
<div className="flex items-center gap-[var(--space-050)]">
<label
htmlFor={inputId}
className="text-[14px] leading-[20px] font-medium text-[var(--color-content-default-primary)]"
@@ -43,12 +43,12 @@ export function InputWithCounterView({
width="12"
height="12"
>
<rect width="12" height="12" fill="#D9D9D9" />
<rect width="12" height="12" fill="var(--color-gray-200)" />
</mask>
<g mask="url(#mask0_21296_8257)">
<path
d="M5.99449 8.80766C6.13725 8.80766 6.25784 8.75838 6.35624 8.6598C6.45463 8.56123 6.50383 8.44055 6.50383 8.29779C6.50383 8.15502 6.45454 8.03444 6.35596 7.93605C6.25739 7.83765 6.13672 7.78845 5.99395 7.78845C5.85118 7.78845 5.7306 7.83774 5.63221 7.93631C5.53381 8.03489 5.48461 8.15556 5.48461 8.29833C5.48461 8.44109 5.5339 8.56168 5.63248 8.66008C5.73105 8.75847 5.85172 8.80766 5.99449 8.80766ZM5.64038 7.01729H6.34421C6.35062 6.77114 6.38668 6.5745 6.45239 6.42739C6.51809 6.28028 6.67754 6.08525 6.93075 5.8423C7.15062 5.62243 7.31905 5.41938 7.43604 5.23316C7.55302 5.04695 7.61151 4.82703 7.61151 4.57343C7.61151 4.14307 7.45687 3.80689 7.14759 3.5649C6.8383 3.32292 6.47243 3.20193 6.04999 3.20193C5.63269 3.20193 5.28734 3.3133 5.01394 3.53606C4.74055 3.75881 4.54552 4.02115 4.42885 4.32306L5.07114 4.58075C5.13204 4.41473 5.2362 4.25303 5.38364 4.09566C5.53108 3.93829 5.74999 3.8596 6.04038 3.8596C6.33589 3.8596 6.55432 3.94053 6.69566 4.1024C6.83701 4.26426 6.90769 4.4423 6.90769 4.63654C6.90769 4.80641 6.85929 4.96185 6.76249 5.10288C6.6657 5.2439 6.5423 5.38012 6.3923 5.51154C6.0641 5.80769 5.85673 6.0439 5.77019 6.22019C5.68365 6.39647 5.64038 6.66217 5.64038 7.01729ZM6.00082 10.75C5.34386 10.75 4.72634 10.6253 4.14828 10.376C3.5702 10.1266 3.06736 9.78827 2.63975 9.36085C2.21213 8.93343 1.8736 8.43081 1.62416 7.85299C1.37472 7.27518 1.25 6.65779 1.25 6.00083C1.25 5.34386 1.37467 4.72634 1.624 4.14828C1.87333 3.5702 2.21171 3.06736 2.63913 2.63975C3.06655 2.21213 3.56917 1.8736 4.14699 1.62416C4.7248 1.37472 5.34218 1.25 5.99915 1.25C6.65612 1.25 7.27363 1.37467 7.8517 1.624C8.42978 1.87333 8.93262 2.21171 9.36023 2.63913C9.78784 3.06655 10.1264 3.56917 10.3758 4.14699C10.6253 4.7248 10.75 5.34218 10.75 5.99915C10.75 6.65612 10.6253 7.27363 10.376 7.8517C10.1266 8.42978 9.78827 8.93262 9.36085 9.36023C8.93343 9.78784 8.43081 10.1264 7.85299 10.3758C7.27518 10.6253 6.65779 10.75 6.00082 10.75ZM5.99999 9.99999C7.11665 9.99999 8.06249 9.61249 8.83749 8.83749C9.61249 8.06249 9.99999 7.11665 9.99999 5.99999C9.99999 4.88332 9.61249 3.93749 8.83749 3.16249C8.06249 2.38749 7.11665 1.99999 5.99999 1.99999C4.88332 1.99999 3.93749 2.38749 3.16249 3.16249C2.38749 3.93749 1.99999 4.88332 1.99999 5.99999C1.99999 7.11665 2.38749 8.06249 3.16249 8.83749C3.93749 9.61249 4.88332 9.99999 5.99999 9.99999Z"
fill="var(--color-content-brand-darker-accent-2)"
fill="var(--color-content-default-brand-secondary)"
/>
</g>
</svg>
@@ -69,7 +69,7 @@ export function InputWithCounterView({
}
}}
maxLength={maxLength}
className={`w-full h-[36px] px-[12px] py-[8px] bg-[var(--color-surface-default-primary)] border-2 border-[var(--color-border-default-tertiary)] rounded-[var(--measures-radius-medium,8px)] text-medium-paragraph text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary)] focus:outline-none ${inputClassName}`}
className={`w-full h-[36px] px-[12px] py-[8px] bg-[var(--color-surface-default-primary)] border-2 border-[var(--color-border-default-tertiary)] rounded-[var(--radius-400)] text-medium-paragraph text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary)] focus:outline-none ${inputClassName}`}
/>
</div>
@@ -31,8 +31,8 @@ function MultiSelectView({
// Size-based spacing
const gapClass = isSmall
? "gap-[var(--measures-spacing-200,8px)]"
: "gap-[var(--measures-spacing-300,12px)]";
? "gap-[var(--space-200)]"
: "gap-[var(--space-300)]";
const chipSize = size;
const selectedCount = options.filter((o) => o.state === "selected").length;
@@ -43,7 +43,7 @@ function MultiSelectView({
return (
<div
className={`flex flex-col ${isSmall ? "gap-[var(--measures-spacing-200,8px)]" : "gap-[var(--measures-spacing-300,12px)]"} items-start relative w-full ${className}`}
className={`flex flex-col ${isSmall ? "gap-[var(--space-200)]" : "gap-[var(--space-300)]"} items-start relative w-full ${className}`}
>
{/* Label using InputLabel component */}
{formHeader && label && (
@@ -133,12 +133,12 @@ function MultiSelectView({
className={
!addButtonText
? // Circular button with border (Rule style)
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary,#141414)]" : "border-[var(--color-border-default-tertiary,#464646)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--measures-radius-full,9999px)] shrink-0 transition-opacity`
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} bg-[var(--color-surface-default-transparent,rgba(0,0,0,0))] border-[1.25px] ${isInverse ? "border-[var(--color-border-default-primary,#141414)]" : "border-[var(--color-border-default-control)]"} border-solid flex items-center justify-center ${isSmall ? "size-[30px]" : "size-[40px]"} rounded-[var(--radius-full)] shrink-0 transition-opacity`
: // Text add control (default palette: white label + brand “+”; inverse: inverse primary for both)
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} flex items-center justify-center overflow-hidden rounded-[var(--measures-radius-full,9999px)] shrink-0 transition-opacity ${
`${atLimit ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:opacity-80"} flex items-center justify-center overflow-hidden rounded-[var(--radius-full)] shrink-0 transition-opacity ${
isSmall
? "gap-[var(--measures-spacing-100,4px)] px-[var(--measures-spacing-300,12px)] py-[var(--measures-spacing-200,8px)]"
: "gap-[var(--measures-spacing-150,6px)] px-[var(--space-400,16px)] py-[var(--measures-spacing-300,12px)]"
? "gap-[var(--space-100)] px-[var(--space-300)] py-[var(--space-200)]"
: "gap-[var(--space-150)] px-[var(--space-400)] py-[var(--space-300)]"
}`
}
>
@@ -151,7 +151,7 @@ function MultiSelectView({
xmlns="http://www.w3.org/2000/svg"
className={`shrink-0 ${
!addButtonText && isInverse
? "text-[var(--color-content-inverse-primary,black)]"
? "text-[var(--color-content-invert-primary,black)]"
: "text-[var(--color-content-default-brand-primary,#fefcc9)]"
}`}
>
@@ -165,9 +165,9 @@ function MultiSelectView({
</svg>
{addButtonText && (
<span
className={`font-medium ${isSmall ? "text-[length:var(--sizing-300,12px)] leading-[14px]" : "text-medium-label"} ${
className={`font-medium ${isSmall ? "text-[length:var(--sizing-300)] leading-[14px]" : "text-medium-label"} ${
isInverse
? "text-[color:var(--color-content-inverse-primary,black)]"
? "text-[color:var(--color-content-invert-primary,black)]"
: "text-[color:var(--color-content-default-primary,white)]"
}`}
>
@@ -54,14 +54,14 @@ const RadioButtonContainer = ({
// When checked, border is brand primary (but changes to invert tertiary on focus)
const defaultBorder = checked
? "border-[var(--color-border-default-brand-primary,#fdfaa8)]"
: "border-[var(--color-border-default-tertiary,#464646)]";
: "border-[var(--color-border-default-control)]";
// When focused and checked, border should be invert tertiary (#2d2d2d) per Figma
const focusBorder = checked
? "focus:border-[var(--color-content-invert-tertiary,#2d2d2d)]"
: "focus:border-[var(--color-border-default-tertiary,#464646)]";
: "focus:border-[var(--color-border-default-control)]";
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] ${focusBorder} focus:shadow-[0px_0px_0px_2px_var(--color-border-invert-primary,white),0px_0px_0px_4px_var(--color-border-default-primary,#141414)] focus:outline-none`;
return `${baseBox} bg-[var(--color-surface-default-primary)] border border-solid ${defaultBorder} hover:border-[var(--color-border-default-brand-primary,#fdfaa8)] ${focusBorder} focus:shadow-[var(--shadow-focus-invert)] focus:outline-none`;
}
// Inverse mode styles
@@ -83,7 +83,7 @@ const RadioButtonContainer = ({
? "focus:border-[var(--color-border-invert-primary,white)]"
: "focus:border-[var(--color-border-invert-primary,white)]";
return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[0px_0px_0px_2px_var(--color-border-default-primary,#141414),0px_0px_0px_4px_var(--color-border-invert-primary,white)] focus:outline-none`;
return `${baseBox} bg-transparent border border-solid ${defaultBorder} ${hoverBorder} ${focusBorder} focus:shadow-[var(--shadow-focus-default)] focus:outline-none`;
}
return baseBox;
@@ -93,7 +93,7 @@ const RadioButtonContainer = ({
// Label color
const labelColor = isInverse
? "var(--color-content-inverse-primary)"
? "var(--color-content-invert-primary)"
: "var(--color-content-default-primary)";
// Generate unique ID for accessibility if not provided
@@ -43,7 +43,7 @@ export function RadioGroupView({
<span
className={`text-small-paragraph ${
mode === "inverse"
? "text-[var(--color-content-inverse-primary)]"
? "text-[var(--color-content-invert-primary)]"
: "text-[var(--color-content-default-primary)]"
}`}
>
@@ -52,7 +52,7 @@ export function RadioGroupView({
<span
className={`text-small-paragraph ${
mode === "inverse"
? "text-[var(--color-content-inverse-secondary,#1f1f1f)]"
? "text-[var(--color-content-invert-secondary,#1f1f1f)]"
: "text-[var(--color-content-default-secondary,#b4b4b4)]"
}`}
>
@@ -11,9 +11,9 @@ interface SelectDropdownProps extends React.HTMLAttributes<HTMLDivElement> {
const SelectDropdown = forwardRef<HTMLDivElement, SelectDropdownProps>(
({ className = "", children, ariaLabel, ...props }, ref) => {
const menuClasses = `
bg-black
bg-[var(--color-surface-default-primary)]
border border-[var(--color-border-default-tertiary)]
rounded-[var(--measures-radius-medium)]
rounded-[var(--radius-400)]
shadow-lg
p-[4px]
min-w-[200px]
@@ -29,7 +29,6 @@ const SelectDropdown = forwardRef<HTMLDivElement, SelectDropdownProps>(
className={menuClasses}
role="listbox"
aria-label={ariaLabel}
style={{ backgroundColor: "#000000" }}
{...props}
>
{children}
@@ -109,7 +109,7 @@ export function SelectInputView({
.replace(/\s+/g, " ");
if (disabled) {
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-inverse-tertiary,#2d2d2d)] border-[var(--color-border-default-primary)] cursor-not-allowed opacity-40`;
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary,#2d2d2d)] border-[var(--color-border-default-primary)] cursor-not-allowed opacity-40`;
}
if (error) {
@@ -118,16 +118,16 @@ export function SelectInputView({
if (state === "focus") {
// Focus state: secondary background, tertiary border, with focus ring
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-tertiary)]`;
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-control)]`;
}
if (state === "active" || isOpen) {
// Active state per Figma: secondary background, tertiary border
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border-[var(--color-border-default-tertiary)]`;
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border-[var(--color-border-default-control)]`;
}
// Default state per Figma: secondary background, primary border (subtle)
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border-[var(--color-border-default-primary)]`;
return `${baseClasses} bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border-[var(--color-border-default-control)]`;
};
const buttonClasses = getButtonClasses();
@@ -145,8 +145,8 @@ export function SelectInputView({
return (
<div className={containerClasses}>
{label && (
<div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full">
<div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0">
<div className="flex flex-wrap gap-[var(--space-100)_var(--space-200)] items-baseline pr-[var(--space-100)] relative shrink-0 w-full">
<div className="flex gap-[var(--space-050)] items-center relative shrink-0">
<label id={labelId} className={labelClasses}>
{label}
</label>
@@ -214,7 +214,7 @@ export function SelectInputView({
</button>
{state === "focus" && (
<div
className="absolute border-2 border-solid border-[var(--color-border-inverse-primary)] inset-0 rounded-[8px] shadow-[0px_0px_0px_2px_var(--color-border-default-primary)] pointer-events-none z-10"
className="absolute border-2 border-solid border-[var(--color-border-invert-primary)] inset-0 rounded-[8px] shadow-[0px_0px_0px_2px_var(--color-border-default-primary)] pointer-events-none z-10"
aria-hidden="true"
/>
)}
@@ -269,7 +269,13 @@ export function SelectInputView({
</div>
{textHint && (
<div className="flex items-start relative shrink-0 w-full">
<p className="flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative text-[color:var(--color-content-default-tertiary,#b4b4b4)]">
<p
className={`flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative ${
ariaInvalid
? "text-[color:var(--color-content-default-negative-primary)]"
: "text-[color:var(--color-content-default-tertiary)]"
}`}
>
{hintDefault}
</p>
</div>
@@ -44,13 +44,13 @@ const SelectOptionContainer = forwardRef<HTMLDivElement, SelectOptionProps>(
transition-colors duration-150
${
selected
? "bg-[var(--color-surface-default-secondary)] rounded-[var(--measures-radius-small)]"
? "bg-[var(--color-surface-default-secondary)] rounded-[var(--radius-300)]"
: ""
}
${
disabled
? "opacity-50 cursor-not-allowed"
: "hover:!bg-[var(--color-surface-default-secondary)] hover:!rounded-[var(--measures-radius-small)]"
: "hover:!bg-[var(--color-surface-default-secondary)] hover:!rounded-[var(--radius-300)]"
}
${className}
`
@@ -69,7 +69,7 @@ const SwitchContainer = memo(
// Switch track styles based on propSwitch state
const getTrackStyles = useCallback(() => {
return propSwitch
? "bg-[var(--color-surface-inverse-tertiary)]"
? "bg-[var(--color-surface-invert-tertiary)]"
: "bg-[var(--color-surface-default-tertiary)]";
}, [propSwitch]);
@@ -81,7 +81,7 @@ const SwitchContainer = memo(
// Focus styles
const getFocusStyles = useCallback(() => {
if (state === "focus") {
return "shadow-[0_0_5px_3px_#3281F8] rounded-full";
return "shadow-[var(--shadow-focus-default)] rounded-full";
}
return "";
}, [state]);
@@ -98,7 +98,7 @@ const SwitchContainer = memo(
transition-all
duration-200
focus:outline-none
focus-visible:shadow-[0_0_5px_3px_#3281F8]
focus-visible:shadow-[var(--shadow-focus-default)]
focus-visible:rounded-full
${focusStyles}
${className}
@@ -123,9 +123,9 @@ const SwitchContainer = memo(
const thumbClasses = `
${thumbStyles}
w-[var(--measures-sizing-020)]
h-[var(--measures-sizing-020)]
rounded-[var(--measures-radius-xlarge)]
w-[var(--space-500)]
h-[var(--space-500)]
rounded-[var(--radius-500)]
transition-all
duration-200
shadow-sm
@@ -134,7 +134,7 @@ const SwitchContainer = memo(
.replace(/\s+/g, " ");
const labelClasses = `
ml-[var(--measures-spacing-008)]
ml-[var(--space-200)]
text-small-paragraph
text-[var(--color-content-default-primary)]
`
@@ -61,7 +61,7 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
: "h-[60px] px-[12px] py-[8px] text-xx-small-paragraph",
label: "text-x-small-label",
container: "gap-[4px]",
radius: "var(--measures-radius-xsmall)",
radius: "var(--radius-200)",
},
medium: {
textarea:
@@ -70,13 +70,13 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
: "h-[100px] px-[12px] py-[8px] text-small-paragraph",
label: "text-small-label",
container: "gap-[8px]",
radius: "var(--measures-radius-xsmall)",
radius: "var(--radius-200)",
},
large: {
textarea: "h-[150px] px-[12px] py-[8px] text-medium-paragraph",
label: "text-medium-label",
container: "gap-[12px]",
radius: "var(--measures-radius-small)",
radius: "var(--radius-300)",
},
};
@@ -90,13 +90,13 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
if (disabled) {
return {
textarea:
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60 min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)]",
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60 min-h-[356px] h-[356px] px-[var(--space-300)] py-[var(--space-250)]",
label: "text-[var(--color-content-default-secondary)]",
};
}
return {
textarea:
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)] focus:outline-none focus:ring-0",
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] min-h-[356px] h-[356px] px-[var(--space-300)] py-[var(--space-250)] focus:outline-none focus:ring-0",
label: "text-[var(--color-content-default-secondary)]",
};
}
@@ -111,7 +111,7 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
}
return {
textarea:
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] focus:text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset",
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] focus:text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-control)] focus:ring-inset",
label: "text-[var(--color-content-default-secondary)]",
};
}
@@ -119,7 +119,7 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
if (disabled) {
return {
textarea:
"bg-[var(--color-content-default-secondary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] cursor-not-allowed",
"bg-[var(--color-content-default-secondary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-control)] cursor-not-allowed",
label: "text-[var(--color-content-default-secondary)]",
};
}
@@ -136,25 +136,25 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
case "active":
return {
textarea:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-control)]",
label: "text-[var(--color-content-default-secondary)]",
};
case "hover":
return {
textarea:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-control)] shadow-[0_0_0_2px_var(--color-border-default-control)]",
label: "text-[var(--color-content-default-secondary)]",
};
case "focus":
return {
textarea:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-utility-info)] shadow-[0_0_5px_3px_#3281F8]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-control)] shadow-[var(--shadow-focus-default)]",
label: "text-[var(--color-content-default-secondary)]",
};
default:
return {
textarea:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-tertiary)] hover:shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border border-[var(--color-border-default-control)] hover:shadow-[0_0_0_2px_var(--color-border-default-control)]",
label: "text-[var(--color-content-default-secondary)]",
};
}
@@ -176,7 +176,7 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
const textareaClasses = `
scrollbar-design w-full transition-all duration-200 ease-in-out resize-none
${appearance === "embedded" || appearance === "compose" ? "rounded-[var(--radius-300,12px)]" : "border"}
${appearance === "embedded" || appearance === "compose" ? "rounded-[var(--radius-300)]" : "border"}
${appearance === "compose" ? "text-medium-paragraph" : currentSize.textarea}
${stateStyles.textarea}
${className}
@@ -31,15 +31,15 @@ export const TextAreaView = forwardRef<HTMLTextAreaElement, TextAreaViewProps>(
size: _size,
labelVariant: _labelVariant,
state: _state,
error: _error,
error = false,
...props
},
ref,
) => {
const labelEl = label ? (
formHeader ? (
<div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full">
<div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0">
<div className="flex flex-wrap gap-[var(--space-100)_var(--space-200)] items-baseline pr-[var(--space-100)] relative shrink-0 w-full">
<div className="flex gap-[var(--space-050)] items-center relative shrink-0">
<label
id={labelId}
htmlFor={textareaId}
@@ -90,7 +90,13 @@ export const TextAreaView = forwardRef<HTMLTextAreaElement, TextAreaViewProps>(
</div>
{textHint ? (
<div className="flex items-start relative shrink-0 w-full">
<p className="flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative text-[color:var(--color-content-default-tertiary,#b4b4b4)]">
<p
className={`flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative ${
error
? "text-[color:var(--color-content-default-negative-primary)]"
: "text-[color:var(--color-content-default-tertiary)]"
}`}
>
{typeof textHint === "string" ? textHint : hintDefault}
</p>
</div>
@@ -83,13 +83,13 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
"h-[32px] px-[10px] py-[6px] text-[16px] md:text-[14px]",
label: "text-[12px] leading-[16px] font-medium",
container: "gap-[6px]",
radius: "var(--measures-radius-200,8px)",
radius: "var(--radius-200)",
}
: {
input: "h-[40px] px-[12px] py-[8px] text-[16px]",
label: "text-[14px] leading-[20px] font-medium",
container: "gap-[8px]",
radius: "var(--measures-radius-200,8px)",
radius: "var(--radius-200)",
};
// State styles based on Figma designs
@@ -102,7 +102,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
if (disabled) {
return {
input:
"bg-[var(--color-surface-default-secondary)] text-[var(--color-content-inverse-tertiary,#2d2d2d)] border border-solid border-[var(--color-border-default-primary)] cursor-not-allowed",
"bg-[var(--color-surface-default-secondary)] text-[var(--color-content-invert-tertiary,#2d2d2d)] border border-solid border-[var(--color-border-default-primary)] cursor-not-allowed",
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
@@ -127,7 +127,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
return {
input: `bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-2 border-solid border-[var(--color-border-default-tertiary)] ${filledStyles}`,
input: `bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] border-2 border-solid border-[var(--color-border-default-control)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
@@ -138,11 +138,11 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
? "font-medium leading-[20px]"
: "font-normal leading-[24px]";
return {
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-tertiary)] ${filledStyles}`,
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-control)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing:
"absolute border-2 border-solid border-[var(--color-border-inverse-primary)] inset-0 rounded-[var(--measures-radius-200,8px)] shadow-[0px_0px_0px_2px_var(--color-border-default-primary)] pointer-events-none",
"absolute inset-0 rounded-[var(--radius-200)] shadow-[var(--shadow-focus-default)] pointer-events-none",
};
}
default: {
@@ -151,7 +151,7 @@ const TextInputContainer = forwardRef<HTMLInputElement, TextInputProps>(
: "font-normal leading-[24px]";
// Default state uses primary border (matches Figma - border color same as background, so border is subtle)
return {
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-primary)] ${filledStyles}`,
input: `bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] border border-solid border-[var(--color-border-default-control)] ${filledStyles}`,
label: "text-[var(--color-content-default-secondary)]",
inputWrapper: "relative",
focusRing: "",
@@ -43,8 +43,8 @@ export const TextInputView = forwardRef<HTMLInputElement, TextInputViewProps>(
) => {
const labelEl = label ? (
formHeader ? (
<div className="flex flex-wrap gap-[var(--measures-spacing-200,4px_8px)] items-baseline pr-[var(--measures-spacing-100,4px)] relative shrink-0 w-full">
<div className="flex gap-[var(--measures-spacing-050,2px)] items-center relative shrink-0">
<div className="flex flex-wrap gap-[var(--space-100)_var(--space-200)] items-baseline pr-[var(--space-100)] relative shrink-0 w-full">
<div className="flex gap-[var(--space-050)] items-center relative shrink-0">
<label
id={labelId}
htmlFor={inputId}
@@ -106,7 +106,13 @@ export const TextInputView = forwardRef<HTMLInputElement, TextInputViewProps>(
</div>
{textHint && (
<div className="flex items-start relative shrink-0 w-full">
<p className="flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative text-[color:var(--color-content-default-tertiary,#b4b4b4)]">
<p
className={`flex-[1_0_0] text-x-small-paragraph min-h-px min-w-px relative ${
error
? "text-[color:var(--color-content-default-negative-primary)]"
: "text-[color:var(--color-content-default-tertiary)]"
}`}
>
{typeof textHint === "string" ? textHint : hintDefault}
</p>
</div>
@@ -33,7 +33,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
// Size styles - single size with specific dimensions
const sizeStyles = {
toggle: "h-[var(--measures-sizing-032)] px-[16px] py-[8px] gap-[4px]",
toggle: "h-[var(--space-800)] px-[16px] py-[8px] gap-[4px]",
label: "text-[12px] leading-[16px]",
};
@@ -61,7 +61,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
case "focus":
return {
toggle:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[0_0_5px_1px_#3281F8]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[var(--shadow-focus-default)]",
label: "text-[var(--color-content-default-secondary)]",
};
default:
@@ -82,7 +82,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
case "focus":
return {
toggle:
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[0_0_5px_1px_#3281F8]",
"bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[var(--shadow-focus-default)]",
label: "text-[var(--color-content-default-secondary)]",
};
default:
@@ -112,7 +112,7 @@ const ToggleContainer = forwardRef<HTMLButtonElement, ToggleProps>(
transition-all
duration-200
focus:outline-none
focus-visible:shadow-[0_0_5px_1px_#3281F8]
focus-visible:shadow-[var(--shadow-focus-default)]
${!checked ? "hover:!bg-[var(--color-surface-default-secondary)]" : ""}
flex
items-center
@@ -34,13 +34,13 @@ const ToggleGroupContainer = memo(
const getPositionStyles = useCallback((pos: string): string => {
switch (pos) {
case "left":
return "rounded-l-[var(--measures-radius-medium)] rounded-r-none";
return "rounded-l-[var(--radius-400)] rounded-r-none";
case "middle":
return "rounded-none";
case "right":
return "rounded-r-[var(--measures-radius-medium)] rounded-l-none";
return "rounded-r-[var(--radius-400)] rounded-l-none";
default:
return "rounded-[var(--measures-radius-medium)]";
return "rounded-[var(--radius-400)]";
}
}, []);
@@ -50,7 +50,7 @@ const ToggleGroupContainer = memo(
case "hover":
return "bg-[var(--color-magenta-magenta100)] text-[var(--color-content-default-primary)]";
case "focus":
return "bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[0_0_5px_1px_#3281F8]";
return "bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] shadow-[var(--shadow-focus-default)]";
case "selected":
return "bg-[var(--color-magenta-magenta100)] text-[var(--color-content-default-primary)] shadow-[inset_0_0_0_1px_var(--color-border-default-secondary)]";
case "default":
@@ -104,9 +104,9 @@ const ToggleGroupContainer = memo(
const toggleClasses = `
${positionStyles}
${stateStyles}
py-[var(--measures-spacing-008)]
px-[var(--measures-spacing-008)]
gap-[var(--measures-spacing-008)]
py-[var(--space-200)]
px-[var(--space-200)]
gap-[var(--space-200)]
font-medium
text-[12px]
leading-[12px]
@@ -114,7 +114,7 @@ const ToggleGroupContainer = memo(
transition-all
duration-200
focus:outline-none
focus-visible:shadow-[0_0_5px_1px_#3281F8]
focus-visible:shadow-[var(--shadow-focus-default)]
hover:bg-[var(--color-magenta-magenta100)]
flex
items-center
@@ -3,6 +3,7 @@
import { memo } from "react";
import { useTranslation } from "../../../contexts/MessagesContext";
import UploadView from "./Upload.view";
import type { UploadStateValue } from "../../../../lib/propNormalization";
import type { UploadProps } from "./Upload.types";
/**
@@ -12,6 +13,8 @@ import type { UploadProps } from "./Upload.types";
const UploadContainer = memo<UploadProps>(
({
active = true,
state,
hasLabel,
label,
showHelpIcon = true,
hintText,
@@ -19,10 +22,13 @@ const UploadContainer = memo<UploadProps>(
className = "",
}) => {
const t = useTranslation("controlsChrome");
const resolvedState: UploadStateValue =
state ?? (active ? "default" : "disabled");
return (
<UploadView
active={active}
state={resolvedState}
hasLabel={hasLabel ?? Boolean(label)}
label={label}
showHelpIcon={showHelpIcon}
hintText={hintText ?? t("uploadHintDefault")}
+14 -1
View File
@@ -1,11 +1,23 @@
import type { UploadStateValue } from "../../../../lib/propNormalization";
export interface UploadProps {
/**
* Whether the upload component is in active state.
* When active, button has white background with black text.
* When inactive, button has dark background with gray text.
* Prefer `state`. `active={false}` maps to `disabled`.
* @default true
*/
active?: boolean;
/**
* Figma state axis. Disabled dims the control and turns the button off.
*/
state?: UploadStateValue;
/**
* Show the label. The upload step uses the control with this off.
* Defaults to true when `label` is set.
*/
hasLabel?: boolean;
/**
* Label text displayed above the upload component
*/
@@ -31,7 +43,8 @@ export interface UploadProps {
}
export interface UploadViewProps {
active: boolean;
state: UploadStateValue;
hasLabel: boolean;
label?: string;
showHelpIcon: boolean;
hintText: string;
+20 -9
View File
@@ -5,7 +5,8 @@ import InputLabel from "../../type/InputLabel";
import type { UploadViewProps } from "./Upload.types";
function UploadView({
active = true,
state,
hasLabel,
label,
showHelpIcon = true,
hintText,
@@ -14,7 +15,8 @@ function UploadView({
onClick,
className = "",
}: UploadViewProps) {
const isActive = active;
const isActive = state !== "disabled";
const isDisabled = state === "disabled";
// Button styles based on active state
const buttonBgClass = isActive
@@ -37,10 +39,10 @@ function UploadView({
return (
<div
className={`flex flex-col gap-[var(--measures-spacing-300,12px)] items-start relative w-full ${className}`}
className={`flex flex-col gap-[var(--space-300)] items-start relative w-full ${className}`}
>
{/* Label using InputLabel component */}
{label && (
{hasLabel && label ? (
<InputLabel
label={label}
helpIcon={showHelpIcon}
@@ -49,15 +51,24 @@ function UploadView({
size="s"
palette="default"
/>
)}
) : null}
{/* Upload container — max width per create-flow spec */}
<div className="bg-[var(--color-surface-default-secondary,#141414)] mx-auto flex w-full max-w-[474px] shrink-0 items-center justify-center gap-[24px] rounded-[var(--measures-radius-200,8px)] px-[var(--measures-spacing-600,24px)] py-[var(--measures-spacing-1200,48px)]">
<div
className={`bg-[var(--color-surface-default-secondary,#141414)] mx-auto flex w-full max-w-[474px] shrink-0 items-center justify-center gap-[24px] rounded-[var(--radius-200)] px-[var(--space-600)] py-[var(--space-1200)] ${
state === "error"
? "border border-[var(--color-border-default-utility-negative)]"
: ""
} ${state === "focus" ? "shadow-[var(--shadow-focus-default)]" : ""}`}
>
{/* Upload button */}
<button
type="button"
onClick={onClick}
className={`${buttonBgClass} flex gap-[var(--measures-spacing-150,6px)] items-center justify-center overflow-clip px-[var(--space-400,16px)] py-[var(--measures-spacing-300,12px)] rounded-[var(--measures-radius-full,9999px)] shrink-0 hover:opacity-80 transition-opacity`}
disabled={isDisabled}
className={`${buttonBgClass} flex gap-[var(--space-150)] items-center justify-center overflow-clip px-[var(--space-400)] py-[var(--space-300)] rounded-[var(--radius-full)] shrink-0 hover:opacity-80 transition-opacity ${
state === "hover" ? "opacity-80" : ""
} ${state === "active" ? "scale-[0.98]" : ""}`}
aria-label={uploadAriaLabel}
>
{/* Upload icon */}
@@ -98,7 +109,7 @@ function UploadView({
</div>
{/* Button text */}
<div
className={`flex flex-col font-medium justify-center leading-[0] relative shrink-0 text-[length:var(--sizing-400,16px)] whitespace-nowrap ${buttonTextColor}`}
className={`flex flex-col font-medium justify-center leading-[0] relative shrink-0 text-[length:var(--sizing-400)] whitespace-nowrap ${buttonTextColor}`}
>
<p className="leading-[20px]">{uploadButtonLabel}</p>
</div>
@@ -106,7 +117,7 @@ function UploadView({
{/* Description text */}
<div
className={`flex flex-[1_0_0] flex-col font-normal h-[32px] justify-center leading-[0] min-h-px min-w-px relative text-[length:var(--sizing-350,14px)] ${descriptionTextColor}`}
className={`flex flex-[1_0_0] flex-col font-normal h-[32px] justify-center leading-[0] min-h-px min-w-px relative text-[length:var(--sizing-350)] ${descriptionTextColor}`}
>
<p className="leading-[20px] whitespace-pre-wrap">{hintText}</p>
</div>