138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import React, {
|
|
ComponentProps,
|
|
DetailedHTMLProps,
|
|
HTMLAttributes,
|
|
ReactNode,
|
|
} from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import CheckMarkSVG from "../svgs/CheckMarkSVG";
|
|
import Stack from "../layout/Stack";
|
|
import Row from "../layout/Row";
|
|
import { Info } from "lucide-react";
|
|
import Span from "../layout/Span";
|
|
|
|
export type CheckboxProps = React.DetailedHTMLProps<
|
|
React.HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
> & {
|
|
wrapperProps?: DetailedHTMLProps<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
>;
|
|
label?: string | ReactNode;
|
|
labelProps?: React.DetailedHTMLProps<
|
|
React.HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
>;
|
|
defaultChecked?: boolean;
|
|
wrapperClassName?: string;
|
|
setChecked?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
checked?: boolean;
|
|
readOnly?: boolean;
|
|
size?: number;
|
|
changeHandler?: (value: boolean) => void;
|
|
info?: string | ReactNode;
|
|
wrapperWrapperProps?: ComponentProps<typeof Stack>;
|
|
};
|
|
|
|
/**
|
|
* # Checkbox Component
|
|
* @className twui-checkbox
|
|
* @className twui-checkbox-checked
|
|
* @className twui-checkbox-unchecked
|
|
*/
|
|
export default function Checkbox({
|
|
wrapperProps,
|
|
label,
|
|
labelProps,
|
|
size,
|
|
wrapperClassName,
|
|
defaultChecked,
|
|
setChecked: externalSetChecked,
|
|
readOnly,
|
|
checked: externalChecked,
|
|
changeHandler,
|
|
info,
|
|
wrapperWrapperProps,
|
|
...props
|
|
}: CheckboxProps) {
|
|
const finalSize = size || 20;
|
|
|
|
const [checked, setChecked] = React.useState(
|
|
defaultChecked || externalChecked || false
|
|
);
|
|
|
|
const finalTitle = props.title
|
|
? props.title
|
|
: `Checkbox-${Math.round(Math.random() * 100000)}`;
|
|
|
|
React.useEffect(() => {
|
|
if (typeof externalChecked == "undefined") return;
|
|
setChecked(externalChecked);
|
|
}, [externalChecked]);
|
|
|
|
React.useEffect(() => {
|
|
changeHandler?.(checked);
|
|
}, [checked]);
|
|
|
|
return (
|
|
<Stack
|
|
{...wrapperWrapperProps}
|
|
className={twMerge("gap-1.5", wrapperWrapperProps?.className)}
|
|
>
|
|
<div
|
|
{...wrapperProps}
|
|
className={twMerge(
|
|
"flex items-start md:items-center gap-2 flex-wrap md:flex-nowrap",
|
|
readOnly ? "opacity-70 pointer-events-none" : "",
|
|
wrapperClassName,
|
|
wrapperProps?.className
|
|
)}
|
|
onClick={() => {
|
|
setChecked(!checked);
|
|
externalSetChecked?.(!checked);
|
|
}}
|
|
>
|
|
<div
|
|
{...props}
|
|
className={twMerge(
|
|
"flex items-center justify-center p-[3px] rounded-default",
|
|
checked
|
|
? "bg-primary twui-checkbox-checked text-white outline-slate-400"
|
|
: "dark:outline-white/50 outline-2 -outline-offset-2 twui-checkbox-unchecked",
|
|
"twui-checkbox",
|
|
props.className
|
|
)}
|
|
style={{
|
|
minWidth: finalSize + "px",
|
|
width: finalSize + "px",
|
|
height: finalSize + "px",
|
|
...props.style,
|
|
}}
|
|
>
|
|
{checked && <CheckMarkSVG />}
|
|
</div>
|
|
<Stack className="gap-0.5">
|
|
<div
|
|
{...labelProps}
|
|
className={twMerge(
|
|
"select-none whitespace-normal md:whitespace-nowrap",
|
|
labelProps?.className
|
|
)}
|
|
>
|
|
{label || finalTitle}
|
|
</div>
|
|
</Stack>
|
|
</div>
|
|
{info && (
|
|
<Row className="gap-1" title={info.toString()}>
|
|
<Info size={12} className="opacity-40" />
|
|
<Span size="smaller" className="opacity-70">
|
|
{info}
|
|
</Span>
|
|
</Row>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|