refactor(ui): migrar componente Checkbox a HeroUI
This commit is contained in:
1 parent
e399f22007
commit
952f30ea19
2 files changed
+118
-82
No files matched your search
@@ -163,7 +163,7 @@ export default function ComponentsContent() {
|
|||||||
<Badge variant="danger" icon={<FiAlertCircle />}>
|
<Badge variant="danger" icon={<FiAlertCircle />}>
|
||||||
Con ícono
|
Con ícono
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="primary" removable onRemove={() => {}}>
|
<Badge variant="primary" removable onRemove={() => { }}>
|
||||||
Removable
|
Removable
|
||||||
</Badge>
|
</Badge>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -279,15 +279,15 @@ export default function ComponentsContent() {
|
|||||||
<Row>
|
<Row>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Recordar sesión"
|
label="Recordar sesión"
|
||||||
checked={checked}
|
isSelected={checked}
|
||||||
onChange={(e) => setChecked(e.target.checked)}
|
onChange={(isSelected) => setChecked(isSelected)}
|
||||||
/>
|
/>
|
||||||
<Checkbox label="Deshabilitado" disabled />
|
<Checkbox label="Deshabilitado" disabled />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Marcado fijo"
|
label="Marcado fijo"
|
||||||
checked
|
checked
|
||||||
disabled
|
disabled
|
||||||
onChange={() => {}}
|
onChange={() => { }}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -302,8 +302,8 @@ export default function ComponentsContent() {
|
|||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={`<Checkbox
|
code={`<Checkbox
|
||||||
label="Recordar sesión"
|
label="Recordar sesión"
|
||||||
checked={checked}
|
isSelected={checked}
|
||||||
onChange={e => setChecked(e.target.checked)}
|
onChange={(isSelected) => setChecked(isSelected)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
+112
-76
@@ -1,112 +1,148 @@
|
|||||||
"use client";
|
"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;
|
label?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
checked?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
export const Checkbox = ({
|
||||||
(
|
|
||||||
{
|
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
indeterminate = false,
|
indeterminate = false,
|
||||||
checked,
|
checked,
|
||||||
disabled,
|
isSelected,
|
||||||
className = '',
|
disabled = false,
|
||||||
|
className = "",
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
onChange,
|
||||||
...props
|
...props
|
||||||
},
|
}: CheckboxProps) => {
|
||||||
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);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (resolvedRef.current) {
|
|
||||||
resolvedRef.current.indeterminate = indeterminate;
|
|
||||||
}
|
|
||||||
}, [indeterminate, resolvedRef]);
|
|
||||||
|
|
||||||
const isChecked = Boolean(checked);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col space-y-1 ${className}`}>
|
<HeroUICheckbox
|
||||||
<label
|
|
||||||
htmlFor={checkboxId}
|
|
||||||
className={`inline-flex items-start gap-2.5 select-none cursor-pointer group ${
|
|
||||||
disabled ? 'opacity-50 !cursor-not-allowed' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* 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}
|
{...props}
|
||||||
/>
|
id={id}
|
||||||
|
name={name}
|
||||||
<div
|
isSelected={checked}
|
||||||
className={`
|
isDisabled={disabled}
|
||||||
w-4 h-4 rounded-md flex items-center justify-center transition-all duration-200
|
isIndeterminate={indeterminate}
|
||||||
border
|
isInvalid={Boolean(errorMessage)}
|
||||||
peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2
|
onChange={onChange}
|
||||||
peer-focus-visible:ring-cyan-500 peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-offset-slate-950
|
className={className}
|
||||||
${
|
|
||||||
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 ? (
|
<HeroUICheckbox.Content className="items-start gap-2.5">
|
||||||
<FiMinus className="text-xs stroke-3" />
|
<HeroUICheckbox.Control
|
||||||
) : isChecked ? (
|
className={[
|
||||||
<FiCheck className="text-xs stroke-3" />
|
"mt-0.5 shrink-0 size-4 rounded-md",
|
||||||
) : null}
|
"border",
|
||||||
</div>
|
"transition-all duration-200",
|
||||||
</div>
|
|
||||||
|
// Estado normal
|
||||||
|
"bg-white dark:bg-slate-900",
|
||||||
|
"border-slate-300 dark:border-slate-700",
|
||||||
|
|
||||||
|
// Hover
|
||||||
|
"group-hover:border-slate-400",
|
||||||
|
"dark:group-hover:border-slate-500",
|
||||||
|
|
||||||
|
// 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(" ")}
|
||||||
|
>
|
||||||
|
<HeroUICheckbox.Indicator>
|
||||||
|
{({ isSelected, isIndeterminate }) => {
|
||||||
|
if (isIndeterminate) {
|
||||||
|
return <FiMinus className="text-xs stroke-3" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
return <FiCheck className="text-xs stroke-3" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</HeroUICheckbox.Indicator>
|
||||||
|
</HeroUICheckbox.Control>
|
||||||
|
|
||||||
{/* Label and Sub-description */}
|
|
||||||
{(label || description) && (
|
{(label || description) && (
|
||||||
<div className="flex flex-col text-left">
|
<div className="flex flex-col text-left">
|
||||||
{label && (
|
{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">
|
<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}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{description && (
|
{description && (
|
||||||
<span className="text-xs text-slate-500 dark:text-slate-400">
|
<Description
|
||||||
|
className="
|
||||||
|
text-xs
|
||||||
|
text-slate-500 dark:text-slate-400
|
||||||
|
"
|
||||||
|
>
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</Description>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</label>
|
</HeroUICheckbox.Content>
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<p className="text-xs text-rose-600 dark:text-rose-400 pl-6.5">{errorMessage}</p>
|
<FieldError className="pl-6.5 text-xs">
|
||||||
|
{errorMessage}
|
||||||
|
</FieldError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroUICheckbox>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
Checkbox.displayName = 'Checkbox';
|
Checkbox.displayName = "Checkbox";
|
||||||
Reference in new issue
Block a user