Start organizational migration

This commit is contained in:
adilallo
2026-02-05 18:21:56 -07:00
parent 69074b23f3
commit db3c0274f6
161 changed files with 145 additions and 145 deletions
@@ -0,0 +1,66 @@
"use client";
import { memo, useCallback, useId, useState } from "react";
import { CheckboxGroupView } from "./CheckboxGroup.view";
import type { CheckboxGroupProps } from "./CheckboxGroup.types";
import { normalizeMode } from "../../../../lib/propNormalization";
const CheckboxGroupContainer = ({
name,
value,
onChange,
mode: modeProp = "standard",
disabled = false,
options = [],
className = "",
...props
}: CheckboxGroupProps) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
const mode = normalizeMode(modeProp);
// Generate unique ID for accessibility if not provided
const generatedId = useId();
const groupId = name || `checkbox-group-${generatedId}`;
// Internal state to track checked values (only used if value prop is not provided)
const [internalCheckedValues, setInternalCheckedValues] = useState<string[]>([]);
// Use controlled value if provided, otherwise use internal state
const checkedValues = value !== undefined ? value : internalCheckedValues;
const handleOptionChange = useCallback(
(optionValue: string, checked: boolean) => {
if (disabled) return;
const newCheckedValues = checked
? [...checkedValues, optionValue]
: checkedValues.filter((v) => v !== optionValue);
// Only update internal state if uncontrolled
if (value === undefined) {
setInternalCheckedValues(newCheckedValues);
}
if (onChange) {
onChange({ value: newCheckedValues });
}
},
[disabled, checkedValues, onChange, value],
);
return (
<CheckboxGroupView
groupId={groupId}
value={checkedValues}
mode={mode}
disabled={disabled}
options={options}
className={className}
ariaLabel={props["aria-label"]}
onOptionChange={handleOptionChange}
/>
);
};
CheckboxGroupContainer.displayName = "CheckboxGroup";
export default memo(CheckboxGroupContainer);
@@ -0,0 +1,34 @@
export interface CheckboxOption {
value: string;
label: string;
subtext?: string;
ariaLabel?: string;
}
import type { ModeValue } from "../../../../lib/propNormalization";
export interface CheckboxGroupProps {
name?: string;
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?: ModeValue;
disabled?: boolean;
options?: CheckboxOption[];
className?: string;
"aria-label"?: string;
}
export interface CheckboxGroupViewProps {
groupId: string;
value: string[];
mode: "standard" | "inverse";
disabled: boolean;
options: CheckboxOption[];
className: string;
ariaLabel?: string;
onOptionChange: (_optionValue: string, _checked: boolean) => void;
}
@@ -0,0 +1,84 @@
import Checkbox from "../Checkbox";
import type { CheckboxGroupViewProps } from "./CheckboxGroup.types";
export function CheckboxGroupView({
groupId,
value,
mode,
disabled,
options,
className,
ariaLabel,
onOptionChange,
}: CheckboxGroupViewProps) {
return (
<div
className={`space-y-[8px] ${className}`}
role="group"
aria-label={ariaLabel}
>
{options.map((option) => {
const isChecked = value.includes(option.value);
// If there's subtext, render checkbox without label and handle layout separately
if (option.subtext) {
return (
<div
key={option.value}
className="flex gap-[8px] items-start"
>
<Checkbox
checked={isChecked}
mode={mode}
disabled={disabled}
name={groupId}
value={option.value}
ariaLabel={option.ariaLabel || option.label}
onChange={({ checked }) => {
onOptionChange(option.value, checked);
}}
/>
<div className="flex flex-col gap-[4px] flex-1">
<span
className={`font-inter text-[14px] leading-[20px] ${
mode === "inverse"
? "text-[var(--color-content-inverse-primary)]"
: "text-[var(--color-content-default-primary)]"
}`}
>
{option.label}
</span>
<span
className={`font-inter text-[14px] leading-[20px] ${
mode === "inverse"
? "text-[var(--color-content-inverse-secondary,#1f1f1f)]"
: "text-[var(--color-content-default-tertiary,#b4b4b4)]"
}`}
>
{option.subtext}
</span>
</div>
</div>
);
}
// If no subtext, use Checkbox's built-in label
return (
<Checkbox
key={option.value}
checked={isChecked}
mode={mode}
disabled={disabled}
label={option.label}
name={groupId}
value={option.value}
ariaLabel={option.ariaLabel}
onChange={({ checked }) => {
onOptionChange(option.value, checked);
}}
/>
);
})}
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./CheckboxGroup.container";