UNPKG

tuix

Version:

A performant TUI framework for Bun with JSX and reactive state management

300 lines (260 loc) 7.56 kB
/** * Button Builder Functions * * Simplified API for creating Button components */ import { Colors, style } from "../../styling/index" import { Borders } from "../../styling/borders" import { styledBox } from "../../layout/box" import { text, styledText, hstack } from "../../core/view" import type { View } from "../../core/types" import type { BorderStyle } from "../../styling/types" import type { StyleProps } from "../../styling/types" export interface ButtonOptions { variant?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'ghost' size?: 'small' | 'medium' | 'large' disabled?: boolean border?: BorderStyle icon?: string iconPosition?: 'left' | 'right' fullWidth?: boolean loading?: boolean onClick?: () => void className?: string } /** * Create a button with text */ export function Button(label: string, options: ButtonOptions = {}): View { const { variant = 'primary', size = 'medium', disabled = false, border = Borders.Rounded, icon, iconPosition = 'left', fullWidth = false, loading = false, onClick, className } = options // Get variant styles const variantStyles = getVariantStyles(variant, disabled) // Get size styles const sizeStyles = getSizeStyles(size) // Build button content let buttonContent: View if (loading) { buttonContent = styledText("⟳ Loading...", variantStyles.textStyle) } else if (icon) { const iconView = styledText(icon, variantStyles.textStyle) const textView = styledText(label, variantStyles.textStyle) buttonContent = iconPosition === 'left' ? hstack(iconView, text(" "), textView) : hstack(textView, text(" "), iconView) } else { buttonContent = styledText(label, variantStyles.textStyle) } // Apply button styling return styledBox(buttonContent, { border, padding: sizeStyles.padding, style: variantStyles.containerStyle, className, onClick: disabled ? undefined : onClick }) } /** * Create a primary button (default styling) */ export function PrimaryButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'primary', onClick }) } /** * Create a secondary button */ export function SecondaryButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'secondary', onClick }) } /** * Create a success button (green) */ export function SuccessButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'success', onClick }) } /** * Create a danger button (red) */ export function DangerButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'danger', onClick }) } /** * Create a warning button (yellow) */ export function WarningButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'warning', onClick }) } /** * Create an info button (blue) */ export function InfoButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'info', onClick }) } /** * Create a ghost button (transparent background) */ export function GhostButton(label: string, onClick?: () => void): View { return Button(label, { variant: 'ghost', onClick }) } /** * Create an icon button */ export function IconButton(icon: string, onClick?: () => void, options: Omit<ButtonOptions, 'icon'> = {}): View { return Button("", { ...options, icon, onClick }) } /** * Create a small button */ export function SmallButton(label: string, options: Omit<ButtonOptions, 'size'> = {}): View { return Button(label, { ...options, size: 'small' }) } /** * Create a large button */ export function LargeButton(label: string, options: Omit<ButtonOptions, 'size'> = {}): View { return Button(label, { ...options, size: 'large' }) } /** * Create a disabled button */ export function DisabledButton(label: string): View { return Button(label, { disabled: true }) } /** * Create a loading button */ export function LoadingButton(label: string): View { return Button(label, { loading: true }) } /** * Create a full-width button */ export function FullWidthButton(label: string, options: Omit<ButtonOptions, 'fullWidth'> = {}): View { return Button(label, { ...options, fullWidth: true }) } /** * Get styles for button variants */ function getVariantStyles(variant: string, disabled: boolean) { if (disabled) { return { containerStyle: style().background(Colors.gray).foreground(Colors.darkGray), textStyle: style().foreground(Colors.darkGray) } } switch (variant) { case 'primary': return { containerStyle: style().background(Colors.blue).foreground(Colors.white), textStyle: style().foreground(Colors.white).bold() } case 'secondary': return { containerStyle: style().background(Colors.gray).foreground(Colors.white), textStyle: style().foreground(Colors.white) } case 'success': return { containerStyle: style().background(Colors.green).foreground(Colors.white), textStyle: style().foreground(Colors.white).bold() } case 'danger': return { containerStyle: style().background(Colors.red).foreground(Colors.white), textStyle: style().foreground(Colors.white).bold() } case 'warning': return { containerStyle: style().background(Colors.yellow).foreground(Colors.black), textStyle: style().foreground(Colors.black).bold() } case 'info': return { containerStyle: style().background(Colors.cyan).foreground(Colors.white), textStyle: style().foreground(Colors.white).bold() } case 'ghost': return { containerStyle: style().foreground(Colors.blue), textStyle: style().foreground(Colors.blue) } default: return { containerStyle: style().background(Colors.blue).foreground(Colors.white), textStyle: style().foreground(Colors.white).bold() } } } /** * Get styles for button sizes */ function getSizeStyles(size: string) { switch (size) { case 'small': return { padding: { top: 0, right: 1, bottom: 0, left: 1 } } case 'medium': return { padding: { top: 1, right: 2, bottom: 1, left: 2 } } case 'large': return { padding: { top: 1, right: 3, bottom: 1, left: 3 } } default: return { padding: { top: 1, right: 2, bottom: 1, left: 2 } } } } /** * Create a button group (multiple buttons arranged horizontally) */ export function ButtonGroup(buttons: View[], spacing: number = 1): View { const spacer = text(" ".repeat(spacing)) const spacedButtons: View[] = [] buttons.forEach((button, index) => { spacedButtons.push(button) if (index < buttons.length - 1) { spacedButtons.push(spacer) } }) return hstack(...spacedButtons) } /** * Create a submit/cancel button pair */ export function SubmitCancelButtons( onSubmit?: () => void, onCancel?: () => void, submitLabel: string = "Submit", cancelLabel: string = "Cancel" ): View { return ButtonGroup([ PrimaryButton(submitLabel, onSubmit), SecondaryButton(cancelLabel, onCancel) ]) } /** * Create a yes/no button pair */ export function YesNoButtons( onYes?: () => void, onNo?: () => void ): View { return ButtonGroup([ SuccessButton("Yes", onYes), DangerButton("No", onNo) ]) }