refactor(ui): migrar componente Checkbox a HeroUI
This commit is contained in:
1 parent
e399f22007
commit
952f30ea19
2 files changed
+138
-102
No files matched your search
+132
-96
@@ -1,112 +1,148 @@
|
||||
"use client";
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { FiCheck, FiMinus } from 'react-icons/fi';
|
||||
|
||||
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
||||
import React from "react";
|
||||
import {
|
||||
Checkbox as HeroUICheckbox,
|
||||
Description,
|
||||
FieldError,
|
||||
} from "@heroui/react";
|
||||
|
||||
import { FiCheck, FiMinus } from "react-icons/fi";
|
||||
|
||||
export interface CheckboxProps
|
||||
extends Omit<
|
||||
React.ComponentProps<typeof HeroUICheckbox>,
|
||||
"children" | "defaultSelected" | "isDisabled" | "isInvalid" | "isIndeterminate"
|
||||
> {
|
||||
label?: React.ReactNode;
|
||||
description?: string;
|
||||
errorMessage?: string;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
indeterminate?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
(
|
||||
{
|
||||
label,
|
||||
description,
|
||||
errorMessage,
|
||||
indeterminate = false,
|
||||
checked,
|
||||
disabled,
|
||||
className = '',
|
||||
id,
|
||||
name,
|
||||
...props
|
||||
},
|
||||
forwardedRef
|
||||
) => {
|
||||
const internalRef = useRef<HTMLInputElement>(null);
|
||||
const resolvedRef = (forwardedRef as React.RefObject<HTMLInputElement>) || internalRef;
|
||||
const checkboxId = id || name || (typeof label === 'string' ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
|
||||
export const Checkbox = ({
|
||||
label,
|
||||
description,
|
||||
errorMessage,
|
||||
indeterminate = false,
|
||||
checked,
|
||||
isSelected,
|
||||
disabled = false,
|
||||
className = "",
|
||||
id,
|
||||
name,
|
||||
onChange,
|
||||
...props
|
||||
}: CheckboxProps) => {
|
||||
return (
|
||||
<HeroUICheckbox
|
||||
{...props}
|
||||
id={id}
|
||||
name={name}
|
||||
isSelected={checked}
|
||||
isDisabled={disabled}
|
||||
isIndeterminate={indeterminate}
|
||||
isInvalid={Boolean(errorMessage)}
|
||||
onChange={onChange}
|
||||
className={className}
|
||||
>
|
||||
<HeroUICheckbox.Content className="items-start gap-2.5">
|
||||
<HeroUICheckbox.Control
|
||||
className={[
|
||||
"mt-0.5 shrink-0 size-4 rounded-md",
|
||||
"border",
|
||||
"transition-all duration-200",
|
||||
|
||||
useEffect(() => {
|
||||
if (resolvedRef.current) {
|
||||
resolvedRef.current.indeterminate = indeterminate;
|
||||
}
|
||||
}, [indeterminate, resolvedRef]);
|
||||
// Estado normal
|
||||
"bg-white dark:bg-slate-900",
|
||||
"border-slate-300 dark:border-slate-700",
|
||||
|
||||
const isChecked = Boolean(checked);
|
||||
// Hover
|
||||
"group-hover:border-slate-400",
|
||||
"dark:group-hover:border-slate-500",
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col space-y-1 ${className}`}>
|
||||
<label
|
||||
htmlFor={checkboxId}
|
||||
className={`inline-flex items-start gap-2.5 select-none cursor-pointer group ${
|
||||
disabled ? 'opacity-50 !cursor-not-allowed' : ''
|
||||
}`}
|
||||
// Focus
|
||||
"data-[focus-visible=true]:ring-2",
|
||||
"data-[focus-visible=true]:ring-cyan-500",
|
||||
"data-[focus-visible=true]:ring-offset-2",
|
||||
"data-[focus-visible=true]:ring-offset-white",
|
||||
"dark:data-[focus-visible=true]:ring-offset-slate-950",
|
||||
|
||||
// Seleccionado
|
||||
"data-[selected=true]:bg-cyan-500",
|
||||
"data-[selected=true]:border-cyan-500",
|
||||
"data-[selected=true]:text-slate-950",
|
||||
"data-[selected=true]:shadow-sm",
|
||||
"data-[selected=true]:shadow-cyan-500/20",
|
||||
|
||||
// Indeterminado
|
||||
"data-[indeterminate=true]:bg-cyan-500",
|
||||
"data-[indeterminate=true]:border-cyan-500",
|
||||
"data-[indeterminate=true]:text-slate-950",
|
||||
|
||||
// Error
|
||||
errorMessage
|
||||
? "data-[invalid=true]:border-rose-500"
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
{/* Custom Styled Box */}
|
||||
<div className="relative flex items-center justify-center mt-0.5 shrink-0">
|
||||
<input
|
||||
ref={resolvedRef}
|
||||
id={checkboxId}
|
||||
name={name}
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
className="peer sr-only"
|
||||
{...props}
|
||||
/>
|
||||
<HeroUICheckbox.Indicator>
|
||||
{({ isSelected, isIndeterminate }) => {
|
||||
if (isIndeterminate) {
|
||||
return <FiMinus className="text-xs stroke-3" />;
|
||||
}
|
||||
|
||||
<div
|
||||
className={`
|
||||
w-4 h-4 rounded-md flex items-center justify-center transition-all duration-200
|
||||
border
|
||||
peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2
|
||||
peer-focus-visible:ring-cyan-500 peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-offset-slate-950
|
||||
${
|
||||
isChecked || indeterminate
|
||||
? 'bg-cyan-500 border-cyan-500 text-slate-950 shadow-sm shadow-cyan-500/20'
|
||||
: 'bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-700 text-transparent group-hover:border-slate-400 dark:group-hover:border-slate-500'
|
||||
}
|
||||
${
|
||||
errorMessage
|
||||
? '!border-rose-500 !bg-rose-50 dark:!bg-rose-950/30'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
{indeterminate ? (
|
||||
<FiMinus className="text-xs stroke-3" />
|
||||
) : isChecked ? (
|
||||
<FiCheck className="text-xs stroke-3" />
|
||||
) : null}
|
||||
</div>
|
||||
if (isSelected) {
|
||||
return <FiCheck className="text-xs stroke-3" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
</HeroUICheckbox.Indicator>
|
||||
</HeroUICheckbox.Control>
|
||||
|
||||
{(label || description) && (
|
||||
<div className="flex flex-col text-left">
|
||||
{label && (
|
||||
<span
|
||||
className="
|
||||
text-xs sm:text-sm font-medium
|
||||
text-slate-800 dark:text-slate-200
|
||||
group-hover:text-slate-950
|
||||
dark:group-hover:text-white
|
||||
transition-colors
|
||||
"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{description && (
|
||||
<Description
|
||||
className="
|
||||
text-xs
|
||||
text-slate-500 dark:text-slate-400
|
||||
"
|
||||
>
|
||||
{description}
|
||||
</Description>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Label and Sub-description */}
|
||||
{(label || description) && (
|
||||
<div className="flex flex-col text-left">
|
||||
{label && (
|
||||
<span className="text-xs sm:text-sm font-medium text-slate-800 dark:text-slate-200 group-hover:text-slate-950 dark:group-hover:text-white transition-colors">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
{description && (
|
||||
<span className="text-xs text-slate-500 dark:text-slate-400">
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
|
||||
{errorMessage && (
|
||||
<p className="text-xs text-rose-600 dark:text-rose-400 pl-6.5">{errorMessage}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</HeroUICheckbox.Content>
|
||||
|
||||
Checkbox.displayName = 'Checkbox';
|
||||
{errorMessage && (
|
||||
<FieldError className="pl-6.5 text-xs">
|
||||
{errorMessage}
|
||||
</FieldError>
|
||||
)}
|
||||
</HeroUICheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
Checkbox.displayName = "Checkbox";
|
||||
Reference in new issue
Block a user