Finish migrating components
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { useComponentId } from "../../hooks";
|
||||
import { CheckboxView } from "./Checkbox.view";
|
||||
import type { CheckboxProps } from "./Checkbox.types";
|
||||
|
||||
const CheckboxContainer = memo<CheckboxProps>(
|
||||
({
|
||||
checked = false,
|
||||
mode = "standard",
|
||||
state = "default",
|
||||
disabled = false,
|
||||
label,
|
||||
className = "",
|
||||
onChange,
|
||||
id,
|
||||
name,
|
||||
value,
|
||||
ariaLabel,
|
||||
...props
|
||||
}) => {
|
||||
const isInverse = mode === "inverse";
|
||||
|
||||
// Base tokens (rough placeholders leveraging existing CSS variables)
|
||||
const colorContent = isInverse
|
||||
? "var(--color-content-inverse-primary)"
|
||||
: "var(--color-content-default-primary)";
|
||||
|
||||
// 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`;
|
||||
|
||||
const stateStyles: Record<string, string> = {
|
||||
default: "",
|
||||
hover: "",
|
||||
focus: "",
|
||||
};
|
||||
|
||||
// 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 checkGlyphColor = checked
|
||||
? isInverse
|
||||
? "var(--color-content-inverse-primary)"
|
||||
: "var(--color-border-default-brand-primary)"
|
||||
: "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)]";
|
||||
|
||||
const handleToggle = (e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
if (disabled) return;
|
||||
onChange?.({
|
||||
checked: !checked,
|
||||
value,
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
// Generate unique ID for accessibility if not provided
|
||||
const { id: checkboxId, labelId } = useComponentId("checkbox", id);
|
||||
|
||||
const accessibilityProps = {
|
||||
role: "checkbox" as const,
|
||||
"aria-checked": checked,
|
||||
...(disabled && { "aria-disabled": true, tabIndex: -1 }),
|
||||
...(!disabled && { tabIndex: 0 }),
|
||||
...(ariaLabel && { "aria-label": ariaLabel }),
|
||||
...(label && !ariaLabel && { "aria-labelledby": labelId }),
|
||||
id: checkboxId,
|
||||
...props,
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||
if (e.key === " " || e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleToggle(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CheckboxView
|
||||
checkboxId={checkboxId}
|
||||
labelId={labelId}
|
||||
checked={checked}
|
||||
mode={mode}
|
||||
state={state}
|
||||
disabled={disabled}
|
||||
label={label}
|
||||
name={name}
|
||||
value={value}
|
||||
ariaLabel={ariaLabel}
|
||||
className={className}
|
||||
combinedBoxStyles={combinedBoxStyles}
|
||||
defaultOutlineClass={defaultOutlineClass}
|
||||
conditionalHoverOutlineClass={conditionalHoverOutlineClass}
|
||||
conditionalFocusClass={conditionalFocusClass}
|
||||
backgroundWhenChecked={backgroundWhenChecked}
|
||||
checkGlyphColor={checkGlyphColor}
|
||||
labelColor={labelColor}
|
||||
accessibilityProps={accessibilityProps}
|
||||
onToggle={handleToggle}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CheckboxContainer.displayName = "Checkbox";
|
||||
|
||||
export default CheckboxContainer;
|
||||
@@ -0,0 +1,41 @@
|
||||
export interface CheckboxProps {
|
||||
checked?: boolean;
|
||||
mode?: "standard" | "inverse";
|
||||
state?: "default" | "hover" | "focus";
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
className?: string;
|
||||
onChange?: (_data: {
|
||||
checked: boolean;
|
||||
value?: string;
|
||||
event: React.MouseEvent | React.KeyboardEvent;
|
||||
}) => void;
|
||||
id?: string;
|
||||
name?: string;
|
||||
value?: string;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export interface CheckboxViewProps {
|
||||
checkboxId: string;
|
||||
labelId: string;
|
||||
checked: boolean;
|
||||
mode: "standard" | "inverse";
|
||||
state: "default" | "hover" | "focus";
|
||||
disabled: boolean;
|
||||
label?: string;
|
||||
name?: string;
|
||||
value?: string;
|
||||
ariaLabel?: string;
|
||||
className: string;
|
||||
combinedBoxStyles: string;
|
||||
defaultOutlineClass: string;
|
||||
conditionalHoverOutlineClass: string;
|
||||
conditionalFocusClass: string;
|
||||
backgroundWhenChecked: string;
|
||||
checkGlyphColor: string;
|
||||
labelColor: string;
|
||||
accessibilityProps: React.HTMLAttributes<HTMLSpanElement>;
|
||||
onToggle: (e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLSpanElement>) => void;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { CheckboxViewProps } from "./Checkbox.types";
|
||||
|
||||
export function CheckboxView({
|
||||
checkboxId,
|
||||
labelId,
|
||||
checked,
|
||||
disabled,
|
||||
label,
|
||||
name,
|
||||
value,
|
||||
ariaLabel,
|
||||
className,
|
||||
combinedBoxStyles,
|
||||
defaultOutlineClass,
|
||||
conditionalHoverOutlineClass,
|
||||
conditionalFocusClass,
|
||||
backgroundWhenChecked,
|
||||
checkGlyphColor,
|
||||
labelColor,
|
||||
accessibilityProps,
|
||||
onToggle,
|
||||
onKeyDown,
|
||||
}: CheckboxViewProps) {
|
||||
return (
|
||||
<label
|
||||
className={`inline-flex items-center gap-[8px] cursor-pointer select-none ${
|
||||
disabled ? "opacity-60 cursor-not-allowed" : ""
|
||||
} ${className}`}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
<span
|
||||
{...accessibilityProps}
|
||||
onClick={onToggle}
|
||||
onKeyDown={onKeyDown}
|
||||
className={`${combinedBoxStyles} ${defaultOutlineClass} ${conditionalHoverOutlineClass} ${conditionalFocusClass} p-[var(--measures-spacing-004)]`}
|
||||
style={{
|
||||
backgroundColor: backgroundWhenChecked,
|
||||
}}
|
||||
>
|
||||
{/* Simple check glyph */}
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 12 12"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<polyline
|
||||
points="2.5 6 5 8.5 10 3.5"
|
||||
stroke={checkGlyphColor}
|
||||
strokeWidth="1.25"
|
||||
fill="none"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="miter"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
{label && (
|
||||
<span
|
||||
id={labelId}
|
||||
className="font-inter text-[14px] leading-[18px]"
|
||||
style={{ color: labelColor }}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
{/* Hidden native input for form compatibility (optional for now) */}
|
||||
<input
|
||||
type="checkbox"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
tabIndex={-1}
|
||||
aria-hidden="true"
|
||||
className="sr-only"
|
||||
readOnly
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Checkbox.container";
|
||||
export type { CheckboxProps } from "./Checkbox.types";
|
||||
Reference in New Issue
Block a user