UNPKG

goobs-frontend

Version:

A comprehensive React-based libary for building modern web applications

404 lines (381 loc) 12.8 kB
// -------------------------------------------------------------------------- // BUTTON THEME SYSTEM // -------------------------------------------------------------------------- import React from 'react' import { TRANSITIONS, SHADOWS } from './shared' export interface ButtonTheme { container: { background: string border: string borderRadius: string boxShadow: string backdropFilter: string fontFamily: string fontSize: string fontWeight: string | number letterSpacing: string color: string textShadow?: string textTransform?: string padding: string minHeight: string gap: string backgroundImage?: string } containerHover: { transform: string boxShadow: string backgroundColor: string borderColor?: string color: string textShadow?: string } containerActive: { transform: string boxShadow: string } containerDisabled: { opacity: number backgroundColor: string color: string borderColor?: string textShadow?: string } transition: string } export interface ButtonStyles { // Theme selection theme?: 'light' | 'dark' | 'sacred' // Layout and positioning contentAlign?: 'left' | 'center' | 'right' iconLocation?: 'left' | 'right' | 'above' // Container styling backgroundColor?: string borderColor?: string borderRadius?: string borderWidth?: string boxShadow?: string backdropFilter?: string backgroundImage?: string fontFamily?: string fontSize?: string fontWeight?: string | number letterSpacing?: string color?: string textShadow?: string textTransform?: string padding?: string minHeight?: string gap?: string // Hover states hoverBackgroundColor?: string hoverBorderColor?: string hoverBoxShadow?: string hoverTransform?: string hoverColor?: string hoverTextShadow?: string // Active states activeTransform?: string activeBoxShadow?: string // Disabled states disabledOpacity?: number disabledBackgroundColor?: string disabledColor?: string disabledBorderColor?: string disabledTextShadow?: string // Layout and spacing margin?: string marginTop?: string marginBottom?: string marginLeft?: string marginRight?: string // Transitions transitionDuration?: string transitionEasing?: string // States disabled?: boolean outline?: boolean // Dimensions width?: string maxWidth?: string minWidth?: string height?: string maxHeight?: string } export const buttonThemes: Record<'light' | 'dark' | 'sacred', ButtonTheme> = { light: { container: { background: 'rgba(255, 255, 255, 0.95)', border: '1px solid rgba(226, 232, 240, 0.8)', borderRadius: '10px', boxShadow: SHADOWS.light.small, backdropFilter: 'blur(8px)', fontFamily: '"Inter", sans-serif', fontSize: '14px', fontWeight: 600, letterSpacing: '-0.025em', color: 'rgb(55, 65, 81)', padding: '12px 24px', minHeight: '44px', gap: '8px', }, containerHover: { transform: 'translateY(-1px)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 6px rgba(0, 0, 0, 0.08)', backgroundColor: 'rgba(239, 246, 255, 0.95)', borderColor: 'rgba(59, 130, 246, 0.3)', color: 'rgb(29, 78, 216)', }, containerActive: { transform: 'translateY(0px)', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.08)', }, containerDisabled: { opacity: 0.6, backgroundColor: 'rgba(249, 250, 251, 0.8)', color: 'rgb(156, 163, 175)', }, transition: TRANSITIONS.medium, }, dark: { container: { background: 'rgba(31, 41, 55, 0.95)', border: '1px solid rgba(75, 85, 99, 0.8)', borderRadius: '10px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.4)', backdropFilter: 'blur(8px)', fontFamily: '"Inter", sans-serif', fontSize: '14px', fontWeight: 600, letterSpacing: '-0.025em', color: 'rgb(243, 244, 246)', padding: '12px 24px', minHeight: '44px', gap: '8px', }, containerHover: { transform: 'translateY(-1px)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.3)', backgroundColor: 'rgba(30, 58, 138, 0.3)', borderColor: 'rgba(96, 165, 250, 0.4)', color: 'rgb(96, 165, 250)', }, containerActive: { transform: 'translateY(0px)', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2)', }, containerDisabled: { opacity: 0.6, backgroundColor: 'rgba(17, 24, 39, 0.8)', color: 'rgb(75, 85, 99)', }, transition: TRANSITIONS.medium, }, sacred: { container: { background: 'rgba(10, 10, 10, 0.9)', border: '2px solid rgba(255, 215, 0, 0.4)', borderRadius: '12px', boxShadow: SHADOWS.sacred.small, backdropFilter: 'blur(8px)', fontFamily: '"Cinzel", serif', fontSize: '15px', fontWeight: 700, letterSpacing: '0.05em', color: 'rgba(255, 215, 0, 0.9)', textShadow: '0 0 10px rgba(255, 215, 0, 0.5)', textTransform: 'uppercase', padding: '16px 32px', minHeight: '52px', gap: '12px', }, containerHover: { transform: 'translateY(-2px)', boxShadow: SHADOWS.sacred.medium, backgroundColor: 'rgba(10, 10, 10, 0.9)', borderColor: 'rgba(255, 215, 0, 0.8)', color: '#FFD700', textShadow: '0 0 15px rgba(255, 215, 0, 0.8)', }, containerActive: { transform: 'translateY(-1px)', boxShadow: '0 0 20px rgba(255, 215, 0, 0.3), 0 0 40px rgba(255, 215, 0, 0.15)', }, containerDisabled: { opacity: 0.4, backgroundColor: 'rgba(10, 10, 10, 0.6)', color: 'rgba(255, 215, 0, 0.3)', borderColor: 'rgba(255, 215, 0, 0.2)', textShadow: 'none', }, transition: TRANSITIONS.premium, }, } // Helper function to get computed theme with custom style overrides export const getButtonTheme = (styles?: ButtonStyles): ButtonTheme => { const theme = styles?.theme || 'light' const baseTheme = buttonThemes[theme] if (!styles) { return baseTheme } return { container: { background: styles.backgroundColor || baseTheme.container.background, border: styles.borderColor ? `${styles.borderWidth || '1px'} solid ${styles.borderColor}` : baseTheme.container.border, borderRadius: styles.borderRadius || baseTheme.container.borderRadius, boxShadow: styles.boxShadow || baseTheme.container.boxShadow, backdropFilter: styles.backdropFilter || baseTheme.container.backdropFilter, fontFamily: styles.fontFamily || baseTheme.container.fontFamily, fontSize: styles.fontSize || baseTheme.container.fontSize, fontWeight: styles.fontWeight || baseTheme.container.fontWeight, letterSpacing: styles.letterSpacing || baseTheme.container.letterSpacing, color: styles.color || baseTheme.container.color, textShadow: styles.textShadow || baseTheme.container.textShadow, textTransform: styles.textTransform || baseTheme.container.textTransform, padding: styles.padding || baseTheme.container.padding, minHeight: styles.minHeight || baseTheme.container.minHeight, gap: styles.gap || baseTheme.container.gap, backgroundImage: styles.backgroundImage || baseTheme.container.backgroundImage, }, containerHover: { transform: styles.hoverTransform || baseTheme.containerHover.transform, boxShadow: styles.hoverBoxShadow || baseTheme.containerHover.boxShadow, backgroundColor: styles.hoverBackgroundColor || baseTheme.containerHover.backgroundColor, borderColor: styles.hoverBorderColor || baseTheme.containerHover.borderColor, color: styles.hoverColor || baseTheme.containerHover.color, textShadow: styles.hoverTextShadow || baseTheme.containerHover.textShadow, }, containerActive: { transform: styles.activeTransform || baseTheme.containerActive.transform, boxShadow: styles.activeBoxShadow || baseTheme.containerActive.boxShadow, }, containerDisabled: { opacity: styles.disabledOpacity ?? baseTheme.containerDisabled.opacity, backgroundColor: styles.disabledBackgroundColor || baseTheme.containerDisabled.backgroundColor, color: styles.disabledColor || baseTheme.containerDisabled.color, borderColor: styles.disabledBorderColor || baseTheme.containerDisabled.borderColor, textShadow: styles.disabledTextShadow || baseTheme.containerDisabled.textShadow, }, transition: styles.transitionDuration ? `all ${styles.transitionDuration} ${styles.transitionEasing || 'cubic-bezier(0.4, 0, 0.2, 1)'}` : baseTheme.transition, } } // Main style generator function export const getButtonStyles = ( styles?: ButtonStyles, isHovered?: boolean, isActive?: boolean, isDisabled?: boolean ) => { const themeConfig = getButtonTheme(styles) const contentAlign = styles?.contentAlign || 'center' const iconLocation = styles?.iconLocation || 'left' const justifyContent = contentAlign === 'left' ? 'flex-start' : contentAlign === 'right' ? 'flex-end' : 'center' const flexDirection: React.CSSProperties['flexDirection'] = iconLocation === 'above' ? 'column' : 'row' // Parse the border shorthand to avoid conflicts with individual borderColor const parseBorder = (border: string) => { // Expecting format like "1px solid #color" or "2px solid rgba(255,215,0,0.4)" const parts = border.split(' ') return { borderWidth: parts[0] || '1px', borderStyle: parts[1] || 'solid', borderColor: parts.slice(2).join(' ') || 'transparent', } } const borderParts = parseBorder(themeConfig.container.border) const containerStyle: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', position: 'relative', overflow: 'hidden', transition: themeConfig.transition, borderRadius: themeConfig.container.borderRadius, borderWidth: borderParts.borderWidth, borderStyle: borderParts.borderStyle, borderColor: borderParts.borderColor, backgroundColor: themeConfig.container.background, backdropFilter: themeConfig.container.backdropFilter, boxShadow: themeConfig.container.boxShadow, cursor: isDisabled ? 'not-allowed' : 'pointer', fontFamily: themeConfig.container.fontFamily, fontSize: themeConfig.container.fontSize, fontWeight: themeConfig.container.fontWeight, letterSpacing: themeConfig.container.letterSpacing, color: themeConfig.container.color, textShadow: themeConfig.container.textShadow, textTransform: themeConfig.container.textTransform as any, textAlign: 'center' as const, userSelect: 'none' as const, whiteSpace: 'nowrap' as const, padding: themeConfig.container.padding, minHeight: themeConfig.container.minHeight, gap: themeConfig.container.gap, backgroundImage: themeConfig.container.backgroundImage, justifyContent, flexDirection, // Layout styling margin: styles?.margin, marginTop: styles?.marginTop, marginBottom: styles?.marginBottom, marginLeft: styles?.marginLeft, marginRight: styles?.marginRight, width: styles?.width, maxWidth: styles?.maxWidth, minWidth: styles?.minWidth, height: styles?.height, maxHeight: styles?.maxHeight, // State-based styling ...(isDisabled && { opacity: themeConfig.containerDisabled.opacity, backgroundColor: themeConfig.containerDisabled.backgroundColor, color: themeConfig.containerDisabled.color, borderColor: themeConfig.containerDisabled.borderColor, textShadow: themeConfig.containerDisabled.textShadow, transform: 'none', boxShadow: 'none', }), ...(isHovered && !isDisabled && { transform: themeConfig.containerHover.transform, boxShadow: themeConfig.containerHover.boxShadow, backgroundColor: themeConfig.containerHover.backgroundColor, borderColor: themeConfig.containerHover.borderColor, color: themeConfig.containerHover.color, textShadow: themeConfig.containerHover.textShadow, }), ...(isActive && !isDisabled && { transform: themeConfig.containerActive.transform, boxShadow: themeConfig.containerActive.boxShadow, }), // Apply outline override ...(styles?.outline === false && { borderWidth: '0', borderStyle: 'none', borderColor: 'transparent', boxShadow: 'none', }), } return { container: containerStyle, } }