refactor(ui): migrar componente Switch a HeroUI

This commit is contained in:
Miguel Ortega committed 2026-09-14 21:29:57 -05:00
1 parent 2b53dd0a11
commit 64e54ea472
1 file changed
+101 -80
+101 -80
View File
@@ -1,5 +1,11 @@
"use client"; "use client";
import React from 'react';
import React from "react";
import {
Switch as HeroSwitch,
Label,
Description,
} from "@heroui/react";
export interface SwitchProps { export interface SwitchProps {
label?: React.ReactNode; label?: React.ReactNode;
@@ -7,7 +13,7 @@ export interface SwitchProps {
checked?: boolean; checked?: boolean;
onChange?: (checked: boolean) => void; onChange?: (checked: boolean) => void;
disabled?: boolean; disabled?: boolean;
size?: 'sm' | 'md' | 'lg'; size?: "sm" | "md" | "lg";
checkedIcon?: React.ReactNode; checkedIcon?: React.ReactNode;
uncheckedIcon?: React.ReactNode; uncheckedIcon?: React.ReactNode;
id?: string; id?: string;
@@ -16,21 +22,21 @@ export interface SwitchProps {
} }
const trackSizes = { const trackSizes = {
sm: 'w-8 h-4.5', sm: "w-8 h-[18px]",
md: 'w-11 h-6', md: "w-11 h-6",
lg: 'w-14 h-7.5', lg: "w-14 h-[30px]",
}; };
const thumbSizes = { const thumbSizes = {
sm: 'w-3.5 h-3.5', sm: "size-3.5",
md: 'w-5 h-5', md: "size-5",
lg: 'w-6.5 h-6.5', lg: "size-[26px]",
}; };
const translateSizes = { const translateSizes = {
sm: 'translate-x-3.5', sm: "translate-x-3.5",
md: 'translate-x-5', md: "translate-x-5",
lg: 'translate-x-6.5', lg: "translate-x-[26px]",
}; };
export const Switch: React.FC<SwitchProps> = ({ export const Switch: React.FC<SwitchProps> = ({
@@ -39,85 +45,100 @@ export const Switch: React.FC<SwitchProps> = ({
checked = false, checked = false,
onChange, onChange,
disabled = false, disabled = false,
size = 'md', size = "md",
checkedIcon, checkedIcon,
uncheckedIcon, uncheckedIcon,
id, id,
name, name,
className = '', className = "",
}) => { }) => {
const switchId = id || name || (typeof label === 'string' ? label.toLowerCase().replace(/\s+/g, '-') : undefined); const switchId =
id ||
const handleClick = () => { name ||
if (!disabled && onChange) { (typeof label === "string"
onChange(!checked); ? label.toLowerCase().replace(/\s+/g, "-")
} : undefined);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
handleClick();
}
};
return ( return (
<div className={`inline-flex items-start gap-3 select-none ${className}`}> <HeroSwitch
{/* Switch Button */} id={switchId}
<button name={name}
id={switchId} isSelected={checked}
type="button" isDisabled={disabled}
role="switch" size={size}
aria-checked={checked} className={className}
disabled={disabled} onChange={(e: any) => {
onClick={handleClick} if (!onChange || disabled) return;
onKeyDown={handleKeyDown} const val = typeof e === "boolean" ? e : Boolean(e?.target?.checked);
className={` onChange(val);
relative shrink-0 inline-flex items-center rounded-full p-0.5 cursor-pointer transition-colors duration-200 ease-in-out }}
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 >
focus-visible:ring-cyan-500 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950 <HeroSwitch.Content>
disabled:opacity-50 disabled:cursor-not-allowed <HeroSwitch.Control
${trackSizes[size]}
${
checked
? 'bg-cyan-500 dark:bg-cyan-500'
: 'bg-slate-300 dark:bg-slate-700 hover:bg-slate-400 dark:hover:bg-slate-600'
}
`}
>
{/* Thumb */}
<span
className={` className={`
pointer-events-none flex items-center justify-center rounded-full bg-white shadow-md transform transition-transform duration-200 ease-in-out text-slate-800 ${trackSizes[size]}
${thumbSizes[size]} relative shrink-0 inline-flex items-center rounded-full p-0.5
${checked ? translateSizes[size] : 'translate-x-0'} transition-colors duration-200 ease-in-out cursor-pointer
border-0 m-0 box-border outline-none
${
checked
? "bg-cyan-500"
: "bg-slate-300 hover:bg-slate-400 dark:bg-slate-700 dark:hover:bg-slate-600"
}
${disabled ? "cursor-not-allowed opacity-50" : ""}
`} `}
> >
{checked ? checkedIcon : uncheckedIcon} <HeroSwitch.Thumb
</span> className={`
</button> ${thumbSizes[size]}
${checked ? translateSizes[size] : "translate-x-0"}
{/* Label and description */} border-0 m-0 pointer-events-none shrink-0 flex items-center justify-center
{(label || description) && ( rounded-full bg-white text-slate-800 shadow-md
<div className="flex flex-col text-left cursor-pointer" onClick={handleClick}> transition-transform duration-200 ease-in-out
{label && ( `}
<span >
className={`text-xs sm:text-sm font-medium transition-colors ${ {(checkedIcon || uncheckedIcon) && (
disabled <HeroSwitch.Icon>
? 'text-slate-400 dark:text-slate-600' {checked ? checkedIcon : uncheckedIcon}
: 'text-slate-800 dark:text-slate-200 hover:text-slate-950 dark:hover:text-white' </HeroSwitch.Icon>
}`} )}
> </HeroSwitch.Thumb>
{label} </HeroSwitch.Control>
</span>
)} {/* TEXTOS */}
{description && ( {(label || description) && (
<span className="text-xs text-slate-500 dark:text-slate-400"> <div className="flex flex-col text-left">
{description} {label && (
</span> <Label
)} className={`
</div> text-xs sm:text-sm font-medium transition-colors cursor-pointer
)} ${
</div> disabled
? "text-slate-400 dark:text-slate-600 cursor-not-allowed"
: "text-slate-800 hover:text-slate-950 dark:text-slate-200 dark:hover:text-white"
}
`}
>
{label}
</Label>
)}
{description && (
<Description
className={`
text-xs text-slate-500 dark:text-slate-400 cursor-pointer
${disabled ? "cursor-not-allowed" : ""}
`}
>
{description}
</Description>
)}
</div>
)}
</HeroSwitch.Content>
</HeroSwitch>
); );
}; };