diff --git a/app/components-preview/page.tsx b/app/components-preview/page.tsx index 8998bfc..8d9df16 100644 --- a/app/components-preview/page.tsx +++ b/app/components-preview/page.tsx @@ -2,13 +2,16 @@ import { useState } from "react"; import TextInput from "../components/TextInput"; -import SelectInput from "../components/SelectInput"; +import Checkbox from "../components/Checkbox"; +import RadioGroup from "../components/RadioGroup"; export default function ComponentsPreview() { const [defaultInputValue, setDefaultInputValue] = useState(""); const [activeInputValue, setActiveInputValue] = useState(""); const [errorInputValue, setErrorInputValue] = useState(""); - const [selectValue, setSelectValue] = useState(""); + const [standardCheckbox, setStandardCheckbox] = useState(false); + const [inverseCheckbox, setInverseCheckbox] = useState(false); + const [radioValue, setRadioValue] = useState(""); return (
@@ -67,10 +70,48 @@ export default function ComponentsPreview() {
- {/* Select Input Section */} + {/* Checkbox Section */}

- Select Input Component + Checkbox Component +

+ +
+
+
+

+ Standard Mode +

+
+ setStandardCheckbox(checked)} + /> +
+
+
+

+ Inverse Mode +

+
+ setInverseCheckbox(checked)} + /> +
+
+
+
+
+ + {/* Radio Group Section */} +
+

+ Radio Group Component

@@ -80,9 +121,8 @@ export default function ComponentsPreview() { States
- - setSelectValue(data.target.value)} + setRadioValue(value)} options={[ { value: "option1", label: "Option 1" }, { value: "option2", label: "Option 2" }, { value: "option3", label: "Option 3" }, ]} /> - -
diff --git a/app/components/Checkbox/Checkbox.container.tsx b/app/components/Checkbox/Checkbox.container.tsx index 1961614..a8a00b1 100644 --- a/app/components/Checkbox/Checkbox.container.tsx +++ b/app/components/Checkbox/Checkbox.container.tsx @@ -21,49 +21,59 @@ const CheckboxContainer = memo( ...props }) => { const isInverse = mode === "inverse"; + const isStandard = mode === "standard"; - // Base tokens (rough placeholders leveraging existing CSS variables) - const colorContent = isInverse - ? "var(--color-content-inverse-primary)" - : "var(--color-content-default-primary)"; + // Generate unique ID for accessibility if not provided + const { id: checkboxId, labelId } = useComponentId("checkbox", id); - // Visual container depending on state - const baseBox = `flex items-center justify-center shrink-0 w-[var(--measures-sizing-024)] h-[var(--measures-sizing-024)] rounded-[var(--measures-radius-medium)] transition-all duration-200 ease-in-out`; + // Base box styles per Figma + const baseBox = ` + flex + items-center + justify-center + shrink-0 + w-[24px] + h-[24px] + rounded-[4px] + transition-all + duration-200 + ease-in-out + `.trim().replace(/\s+/g, " "); - const stateStyles: Record = { - default: "", - hover: "", - focus: "", + // Get box styles based on state and checked status per Figma designs + const getBoxStyles = (): string => { + // Standard mode styles + if (isStandard) { + // 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`; + } + + // Inverse mode styles per Figma + if (isInverse) { + // 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; }; - // Background behavior: - // - Standard: background does not change on check; only checkmark appears - // - Inverse: transparent background, checkmark appears on check - const backgroundWhenChecked = isInverse - ? "var(--color-surface-default-transparent)" - : "var(--color-surface-default-primary)"; + const combinedBoxStyles = getBoxStyles(); + + // Checkmark color per Figma const checkGlyphColor = checked - ? isInverse - ? "var(--color-content-inverse-primary)" - : "var(--color-border-default-brand-primary)" + ? isStandard + ? "var(--color-content-default-brand-primary, #fefcc9)" // Light yellow/cream for standard mode + : "var(--color-content-inverse-primary, #000000)" // Black for inverse mode : "transparent"; - const labelColor = colorContent; - const combinedBoxStyles = `${baseBox} ${stateStyles[state]}`; - - // Force visible outline for standard / default / unchecked - // Outline classes instead of inline styles so hover can override - const defaultOutlineClass = isInverse - ? "outline outline-1 outline-[var(--color-border-inverse-primary)]" - : "outline outline-1 outline-[var(--color-border-default-tertiary)]"; - - // Apply brand outline only on actual :hover, and only when standard/unchecked - const conditionalHoverOutlineClass = - "hover:outline hover:outline-1 hover:outline-[var(--color-border-default-brand-primary)]"; - - // Focus state for standard/unchecked with brand primary color and specific blur/spread - const conditionalFocusClass = - "focus:outline focus:outline-1 focus:outline-[var(--color-border-default-utility-info)] focus:shadow-[0_0_10px_1px_var(--color-surface-inverse-brand-primary)]"; + // Label color + const labelColor = isInverse + ? "var(--color-content-inverse-primary)" + : "var(--color-content-default-primary)"; const handleToggle = (e: React.MouseEvent | React.KeyboardEvent) => { if (disabled) return; @@ -74,9 +84,6 @@ const CheckboxContainer = memo( }); }; - // Generate unique ID for accessibility if not provided - const { id: checkboxId, labelId } = useComponentId("checkbox", id); - const accessibilityProps = { role: "checkbox" as const, "aria-checked": checked, @@ -107,10 +114,6 @@ const CheckboxContainer = memo( value={value} className={className} combinedBoxStyles={combinedBoxStyles} - defaultOutlineClass={defaultOutlineClass} - conditionalHoverOutlineClass={conditionalHoverOutlineClass} - conditionalFocusClass={conditionalFocusClass} - backgroundWhenChecked={backgroundWhenChecked} checkGlyphColor={checkGlyphColor} labelColor={labelColor} accessibilityProps={accessibilityProps} diff --git a/app/components/Checkbox/Checkbox.types.ts b/app/components/Checkbox/Checkbox.types.ts index 2a0d28d..70ba736 100644 --- a/app/components/Checkbox/Checkbox.types.ts +++ b/app/components/Checkbox/Checkbox.types.ts @@ -27,10 +27,6 @@ export interface CheckboxViewProps { value?: string; className: string; combinedBoxStyles: string; - defaultOutlineClass: string; - conditionalHoverOutlineClass: string; - conditionalFocusClass: string; - backgroundWhenChecked: string; checkGlyphColor: string; labelColor: string; accessibilityProps: React.HTMLAttributes; diff --git a/app/components/Checkbox/Checkbox.view.tsx b/app/components/Checkbox/Checkbox.view.tsx index f4d4144..b6b2a14 100644 --- a/app/components/Checkbox/Checkbox.view.tsx +++ b/app/components/Checkbox/Checkbox.view.tsx @@ -9,10 +9,6 @@ export function CheckboxView({ value, className, combinedBoxStyles, - defaultOutlineClass, - conditionalHoverOutlineClass, - conditionalFocusClass, - backgroundWhenChecked, checkGlyphColor, labelColor, accessibilityProps, @@ -30,18 +26,16 @@ export function CheckboxView({ {...accessibilityProps} onClick={onToggle} onKeyDown={onKeyDown} - className={`${combinedBoxStyles} ${defaultOutlineClass} ${conditionalHoverOutlineClass} ${conditionalFocusClass} p-[var(--measures-spacing-004)]`} - style={{ - backgroundColor: backgroundWhenChecked, - }} + className={`${combinedBoxStyles} p-[4px] ${disabled ? "" : "cursor-pointer"}`} > - {/* Simple check glyph */} + {/* Checkmark SVG per Figma - 16px size */}
@@ -146,13 +117,7 @@ export const Standard = {

Standard Mode

setUnchecked(newChecked)} - /> - setChecked(newChecked)} @@ -162,13 +127,11 @@ export const Standard = {
); }, - play: StandardInteraction.play, }; export const Inverse = { render: () => { - const [unchecked, setUnchecked] = React.useState(false); - const [checked, setChecked] = React.useState(true); + const [checked, setChecked] = React.useState(false); return (
@@ -176,13 +139,7 @@ export const Inverse = {

Inverse Mode

setUnchecked(newChecked)} - /> - setChecked(newChecked)} @@ -192,5 +149,60 @@ export const Inverse = {
); }, - play: InverseInteraction.play, +}; + +export const Disabled = { + args: { + checked: false, + mode: "standard", + state: "default", + disabled: true, + label: "Disabled checkbox", + }, + render: (args) => , +}; + +export const DisabledChecked = { + args: { + checked: true, + mode: "standard", + state: "default", + disabled: true, + label: "Disabled checked checkbox", + }, + render: (args) => , +}; + +// All modes comparison +export const AllModes = () => { + const [standardChecked, setStandardChecked] = React.useState(false); + const [inverseChecked, setInverseChecked] = React.useState(false); + + return ( +
+
+

Standard Mode

+
+ setStandardChecked(checked)} + /> +
+
+ +
+

Inverse Mode

+
+ setInverseChecked(checked)} + /> +
+
+
+ ); };