[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_inverted'
| 'negative'
| 'negative_secondary'
| 'primary_subtle'
| 'negative_subtle'
export type ButtonSize = 'tiny' | 'small' | 'large'
export type ButtonShape = 'round' | 'square' | 'default'
export type VariantProps = {
@@ -214,7 +215,11 @@ export const Button = React.forwardRef<View, ButtonProps>(
)
const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = []
const baseStyles: ViewStyle[] = [
{
borderCurve: 'continuous',
},
]
const hoverStyles: ViewStyle[] = []
/*
@@ -233,19 +238,15 @@ export const Button = React.forwardRef<View, ButtonProps>(
})
} else {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.primary_700,
dim: t.palette.primary_300,
dark: t.palette.primary_300,
}),
backgroundColor: t.palette.primary_200,
})
}
} else if (color === 'secondary') {
if (!disabled) {
baseStyles.push(t.atoms.bg_contrast_25)
hoverStyles.push(t.atoms.bg_contrast_50)
hoverStyles.push(t.atoms.bg_contrast_100)
} else {
baseStyles.push(t.atoms.bg_contrast_100)
baseStyles.push(t.atoms.bg_contrast_50)
}
} else if (color === 'secondary_inverted') {
if (!disabled) {
@@ -253,7 +254,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
backgroundColor: t.palette.contrast_900,
})
hoverStyles.push({
backgroundColor: t.palette.contrast_950,
backgroundColor: t.palette.contrast_975,
})
} else {
baseStyles.push({
@@ -269,15 +270,36 @@ export const Button = React.forwardRef<View, ButtonProps>(
backgroundColor: t.palette.negative_600,
})
} else {
baseStyles.push({
backgroundColor: t.palette.negative_700,
})
}
} else if (color === 'primary_subtle') {
if (!disabled) {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.negative_700,
dim: t.palette.negative_300,
dark: t.palette.negative_300,
light: t.palette.primary_50,
dim: t.palette.primary_100,
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) {
baseStyles.push({
backgroundColor: select(t.name, {
@@ -296,7 +318,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
} else {
baseStyles.push({
backgroundColor: select(t.name, {
light: t.palette.negative_100,
light: t.palette.negative_25,
dim: 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') {
baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
@@ -442,44 +464,48 @@ export const Button = React.forwardRef<View, ButtonProps>(
if (shape === 'default') {
if (size === 'large') {
baseStyles.push({
paddingVertical: 13,
paddingHorizontal: 20,
borderRadius: 8,
gap: 8,
paddingVertical: 14,
paddingHorizontal: 24,
borderRadius: 10,
gap: 4,
})
} else if (size === 'small') {
baseStyles.push({
paddingVertical: 9,
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 6,
gap: 6,
borderRadius: 8,
gap: 3,
})
} else if (size === 'tiny') {
baseStyles.push({
paddingVertical: 4,
paddingVertical: 6,
paddingHorizontal: 8,
borderRadius: 4,
gap: 4,
borderRadius: 6,
gap: 2,
})
}
} 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 (shape === 'round') {
baseStyles.push({height: 46, width: 46})
baseStyles.push({height: 45, width: 45})
} else {
baseStyles.push({height: 44, width: 44})
baseStyles.push({height: 45, width: 45})
}
} else if (size === 'small') {
if (shape === 'round') {
baseStyles.push({height: 34, width: 34})
baseStyles.push({height: 33, width: 33})
} else {
baseStyles.push({height: 34, width: 34})
baseStyles.push({height: 33, width: 33})
}
} else if (size === 'tiny') {
if (shape === 'round') {
baseStyles.push({height: 22, width: 22})
baseStyles.push({height: 25, width: 25})
} 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)
} else if (shape === 'square') {
if (size === 'tiny') {
baseStyles.push(a.rounded_xs)
baseStyles.push({
borderRadius: 6,
})
} else {
baseStyles.push(a.rounded_sm)
}
@@ -567,51 +595,64 @@ export function useSharedButtonTextStyles() {
if (!disabled) {
baseStyles.push({color: t.palette.white})
} else {
baseStyles.push({color: t.palette.white, opacity: 0.5})
baseStyles.push({color: t.palette.white})
}
} else if (color === 'secondary') {
if (!disabled) {
baseStyles.push({
color: t.palette.contrast_700,
})
baseStyles.push(t.atoms.text_contrast_medium)
} else {
baseStyles.push({
color: t.palette.contrast_400,
color: t.palette.contrast_300,
})
}
} else if (color === 'secondary_inverted') {
if (!disabled) {
baseStyles.push({
color: t.palette.contrast_50,
})
baseStyles.push(t.atoms.text_inverted)
} else {
baseStyles.push({
color: t.palette.contrast_400,
color: t.palette.contrast_300,
})
}
} else if (color === 'negative') {
if (!disabled) {
baseStyles.push({color: t.palette.white})
} 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) {
baseStyles.push({
color: select(t.name, {
light: t.palette.negative_500,
dim: t.palette.negative_950,
dark: t.palette.negative_900,
light: t.palette.primary_600,
dim: t.palette.primary_800,
dark: t.palette.primary_800,
}),
})
} else {
baseStyles.push({
color: select(t.name, {
light: t.palette.negative_500,
dim: t.palette.negative_700,
dark: t.palette.negative_700,
light: t.palette.primary_200,
dim: t.palette.primary_200,
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})
}
}
} else if (color === 'negative_secondary') {
} else if (color === 'negative_subtle') {
if (variant === 'outline') {
if (!disabled) {
baseStyles.push({color: t.palette.negative_400})
@@ -716,7 +757,7 @@ export function useSharedButtonTextStyles() {
if (size === 'large') {
baseStyles.push(a.text_md, a.leading_tight)
} else if (size === 'small') {
baseStyles.push(a.text_sm, a.leading_tight)
baseStyles.push(a.text_md, a.leading_tight)
} else if (size === 'tiny') {
baseStyles.push(a.text_xs, a.leading_tight)
}
@@ -737,14 +778,16 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
export function ButtonIcon({
icon: Comp,
position,
size,
}: {
icon: React.ComponentType<SVGIconProps>
/**
* @deprecated no longer needed
*/
position?: 'left' | 'right'
size?: SVGIconProps['size']
}) {
const {size: buttonSize, disabled} = useButtonContext()
const {size: buttonSize} = useButtonContext()
const textStyles = useSharedButtonTextStyles()
const {iconSize, iconContainerSize} = React.useMemo(() => {
/**
@@ -779,8 +822,8 @@ export function ButtonIcon({
* don't increase button size
*/
const iconContainerSize = {
large: 18,
small: 16,
large: 17,
small: 17,
tiny: 13,
}[buttonSize || 'small']
@@ -797,9 +840,6 @@ export function ButtonIcon({
{
width: iconContainerSize,
height: iconContainerSize,
opacity: disabled ? 0.7 : 1,
marginLeft: position === 'left' ? -2 : 0,
marginRight: position === 'right' ? -2 : 0,
},
]}>
<View
+1 -1
View File
@@ -240,7 +240,7 @@ function DialogInner({
label={_(msg`Remove live status`)}
onPress={() => removeLiveStatus()}
size={platform({native: 'large', web: 'small'})}
color="negative_secondary"
color="negative_subtle"
variant="solid"
disabled={isRemovingLiveStatus || isGoingLive}>
<ButtonText>
+68 -144
View File
@@ -1,3 +1,4 @@
import {Fragment} from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
@@ -5,159 +6,82 @@ import {
Button,
type ButtonColor,
ButtonIcon,
type ButtonSize,
ButtonText,
} from '#/components/Button'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {H1} from '#/components/Typography'
import {Text} from '#/components/Typography'
export function Buttons() {
return (
<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',
'secondary',
'secondary_inverted',
'negative',
'negative_secondary',
].map(color => (
<View key={color} style={[a.gap_md, a.align_start]}>
<Button
color={color as ButtonColor}
size="large"
label="Click here">
<ButtonText>Button</ButtonText>
</Button>
<Button
disabled
color={color as ButtonColor}
size="large"
label="Click here">
<ButtonText>Button</ButtonText>
</Button>
</View>
))}
</View>
<View style={[a.flex_wrap, a.gap_md, a.align_start]}>
<Button color="primary" size="large" label="Link out">
<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" />
<ButtonText>Button</ButtonText>
</Button>
</View>
<View style={[a.flex_row, a.gap_md, a.align_center]}>
<Button color="primary" size="large" label="Link out">
<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="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>
{[
'primary',
'secondary',
'secondary_inverted',
'negative',
'primary_subtle',
'negative_subtle',
].map(color => (
<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
color={color as ButtonColor}
size={size as ButtonSize}
label="Click here">
<ButtonText>Button</ButtonText>
</Button>
<Button
disabled
color={color as ButtonColor}
size={size as ButtonSize}
label="Click here">
<ButtonText>Button</ButtonText>
</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 style={[a.flex_row, a.gap_md]}>
<Button
color={color as ButtonColor}
size={size as ButtonSize}
label="Click here">
<ButtonIcon icon={Globe} position="left" />
<ButtonText>Button</ButtonText>
</Button>
<Button
disabled
color={color as ButtonColor}
size={size as ButtonSize}
label="Click here">
<ButtonText>Button</ButtonText>
<ButtonIcon icon={Globe} position="right" />
</Button>
</View>
</Fragment>
))}
</Fragment>
))}
</View>
)
}
+1 -1
View File
@@ -87,6 +87,7 @@ function StorybookInner() {
</Button>
</View>
<Buttons />
<Toasts />
<Button
@@ -109,7 +110,6 @@ function StorybookInner() {
</ThemeProvider>
<Forms />
<Buttons />
<Typography />
<Spacing />
<Shadows />