You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

64 lines
1.9 KiB
TypeScript

import { type Component, type JSX, Show, splitProps } from 'solid-js';
/**
* Props for the reusable Button component.
*
* - `primary` (default): indigo background, white text.
* - `secondary`: white background, gray border, dark text.
* - `danger`: red background, white text.
*
* When `loading` is true, the button is disabled and a spinner replaces the icon.
*/
interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
loading?: boolean;
icon?: Component<{ class?: string }>;
}
const variantClasses: Record<string, string> = {
primary:
'text-white bg-indigo-600 hover:bg-indigo-700 border-transparent focus:ring-indigo-500',
secondary:
'text-gray-700 bg-white hover:bg-gray-50 border-gray-300 focus:ring-indigo-500',
danger:
'text-white bg-red-600 hover:bg-red-700 border-transparent focus:ring-red-500',
};
/** Styled button with variant theming, optional leading icon, and loading state. */
const Button: Component<ButtonProps> = (allProps) => {
const [props, rest] = splitProps(allProps, [
'variant',
'loading',
'icon',
'children',
'class',
]);
const variant = () => props.variant ?? 'primary';
const renderIcon = () => {
if (props.loading) {
return (
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2" />
);
}
if (props.icon) {
const Icon = props.icon;
return <Icon class="-ml-1 mr-2 h-5 w-5" />;
}
return null;
};
return (
<button
{...rest}
disabled={rest.disabled || props.loading}
class={`inline-flex items-center justify-center px-4 py-2 border shadow-sm text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed ${variantClasses[variant()]} ${props.class ?? ''}`}
>
{renderIcon()}
{props.children}
</button>
);
};
export default Button;