[APP-1310] Button refresh (#8758)

* Rm gradient buttons from Storybook

* TEMP move storybook button section

* Remove gradient_sky

* Remove actual defs for gradient_sky and gradient_primary

* Remove other gradient defs

* Remove gradient support entirely

* Deprecate 'variant' in favor of 'color'

* Fork base styles codepath to make variant deprecation more obvious

* Remove text styles for when no color is set, never been used

* Fork text styles codepath to make variant deprecation more obvious

* Revert temp storybook commit, remove deprecated values

* Replace remaining gradient button usage

* Update Buttons storybook section

* Update tiny styles

* Update small styles

* Update large sizes

* Ensure proper alignment of buttons in storybook

* Update button colors

* Rename negative_secondary to negative_subtle

* Remove unnecessary select()

* Update icon size and gap

* Update negative_subtle styles

* Custom button colors

* Add borderCurve
This commit is contained in:
Eric Bailey
2025-08-07 11:08:31 -05:00
committed by GitHub
parent 20ac2e2733
commit cb54082eda
4 changed files with 170 additions and 206 deletions
+100 -60
View File
@@ -37,7 +37,8 @@ export type ButtonColor =
| 'secondary' | 'secondary'
| 'secondary_inverted' | 'secondary_inverted'
| 'negative' | 'negative'
| 'negative_secondary' | 'primary_subtle'
| 'negative_subtle'
export type ButtonSize = 'tiny' | 'small' | 'large' export type ButtonSize = 'tiny' | 'small' | 'large'
export type ButtonShape = 'round' | 'square' | 'default' export type ButtonShape = 'round' | 'square' | 'default'
export type VariantProps = { export type VariantProps = {
@@ -214,7 +215,11 @@ export const Button = React.forwardRef<View, ButtonProps>(
) )
const {baseStyles, hoverStyles} = React.useMemo(() => { const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = [] const baseStyles: ViewStyle[] = [
{
borderCurve: 'continuous',
},
]
const hoverStyles: ViewStyle[] = [] const hoverStyles: ViewStyle[] = []
/* /*
@@ -233,19 +238,15 @@ export const Button = React.forwardRef<View, ButtonProps>(
}) })
} else { } else {
baseStyles.push({ baseStyles.push({
backgroundColor: select(t.name, { backgroundColor: t.palette.primary_200,
light: t.palette.primary_700,
dim: t.palette.primary_300,
dark: t.palette.primary_300,
}),
}) })
} }
} else if (color === 'secondary') { } else if (color === 'secondary') {
if (!disabled) { if (!disabled) {
baseStyles.push(t.atoms.bg_contrast_25) baseStyles.push(t.atoms.bg_contrast_25)
hoverStyles.push(t.atoms.bg_contrast_50) hoverStyles.push(t.atoms.bg_contrast_100)
} else { } else {
baseStyles.push(t.atoms.bg_contrast_100) baseStyles.push(t.atoms.bg_contrast_50)
} }
} else if (color === 'secondary_inverted') { } else if (color === 'secondary_inverted') {
if (!disabled) { if (!disabled) {
@@ -253,7 +254,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
backgroundColor: t.palette.contrast_900, backgroundColor: t.palette.contrast_900,
}) })
hoverStyles.push({ hoverStyles.push({
backgroundColor: t.palette.contrast_950, backgroundColor: t.palette.contrast_975,
}) })
} else { } else {
baseStyles.push({ baseStyles.push({
@@ -269,15 +270,36 @@ export const Button = React.forwardRef<View, ButtonProps>(
backgroundColor: t.palette.negative_600, backgroundColor: t.palette.negative_600,
}) })
} else { } else {
baseStyles.push({
backgroundColor: t.palette.negative_700,
})
}
} else if (color === 'primary_subtle') {
if (!disabled) {
baseStyles.push({ baseStyles.push({
backgroundColor: select(t.name, { backgroundColor: select(t.name, {
light: t.palette.negative_700, light: t.palette.primary_50,
dim: t.palette.negative_300, dim: t.palette.primary_100,
dark: t.palette.negative_300, dark: t.palette.primary_100,
}),
})
hoverStyles.push({
backgroundColor: select(t.name, {
light: t.palette.primary_100,
dim: t.palette.primary_200,
dark: t.palette.primary_200,
}),
})
} else {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.primary_25,
dim: t.palette.primary_50,
dark: t.palette.primary_50,
}), }),
}) })
} }
} else if (color === 'negative_secondary') { } else if (color === 'negative_subtle') {
if (!disabled) { if (!disabled) {
baseStyles.push({ baseStyles.push({
backgroundColor: select(t.name, { backgroundColor: select(t.name, {
@@ -296,7 +318,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
} else { } else {
baseStyles.push({ baseStyles.push({
backgroundColor: select(t.name, { backgroundColor: select(t.name, {
light: t.palette.negative_100, light: t.palette.negative_25,
dim: t.palette.negative_50, dim: t.palette.negative_50,
dark: t.palette.negative_50, dark: t.palette.negative_50,
}), }),
@@ -407,7 +429,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
}) })
} }
} }
} else if (color === 'negative_secondary') { } else if (color === 'negative_subtle') {
if (variant === 'outline') { if (variant === 'outline') {
baseStyles.push(a.border, t.atoms.bg, { baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1, borderWidth: 1,
@@ -442,44 +464,48 @@ export const Button = React.forwardRef<View, ButtonProps>(
if (shape === 'default') { if (shape === 'default') {
if (size === 'large') { if (size === 'large') {
baseStyles.push({ baseStyles.push({
paddingVertical: 13, paddingVertical: 14,
paddingHorizontal: 20, paddingHorizontal: 24,
borderRadius: 8, borderRadius: 10,
gap: 8, gap: 4,
}) })
} else if (size === 'small') { } else if (size === 'small') {
baseStyles.push({ baseStyles.push({
paddingVertical: 9, paddingVertical: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
borderRadius: 6, borderRadius: 8,
gap: 6, gap: 3,
}) })
} else if (size === 'tiny') { } else if (size === 'tiny') {
baseStyles.push({ baseStyles.push({
paddingVertical: 4, paddingVertical: 6,
paddingHorizontal: 8, paddingHorizontal: 8,
borderRadius: 4, borderRadius: 6,
gap: 4, gap: 2,
}) })
} }
} else if (shape === 'round' || shape === 'square') { } else if (shape === 'round' || shape === 'square') {
/*
* These sizes match the actual rendered size on screen, based on
* Chrome's web inspector
*/
if (size === 'large') { if (size === 'large') {
if (shape === 'round') { if (shape === 'round') {
baseStyles.push({height: 46, width: 46}) baseStyles.push({height: 45, width: 45})
} else { } else {
baseStyles.push({height: 44, width: 44}) baseStyles.push({height: 45, width: 45})
} }
} else if (size === 'small') { } else if (size === 'small') {
if (shape === 'round') { if (shape === 'round') {
baseStyles.push({height: 34, width: 34}) baseStyles.push({height: 33, width: 33})
} else { } else {
baseStyles.push({height: 34, width: 34}) baseStyles.push({height: 33, width: 33})
} }
} else if (size === 'tiny') { } else if (size === 'tiny') {
if (shape === 'round') { if (shape === 'round') {
baseStyles.push({height: 22, width: 22}) baseStyles.push({height: 25, width: 25})
} else { } else {
baseStyles.push({height: 21, width: 21}) baseStyles.push({height: 25, width: 25})
} }
} }
@@ -487,7 +513,9 @@ export const Button = React.forwardRef<View, ButtonProps>(
baseStyles.push(a.rounded_full) baseStyles.push(a.rounded_full)
} else if (shape === 'square') { } else if (shape === 'square') {
if (size === 'tiny') { if (size === 'tiny') {
baseStyles.push(a.rounded_xs) baseStyles.push({
borderRadius: 6,
})
} else { } else {
baseStyles.push(a.rounded_sm) baseStyles.push(a.rounded_sm)
} }
@@ -567,51 +595,64 @@ export function useSharedButtonTextStyles() {
if (!disabled) { if (!disabled) {
baseStyles.push({color: t.palette.white}) baseStyles.push({color: t.palette.white})
} else { } else {
baseStyles.push({color: t.palette.white, opacity: 0.5}) baseStyles.push({color: t.palette.white})
} }
} else if (color === 'secondary') { } else if (color === 'secondary') {
if (!disabled) { if (!disabled) {
baseStyles.push({ baseStyles.push(t.atoms.text_contrast_medium)
color: t.palette.contrast_700,
})
} else { } else {
baseStyles.push({ baseStyles.push({
color: t.palette.contrast_400, color: t.palette.contrast_300,
}) })
} }
} else if (color === 'secondary_inverted') { } else if (color === 'secondary_inverted') {
if (!disabled) { if (!disabled) {
baseStyles.push({ baseStyles.push(t.atoms.text_inverted)
color: t.palette.contrast_50,
})
} else { } else {
baseStyles.push({ baseStyles.push({
color: t.palette.contrast_400, color: t.palette.contrast_300,
}) })
} }
} else if (color === 'negative') { } else if (color === 'negative') {
if (!disabled) { if (!disabled) {
baseStyles.push({color: t.palette.white}) baseStyles.push({color: t.palette.white})
} else { } else {
baseStyles.push({color: t.palette.white, opacity: 0.5}) baseStyles.push({color: t.palette.negative_300})
} }
} else if (color === 'negative_secondary') { } else if (color === 'primary_subtle') {
if (!disabled) { if (!disabled) {
baseStyles.push({ baseStyles.push({
color: select(t.name, { color: select(t.name, {
light: t.palette.negative_500, light: t.palette.primary_600,
dim: t.palette.negative_950, dim: t.palette.primary_800,
dark: t.palette.negative_900, dark: t.palette.primary_800,
}), }),
}) })
} else { } else {
baseStyles.push({ baseStyles.push({
color: select(t.name, { color: select(t.name, {
light: t.palette.negative_500, light: t.palette.primary_200,
dim: t.palette.negative_700, dim: t.palette.primary_200,
dark: t.palette.negative_700, dark: t.palette.primary_200,
}),
})
}
} else if (color === 'negative_subtle') {
if (!disabled) {
baseStyles.push({
color: select(t.name, {
light: t.palette.negative_600,
dim: t.palette.negative_800,
dark: t.palette.negative_800,
}),
})
} else {
baseStyles.push({
color: select(t.name, {
light: t.palette.negative_200,
dim: t.palette.negative_200,
dark: t.palette.negative_200,
}), }),
opacity: 0.5,
}) })
} }
} }
@@ -693,7 +734,7 @@ export function useSharedButtonTextStyles() {
baseStyles.push({color: t.palette.negative_400, opacity: 0.5}) baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
} }
} }
} else if (color === 'negative_secondary') { } else if (color === 'negative_subtle') {
if (variant === 'outline') { if (variant === 'outline') {
if (!disabled) { if (!disabled) {
baseStyles.push({color: t.palette.negative_400}) baseStyles.push({color: t.palette.negative_400})
@@ -716,7 +757,7 @@ export function useSharedButtonTextStyles() {
if (size === 'large') { if (size === 'large') {
baseStyles.push(a.text_md, a.leading_tight) baseStyles.push(a.text_md, a.leading_tight)
} else if (size === 'small') { } else if (size === 'small') {
baseStyles.push(a.text_sm, a.leading_tight) baseStyles.push(a.text_md, a.leading_tight)
} else if (size === 'tiny') { } else if (size === 'tiny') {
baseStyles.push(a.text_xs, a.leading_tight) baseStyles.push(a.text_xs, a.leading_tight)
} }
@@ -737,14 +778,16 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
export function ButtonIcon({ export function ButtonIcon({
icon: Comp, icon: Comp,
position,
size, size,
}: { }: {
icon: React.ComponentType<SVGIconProps> icon: React.ComponentType<SVGIconProps>
/**
* @deprecated no longer needed
*/
position?: 'left' | 'right' position?: 'left' | 'right'
size?: SVGIconProps['size'] size?: SVGIconProps['size']
}) { }) {
const {size: buttonSize, disabled} = useButtonContext() const {size: buttonSize} = useButtonContext()
const textStyles = useSharedButtonTextStyles() const textStyles = useSharedButtonTextStyles()
const {iconSize, iconContainerSize} = React.useMemo(() => { const {iconSize, iconContainerSize} = React.useMemo(() => {
/** /**
@@ -779,8 +822,8 @@ export function ButtonIcon({
* don't increase button size * don't increase button size
*/ */
const iconContainerSize = { const iconContainerSize = {
large: 18, large: 17,
small: 16, small: 17,
tiny: 13, tiny: 13,
}[buttonSize || 'small'] }[buttonSize || 'small']
@@ -797,9 +840,6 @@ export function ButtonIcon({
{ {
width: iconContainerSize, width: iconContainerSize,
height: iconContainerSize, height: iconContainerSize,
opacity: disabled ? 0.7 : 1,
marginLeft: position === 'left' ? -2 : 0,
marginRight: position === 'right' ? -2 : 0,
}, },
]}> ]}>
<View <View
+1 -1
View File
@@ -240,7 +240,7 @@ function DialogInner({
label={_(msg`Remove live status`)} label={_(msg`Remove live status`)}
onPress={() => removeLiveStatus()} onPress={() => removeLiveStatus()}
size={platform({native: 'large', web: 'small'})} size={platform({native: 'large', web: 'small'})}
color="negative_secondary" color="negative_subtle"
variant="solid" variant="solid"
disabled={isRemovingLiveStatus || isGoingLive}> disabled={isRemovingLiveStatus || isGoingLive}>
<ButtonText> <ButtonText>
+43 -119
View File
@@ -1,3 +1,4 @@
import {Fragment} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
@@ -5,159 +6,82 @@ import {
Button, Button,
type ButtonColor, type ButtonColor,
ButtonIcon, ButtonIcon,
type ButtonSize,
ButtonText, ButtonText,
} from '#/components/Button' } from '#/components/Button'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {H1} from '#/components/Typography' import {Text} from '#/components/Typography'
export function Buttons() { export function Buttons() {
return ( return (
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<H1>Buttons</H1> <Text style={[a.font_heavy, a.text_5xl]}>Buttons</Text>
<View style={[a.flex_row, a.flex_wrap, a.gap_md, a.align_start]}>
{[ {[
'primary', 'primary',
'secondary', 'secondary',
'secondary_inverted', 'secondary_inverted',
'negative', 'negative',
'negative_secondary', 'primary_subtle',
'negative_subtle',
].map(color => ( ].map(color => (
<View key={color} style={[a.gap_md, a.align_start]}> <Fragment key={color}>
{['tiny', 'small', 'large'].map(size => (
<Fragment key={size}>
<Text style={[a.font_heavy, a.text_2xl]}>
color={color} size={size}
</Text>
<View style={[a.flex_row, a.align_start, a.gap_md]}>
<Button <Button
color={color as ButtonColor} color={color as ButtonColor}
size="large" size={size as ButtonSize}
label="Click here"> label="Click here">
<ButtonText>Button</ButtonText> <ButtonText>Button</ButtonText>
</Button> </Button>
<Button <Button
disabled disabled
color={color as ButtonColor} color={color as ButtonColor}
size="large" size={size as ButtonSize}
label="Click here"> label="Click here">
<ButtonText>Button</ButtonText> <ButtonText>Button</ButtonText>
</Button> </Button>
<Button
color={color as ButtonColor}
size={size as ButtonSize}
shape="round"
label="Click here">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button
color={color as ButtonColor}
size={size as ButtonSize}
shape="square"
label="Click here">
<ButtonIcon icon={ChevronLeft} />
</Button>
</View> </View>
))} <View style={[a.flex_row, a.gap_md]}>
</View> <Button
color={color as ButtonColor}
<View style={[a.flex_wrap, a.gap_md, a.align_start]}> size={size as ButtonSize}
<Button color="primary" size="large" label="Link out"> label="Click here">
<ButtonText>Button</ButtonText>
</Button>
<Button color="primary" size="large" label="Link out">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" />
</Button>
<Button color="primary" size="small" label="Link out">
<ButtonText>Button</ButtonText>
</Button>
<Button color="primary" size="small" label="Link out">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" />
</Button>
<Button color="primary" size="tiny" label="Link out">
<ButtonIcon icon={Globe} position="left" /> <ButtonIcon icon={Globe} position="left" />
<ButtonText>Button</ButtonText> <ButtonText>Button</ButtonText>
</Button> </Button>
</View> <Button
disabled
<View style={[a.flex_row, a.gap_md, a.align_center]}> color={color as ButtonColor}
<Button color="primary" size="large" label="Link out"> size={size as ButtonSize}
<ButtonText>Button</ButtonText> label="Click here">
</Button>
<Button color="primary" size="large" label="Link out">
<ButtonText>Button</ButtonText> <ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" /> <ButtonIcon icon={Globe} position="right" />
</Button> </Button>
<Button color="primary" size="large" label="Link out">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" size="lg" />
</Button>
<Button color="primary" size="large" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="large" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} size="lg" />
</Button>
</View>
<View style={[a.flex_row, a.gap_md, a.align_center]}>
<Button color="primary" size="small" label="Link out">
<ButtonText>Button</ButtonText>
</Button>
<Button color="primary" size="small" label="Link out">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" />
</Button>
<Button color="primary" size="small" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="small" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} size="lg" />
</Button>
</View>
<View style={[a.flex_row, a.gap_md, a.align_center]}>
<Button color="primary" size="tiny" label="Link out">
<ButtonText>Button</ButtonText>
</Button>
<Button color="primary" size="tiny" label="Link out">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" />
</Button>
<Button color="primary" size="tiny" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="tiny" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} size="md" />
</Button>
</View>
<View style={[a.flex_row, a.gap_md, a.align_center]}>
<Button color="primary" size="large" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="small" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="tiny" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="large" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="small" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="tiny" shape="round" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
</View>
<View style={[a.flex_row, a.gap_md, a.align_start]}>
<Button color="primary" size="large" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="small" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="tiny" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="large" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="small" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
<Button color="primary" size="tiny" shape="square" label="Link out">
<ButtonIcon icon={ChevronLeft} />
</Button>
</View> </View>
</Fragment>
))}
</Fragment>
))}
</View> </View>
) )
} }
+1 -1
View File
@@ -87,6 +87,7 @@ function StorybookInner() {
</Button> </Button>
</View> </View>
<Buttons />
<Toasts /> <Toasts />
<Button <Button
@@ -109,7 +110,6 @@ function StorybookInner() {
</ThemeProvider> </ThemeProvider>
<Forms /> <Forms />
<Buttons />
<Typography /> <Typography />
<Spacing /> <Spacing />
<Shadows /> <Shadows />