chore(release): snapshot 1.4.2

This commit is contained in:
Erik Silva
2025-12-17 13:36:23 -03:00
parent 2a112f169d
commit 99d828869a
95 changed files with 9933 additions and 1601 deletions

View File

@@ -1,13 +1,13 @@
"use client";
import { ButtonHTMLAttributes, forwardRef } from "react";
import { ButtonHTMLAttributes, forwardRef, ReactNode } from "react";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline" | "ghost";
size?: "sm" | "md" | "lg";
isLoading?: boolean;
leftIcon?: string;
rightIcon?: string;
leftIcon?: string | ReactNode;
rightIcon?: string | ReactNode;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
@@ -55,11 +55,19 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<i className="ri-loader-4-line animate-spin mr-2 text-[20px]" />
)}
{!isLoading && leftIcon && (
<i className={`${leftIcon} mr-2 text-[20px]`} />
typeof leftIcon === 'string' ? (
<i className={`${leftIcon} mr-2 text-[20px]`} />
) : (
<div className="w-5 h-5 mr-2">{leftIcon}</div>
)
)}
{children}
{!isLoading && rightIcon && (
<i className={`${rightIcon} ml-2 text-[20px]`} />
typeof rightIcon === 'string' ? (
<i className={`${rightIcon} ml-2 text-[20px]`} />
) : (
<div className="w-5 h-5 ml-2">{rightIcon}</div>
)
)}
</button>
);

View File

@@ -1,13 +1,14 @@
"use client";
import { InputHTMLAttributes, forwardRef, useState, ReactNode } from "react";
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: ReactNode;
leftIcon?: string;
rightIcon?: string;
leftIcon?: string | ReactNode;
rightIcon?: string | ReactNode;
onRightIconClick?: () => void;
}
@@ -41,9 +42,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
)}
<div className="relative">
{leftIcon && (
<i
className={`${leftIcon} absolute left-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] dark:text-gray-400 text-[20px]`}
/>
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-zinc-400 dark:text-gray-400">
{typeof leftIcon === 'string' ? (
<i className={`${leftIcon} text-[20px]`} />
) : (
<div className="w-5 h-5">{leftIcon}</div>
)}
</div>
)}
<input
ref={ref}
@@ -69,21 +74,23 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 dark:text-gray-400 dark:hover:text-white transition-colors cursor-pointer"
>
<i
className={`${showPassword ? "ri-eye-off-line" : "ri-eye-line"} text-[20px]`}
/>
{showPassword ? (
<EyeSlashIcon className="w-5 h-5" />
) : (
<EyeIcon className="w-5 h-5" />
)}
</button>
)}
{!isPassword && rightIcon && (
<button
type="button"
onClick={onRightIconClick}
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
>
<i className={`${rightIcon} text-[20px]`} />
</button>
<div className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-400 dark:text-gray-400">
{typeof rightIcon === 'string' ? (
<i className={`${rightIcon} text-[20px]`} />
) : (
<div className="w-5 h-5">{rightIcon}</div>
)}
</div>
)}
</div>
{error && (

View File

@@ -1,6 +1,6 @@
"use client";
import { SelectHTMLAttributes, forwardRef, useState, useRef, useEffect } from "react";
import { SelectHTMLAttributes, forwardRef, useState, useRef, useEffect, ReactNode } from "react";
interface SelectOption {
value: string;
@@ -11,7 +11,7 @@ interface SearchableSelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElem
label?: string;
error?: string;
helperText?: string;
leftIcon?: string;
leftIcon?: string | ReactNode;
options: SelectOption[];
placeholder?: string;
onChange?: (value: string) => void;
@@ -115,9 +115,13 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
<div ref={containerRef} className="relative">
{leftIcon && (
<i
className={`${leftIcon} absolute left-3.5 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-400 text-[20px] pointer-events-none z-10`}
/>
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-zinc-400 dark:text-gray-400 pointer-events-none z-10">
{typeof leftIcon === 'string' ? (
<i className={`${leftIcon} text-[20px]`} />
) : (
<div className="w-5 h-5">{leftIcon}</div>
)}
</div>
)}
{/* Custom trigger */}

View File

@@ -0,0 +1,102 @@
'use client';
import { Tab } from '@headlessui/react';
import { ReactNode } from 'react';
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}
export interface TabItem {
name: string;
icon?: React.ComponentType<{ className?: string }>;
content: ReactNode;
}
interface TabsProps {
tabs: TabItem[];
defaultIndex?: number;
onChange?: (index: number) => void;
variant?: 'card' | 'modal'; // Novo: variante para diferentes contextos
}
export default function Tabs({ tabs, defaultIndex = 0, onChange, variant = 'card' }: TabsProps) {
if (variant === 'modal') {
// Versão para modais - sem card wrapper
return (
<Tab.Group defaultIndex={defaultIndex} onChange={onChange}>
<Tab.List className="flex border-b border-zinc-200 dark:border-zinc-700 px-6">
{tabs.map((tab) => (
<Tab
key={tab.name}
className={({ selected }) =>
classNames(
'px-4 py-3 text-sm font-medium border-b-2 transition-colors focus:outline-none whitespace-nowrap',
selected
? 'border-[var(--brand-color)] text-[var(--brand-color)]'
: 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
)
}
>
<div className="flex items-center justify-center gap-2">
{tab.icon && <tab.icon className="w-4 h-4 flex-shrink-0" />}
{tab.name}
</div>
</Tab>
))}
</Tab.List>
<Tab.Panels className="p-6">
{tabs.map((tab, idx) => (
<Tab.Panel
key={idx}
className="focus:outline-none"
>
{tab.content}
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
);
}
// Versão padrão para páginas - com card wrapper
return (
<Tab.Group defaultIndex={defaultIndex} onChange={onChange}>
<div className="bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden">
<Tab.List className="flex space-x-1 rounded-t-xl bg-zinc-100 dark:bg-zinc-800/50 p-1 overflow-x-auto scrollbar-hide">
{tabs.map((tab) => (
<Tab
key={tab.name}
className={({ selected }) =>
classNames(
'w-full rounded-lg py-2 sm:py-2.5 px-3 sm:px-4 text-xs sm:text-sm font-medium leading-5 transition-all duration-200 whitespace-nowrap',
'focus:outline-none',
selected
? 'bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white shadow'
: 'text-zinc-500 hover:bg-white/[0.12] hover:text-zinc-700 dark:hover:text-zinc-300'
)
}
>
<div className="flex items-center justify-center gap-1.5 sm:gap-2">
{tab.icon && <tab.icon className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />}
{tab.name}
</div>
</Tab>
))}
</Tab.List>
<Tab.Panels className="p-4 sm:p-6">
{tabs.map((tab, idx) => (
<Tab.Panel
key={idx}
className="focus:outline-none"
>
{tab.content}
</Tab.Panel>
))}
</Tab.Panels>
</div>
</Tab.Group>
);
}