/** * Button styles using defineStyle with $iterator expansion. * * Dynamic style functions are used for intent/type combinations since * the color depends on both values (compound logic). */ import { StyleSheet } from 'react-native-unistyles'; import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme'; import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme'; import { ButtonGradient } from './types'; // Required: Unistyles must see StyleSheet usage in original source to process this file void StyleSheet; // Wrap theme for $iterator support type Theme = ThemeStyleWrapper; type ButtonSize = Size; type ButtonType = 'contained' | 'outlined' | 'text'; export type ButtonVariants = { size: ButtonSize; intent: Intent; type: ButtonType; disabled: boolean; gradient?: ButtonGradient; } /** * All dynamic props passed to button style functions. */ export type ButtonDynamicProps = { intent?: Intent; type?: ButtonType; size?: Size; disabled?: boolean; gradient?: ButtonGradient; }; /** * Button styles with $iterator expansion for size variants. * * Intent/type combinations use dynamic functions with inlined theme accesses * so Unistyles can trace all possible theme paths. */ export const buttonStyles = defineStyle('Button', (theme: Theme) => ({ button: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({ boxSizing: 'border-box', alignItems: 'center', justifyContent: 'center', borderRadius: 8, fontWeight: '500', textAlign: 'center', // Inline theme accesses so Unistyles can trace them backgroundColor: type === 'contained' ? theme.intents[intent].primary : type === 'outlined' ? theme.colors.surface.primary : 'transparent', borderColor: type === 'outlined' ? theme.intents[intent].primary : 'transparent', borderWidth: type === 'outlined' ? 1 : 0, borderStyle: type === 'outlined' ? 'solid' as const : undefined, _web: { display: 'flex', transition: 'all 0.1s ease', }, variants: { // $iterator expands for each button size type: { contained: { backgroundColor: theme.$intents.primary, borderColor: 'transparent', }, outlined: { backgroundColor: 'transparent', borderColor: theme.$intents.primary, }, text: { backgroundColor: 'transparent', borderColor: 'transparent', borderWidth: 0, _web: { _hover: { backgroundColor: theme.colors.surface.secondary, }, _active: { backgroundColor: theme.colors.surface.tertiary, }, }, } }, size: { paddingVertical: theme.sizes.$button.paddingVertical, paddingHorizontal: theme.sizes.$button.paddingHorizontal, minHeight: theme.sizes.$button.minHeight, }, disabled: { true: { opacity: 0.6 }, false: { opacity: 1, _web: { cursor: 'pointer', _hover: { opacity: 0.90 }, _active: { opacity: 0.75 } } }, }, gradient: { darken: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%)' } }, lighten: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)' } }, }, }, }), text: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({ fontWeight: '500', textAlign: 'center', // Inline: contained uses contrast, others use primary color: type === 'contained' ? theme.intents[intent].contrast : theme.intents[intent].primary, variants: { size: { fontSize: theme.sizes.$button.fontSize, lineHeight: theme.sizes.$button.fontSize, }, disabled: { true: { opacity: 0.6 }, false: { opacity: 1 }, }, }, }), icon: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', color: type === 'contained' ? theme.intents[intent].contrast : theme.intents[intent].primary, variants: { size: { width: theme.sizes.$button.iconSize, height: theme.sizes.$button.iconSize, }, }, }), iconContainer: (_props: ButtonDynamicProps) => ({ display: 'flex' as const, flexDirection: 'row' as const, alignItems: 'center' as const, justifyContent: 'center' as const, gap: 10, }), spinner: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', // Match the text color based on button type color: type === 'contained' ? theme.intents[intent].contrast : theme.intents[intent].primary, variants: { size: { width: theme.sizes.$button.iconSize, height: theme.sizes.$button.iconSize, }, }, }), }));