Instantly share code, notes, and snippets.
Created
January 24, 2023 07:26
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save mwood23/075f45b73d70c871155a39bca62b6902 to your computer and use it in GitHub Desktop.
Tamagui Button With Multiple Variants
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Stack, | |
styled, | |
GetProps, | |
themeable, | |
TamaguiElement, | |
VariantSpreadFunction, | |
} from '@tamagui/core' | |
import { Text } from './text' | |
import { forwardRef } from 'react' | |
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'link' | |
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | |
export type ButtonColorScheme = | |
| 'whiteAlpha' | |
| 'blackAlpha' | |
| 'primary' | |
| 'success' | |
| 'warning' | |
| 'error' | |
| 'neutral' | |
type VariantProps = { | |
variant?: ButtonVariant | |
size?: ButtonSize | |
colorScheme?: ButtonColorScheme | |
} | |
const getButtonColorScheme: VariantSpreadFunction< | |
GetProps<typeof Stack> & VariantProps, | |
ButtonColorScheme | |
> = (val = 'primary', { fonts, theme, props }) => { | |
const { variant } = props | |
switch (variant) { | |
case 'solid': | |
return { | |
backgroundColor: `$${val}.500`, | |
pressStyle: { | |
backgroundColor: `$${val}.700`, | |
}, | |
hoverStyle: { | |
backgroundColor: `$${val}.600`, | |
}, | |
focusStyle: { | |
backgroundColor: `$${val}.700`, | |
}, | |
} | |
case 'link': | |
return {} | |
case 'outline': | |
return { | |
borderColor: `$${val}.500`, | |
pressStyle: { | |
backgroundColor: `$${val}.100`, | |
}, | |
hoverStyle: { | |
backgroundColor: `$${val}.50`, | |
}, | |
focusStyle: { | |
backgroundColor: `$${val}.100`, | |
}, | |
} | |
case 'ghost': | |
return { | |
pressStyle: { | |
backgroundColor: `$${val}.100`, | |
}, | |
hoverStyle: { | |
backgroundColor: `$${val}.50`, | |
}, | |
focusStyle: { | |
backgroundColor: `$${val}.100`, | |
}, | |
} | |
default: | |
throw new Error(variant) | |
} | |
} | |
const getIsDisabledColorScheme: VariantSpreadFunction< | |
GetProps<typeof Stack> & VariantProps, | |
boolean | |
> = (val = false, { fonts, theme, props }) => { | |
return val | |
? { | |
pointerEvents: 'none', | |
backgroundColor: `$${val}.200`, | |
opacity: 0.7, | |
} | |
: {} | |
} | |
const getButtonVariant: VariantSpreadFunction< | |
GetProps<typeof Stack> & VariantProps, | |
ButtonVariant | |
> = (val = 'solid', { fonts, theme, props }) => { | |
switch (val) { | |
case 'solid': | |
return { | |
borderColor: 'transparent', | |
justifyContent: 'center', | |
alignItems: 'center', | |
flexWrap: 'nowrap', | |
flexDirection: 'row', | |
borderRadius: '$2', | |
} | |
case 'link': | |
return { | |
padding: '$0', | |
height: 'auto', | |
// borderColor: 'transparent', | |
// justifyContent: 'center', | |
// alignItems: 'center', | |
// flexWrap: 'nowrap', | |
// flexDirection: 'row', | |
// borderRadius: '$2', | |
} | |
case 'outline': | |
return { | |
borderWidth: 1, | |
justifyContent: 'center', | |
alignItems: 'center', | |
flexWrap: 'nowrap', | |
flexDirection: 'row', | |
borderRadius: '$2', | |
} | |
case 'ghost': | |
return { | |
justifyContent: 'center', | |
alignItems: 'center', | |
flexWrap: 'nowrap', | |
flexDirection: 'row', | |
borderRadius: '$2', | |
} | |
default: | |
throw new Error(val) | |
} | |
} | |
const ButtonFrame = styled(Stack, { | |
name: 'Button', | |
tag: 'button', | |
// if we wanted this only when pressable = true, we'd need to merge variants? | |
cursor: 'pointer', | |
variants: { | |
size: { | |
xs: { | |
height: '$6', | |
paddingHorizontal: '$2', | |
}, | |
sm: { | |
height: '$8', | |
paddingHorizontal: '$3', | |
}, | |
md: { | |
height: '$10', | |
paddingHorizontal: '$3', | |
}, | |
lg: { | |
height: '$12', | |
paddingHorizontal: '$4', | |
}, | |
}, | |
colorScheme: getButtonColorScheme, | |
variant: getButtonVariant, | |
// active: { | |
// true: { | |
// hoverStyle: { | |
// backgroundColor: '$background', | |
// }, | |
// }, | |
// }, | |
isDisabled: getIsDisabledColorScheme, | |
} as const, | |
}) | |
const getButtonTextVariant: VariantSpreadFunction< | |
GetProps<typeof Text> & Omit<VariantProps, 'size'>, | |
ButtonVariant | |
> = (val = 'solid', { fonts, theme, props }) => { | |
switch (val) { | |
case 'solid': | |
return {} | |
case 'link': | |
return { | |
focusStyle: { | |
textDecorationLine: 'underline', | |
}, | |
} | |
case 'outline': | |
return { | |
borderColor: `$${props.colorScheme}.500`, | |
} | |
case 'ghost': | |
return {} | |
default: | |
throw new Error(val) | |
} | |
} | |
const getButtonTextColorScheme: VariantSpreadFunction< | |
GetProps<typeof Text> & Omit<VariantProps, 'size'>, | |
ButtonColorScheme | |
> = (val = 'primary', { fonts, theme, props }) => { | |
const { variant } = props | |
switch (variant) { | |
case 'solid': | |
return { | |
color: 'white', | |
} | |
case 'link': | |
return { | |
color: `$${val}.500`, | |
} | |
case 'outline': | |
return { | |
color: val === 'neutral' ? `$${val}.900` : `$${val}.500`, | |
} | |
case 'ghost': | |
return { | |
color: `$${val}.500`, | |
} | |
default: | |
throw new Error(variant) | |
} | |
} | |
const ButtonText = styled(Text, { | |
name: 'ButtonText', | |
fontWeight: '$semibold', | |
userSelect: 'none', | |
cursor: 'pointer', | |
// flexGrow 1 leads to inconsistent native style where text pushes to start of view | |
flexGrow: 0, | |
flexShrink: 1, | |
ellipse: true, | |
variants: { | |
colorScheme: getButtonTextColorScheme, | |
variant: getButtonTextVariant, | |
} as const, | |
}) | |
export type ButtonProps = GetProps<typeof ButtonFrame> | |
const ButtonComponent = forwardRef<TamaguiElement, ButtonProps>(function Button( | |
{ | |
children, | |
size = 'md', | |
colorScheme = 'primary', | |
variant = 'solid', | |
isDisabled, | |
...props | |
}, | |
ref, | |
) { | |
return ( | |
<ButtonFrame | |
size={size} | |
colorScheme={colorScheme} | |
variant={variant} | |
isDisabled={isDisabled} | |
{...props} | |
ref={ref} | |
> | |
<ButtonText | |
size={`$${size}`} | |
colorScheme={colorScheme} | |
variant={variant} | |
// isDisabled={isDisabled} | |
> | |
{children} | |
</ButtonText> | |
</ButtonFrame> | |
) | |
}) | |
export const buttonStaticConfig = { | |
inlineProps: new Set([ | |
// text props go here (can't really optimize them, but we never fully extract button anyway) | |
// may be able to remove this entirely, as the compiler / runtime have gotten better | |
'color', | |
'fontWeight', | |
'fontSize', | |
'fontFamily', | |
'letterSpacing', | |
'textAlign', | |
]), | |
} | |
export const Button = ButtonFrame.extractable( | |
themeable(ButtonComponent, ButtonFrame.staticConfig), | |
buttonStaticConfig, | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment