import type { HTMLAttributes, ReactElement, ReactNode } from "react";
import { Children, cloneElement, createContext, isValidElement, useContext, useState } from "react";
import { cx } from "../../utils/cx.utils";
import { Icon } from "../icons/icon";
import "./todo.component.css";
/** Identifier reported for a {@link Todo.Item}: its `id` prop, or its index among siblings when `id` is omitted. */
export type TodoItemIdentifier = string | number;
interface TodoContextValue {
onItemChange: (identifier: TodoItemIdentifier, checked: boolean) => void;
}
const TodoContext = createContext(null);
/** Props accepted by {@link Todo.Item}. */
export interface TodoItemProps extends Omit, "id" | "onClick"> {
/** Unique identifier for this item, reported to {@link TodoProps.onChange}. Defaults to the item's index among siblings when omitted. */
id?: string;
/** Whether the item is checked when it first renders. Defaults to `false`. */
initialState?: boolean;
/** Text describing the task. */
children: ReactNode;
}
interface TodoItemInternalProps extends TodoItemProps {
index?: number;
}
/**
* Single todo entry rendered on its own line with a round checkbox in front.
* Clicking it toggles between the empty and checked state, reporting the
* new state to the parent {@link Todo} via context.
*
* @example
* ```tsx
* Water the plants
* ```
*/
function TodoItem({
className,
id,
index = 0,
initialState = false,
children,
...rest
}: TodoItemInternalProps) {
const [checked, setChecked] = useState(initialState);
const context = useContext(TodoContext);
return (
);
}
/** Props accepted by {@link Todo}. */
export interface TodoProps extends Omit, "title" | "onChange"> {
/** Optional title rendered above the list of items. */
title?: ReactNode;
/** Called whenever an item is toggled, with its identifier ({@link TodoItemProps.id} or index) and new checked state. */
onChange?: (identifier: TodoItemIdentifier, checked: boolean) => void;
}
/**
* A list of checkable tasks. Composes {@link Todo.Item} for each entry; each
* item renders on its own line with a round checkbox that fills in with a
* check icon once marked done. Items report their state changes up to
* {@link TodoProps.onChange} via context.
*
* @example
* ```tsx
* console.log(id, checked)}>
* Water the plants
* Feed the cat
*
* ```
*/
export function Todo({ className, title, children, onChange, ...rest }: TodoProps) {
let index = 0;
const items = Children.map(children, (child) => {
if (!isValidElement(child) || child.type !== TodoItem) {
return child;
}
return cloneElement(child as ReactElement, { index: index++ });
});
return (
onChange?.(identifier, checked) }}
>