[Neue] Buttons (#5406)
* Re-align button sizing (cherry picked from commit bcec243bb59dfe468313d98ba61f464d9750feec) * Use large, small, tiny (cherry picked from commit 1dc333c2993ab7f2e0ac750c0670dcec9a7069d0) * Tweaks
This commit is contained in:
+111
-37
@@ -14,7 +14,7 @@ import {
|
||||
} from 'react-native'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
|
||||
import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf'
|
||||
import {atoms as a, flatten, select, tokens, useTheme, web} from '#/alf'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -30,7 +30,7 @@ export type ButtonColor =
|
||||
| 'gradient_sunset'
|
||||
| 'gradient_nordic'
|
||||
| 'gradient_bonfire'
|
||||
export type ButtonSize = 'tiny' | 'xsmall' | 'small' | 'medium' | 'large'
|
||||
export type ButtonSize = 'tiny' | 'small' | 'large'
|
||||
export type ButtonShape = 'round' | 'square' | 'default'
|
||||
export type VariantProps = {
|
||||
/**
|
||||
@@ -343,39 +343,46 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
|
||||
if (shape === 'default') {
|
||||
if (size === 'large') {
|
||||
baseStyles.push(
|
||||
{paddingVertical: 15},
|
||||
a.px_2xl,
|
||||
a.rounded_sm,
|
||||
a.gap_md,
|
||||
)
|
||||
} else if (size === 'medium') {
|
||||
baseStyles.push(
|
||||
{paddingVertical: 12},
|
||||
a.px_2xl,
|
||||
a.rounded_sm,
|
||||
a.gap_md,
|
||||
)
|
||||
baseStyles.push({
|
||||
paddingVertical: 13,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
gap: 8,
|
||||
})
|
||||
} else if (size === 'small') {
|
||||
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
|
||||
} else if (size === 'xsmall') {
|
||||
baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm)
|
||||
baseStyles.push({
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 6,
|
||||
gap: 6,
|
||||
})
|
||||
} else if (size === 'tiny') {
|
||||
baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs)
|
||||
baseStyles.push({
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 8,
|
||||
borderRadius: 4,
|
||||
gap: 4,
|
||||
})
|
||||
}
|
||||
} else if (shape === 'round' || shape === 'square') {
|
||||
if (size === 'large') {
|
||||
if (shape === 'round') {
|
||||
baseStyles.push({height: 54, width: 54})
|
||||
baseStyles.push({height: 46, width: 46})
|
||||
} else {
|
||||
baseStyles.push({height: 50, width: 50})
|
||||
baseStyles.push({height: 44, width: 44})
|
||||
}
|
||||
} else if (size === 'small') {
|
||||
baseStyles.push({height: 34, width: 34})
|
||||
} else if (size === 'xsmall') {
|
||||
baseStyles.push({height: 28, width: 28})
|
||||
if (shape === 'round') {
|
||||
baseStyles.push({height: 36, width: 36})
|
||||
} else {
|
||||
baseStyles.push({height: 34, width: 34})
|
||||
}
|
||||
} else if (size === 'tiny') {
|
||||
baseStyles.push({height: 20, width: 20})
|
||||
if (shape === 'round') {
|
||||
baseStyles.push({height: 22, width: 22})
|
||||
} else {
|
||||
baseStyles.push({height: 21, width: 21})
|
||||
}
|
||||
}
|
||||
|
||||
if (shape === 'round') {
|
||||
@@ -619,11 +626,11 @@ export function useSharedButtonTextStyles() {
|
||||
}
|
||||
|
||||
if (size === 'large') {
|
||||
baseStyles.push(a.text_md, android({paddingBottom: 1}))
|
||||
baseStyles.push(a.text_md, a.leading_tight, web({paddingTop: 1}))
|
||||
} else if (size === 'small') {
|
||||
baseStyles.push(a.text_sm, a.leading_tight, web({paddingTop: 1}))
|
||||
} else if (size === 'tiny') {
|
||||
baseStyles.push(a.text_xs, android({paddingBottom: 1}))
|
||||
} else {
|
||||
baseStyles.push(a.text_sm, android({paddingBottom: 1}))
|
||||
baseStyles.push(a.text_xs, a.leading_tight)
|
||||
}
|
||||
|
||||
return StyleSheet.flatten(baseStyles)
|
||||
@@ -643,31 +650,98 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
|
||||
export function ButtonIcon({
|
||||
icon: Comp,
|
||||
position,
|
||||
size: iconSize,
|
||||
size,
|
||||
}: {
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
position?: 'left' | 'right'
|
||||
size?: SVGIconProps['size']
|
||||
}) {
|
||||
const {size, disabled} = useButtonContext()
|
||||
const {size: buttonSize, disabled} = useButtonContext()
|
||||
const textStyles = useSharedButtonTextStyles()
|
||||
const {iconSize, iconContainerSize} = React.useMemo(() => {
|
||||
/**
|
||||
* Pre-set icon sizes for different button sizes
|
||||
*/
|
||||
const iconSizeShorthand =
|
||||
size ??
|
||||
(({
|
||||
large: 'sm',
|
||||
small: 'xs',
|
||||
tiny: 'xs',
|
||||
}[buttonSize || 'small'] || 'sm') as Exclude<
|
||||
SVGIconProps['size'],
|
||||
undefined
|
||||
>)
|
||||
|
||||
/*
|
||||
* Copied here from icons/common.tsx so we can tweak if we need to, but
|
||||
* also so that we can calculate transforms.
|
||||
*/
|
||||
const iconSize = {
|
||||
xs: 12,
|
||||
sm: 16,
|
||||
md: 20,
|
||||
lg: 24,
|
||||
xl: 28,
|
||||
'2xl': 32,
|
||||
}[iconSizeShorthand]
|
||||
|
||||
/*
|
||||
* Goal here is to match rendered text size so that different size icons
|
||||
* don't increase button size
|
||||
*/
|
||||
const iconContainerSize = {
|
||||
large: 18,
|
||||
small: 16,
|
||||
tiny: 13,
|
||||
}[buttonSize || 'small']
|
||||
|
||||
return {
|
||||
iconSize,
|
||||
iconContainerSize,
|
||||
}
|
||||
}, [buttonSize, size])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.z_20,
|
||||
{
|
||||
width: iconContainerSize,
|
||||
height: iconContainerSize,
|
||||
opacity: disabled ? 0.7 : 1,
|
||||
marginLeft: position === 'left' ? -2 : 0,
|
||||
marginRight: position === 'right' ? -2 : 0,
|
||||
},
|
||||
]}>
|
||||
<Comp
|
||||
size={
|
||||
iconSize ?? (size === 'large' ? 'md' : size === 'tiny' ? 'xs' : 'sm')
|
||||
}
|
||||
style={[{color: textStyles.color, pointerEvents: 'none'}]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
{
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: [
|
||||
{
|
||||
translateX: (iconSize / 2) * -1,
|
||||
},
|
||||
{
|
||||
translateY: (iconSize / 2) * -1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
<Comp
|
||||
width={iconSize}
|
||||
style={[
|
||||
{
|
||||
color: textStyles.color,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export function Cancel({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size={gtMobile ? 'small' : 'medium'}
|
||||
size={gtMobile ? 'small' : 'large'}
|
||||
label={cta || _(msg`Cancel`)}
|
||||
onPress={onPress}>
|
||||
<ButtonText>{cta || _(msg`Cancel`)}</ButtonText>
|
||||
@@ -163,7 +163,7 @@ export function Action({
|
||||
<Button
|
||||
variant="solid"
|
||||
color={color}
|
||||
size={gtMobile ? 'small' : 'medium'}
|
||||
size={gtMobile ? 'small' : 'large'}
|
||||
label={cta || _(msg`Confirm`)}
|
||||
onPress={handleOnPress}
|
||||
testID={testID}>
|
||||
|
||||
@@ -125,7 +125,7 @@ export function WizardEditListDialog({
|
||||
label={_(msg`Close`)}
|
||||
variant="ghost"
|
||||
color="primary"
|
||||
size="xsmall"
|
||||
size="small"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
|
||||
@@ -101,7 +101,7 @@ function WizardListCard({
|
||||
label={_(msg`Remove`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="xsmall"
|
||||
size="small"
|
||||
style={[a.self_center, {marginLeft: 'auto'}]}
|
||||
onPress={onPress}>
|
||||
<ButtonText>
|
||||
|
||||
@@ -117,7 +117,7 @@ function BirthdayInner({
|
||||
<View style={isWeb && [a.flex_row, a.justify_end]}>
|
||||
<Button
|
||||
label={hasChanged ? _(msg`Save birthday`) : _(msg`Done`)}
|
||||
size="medium"
|
||||
size="large"
|
||||
onPress={onSave}
|
||||
variant="solid"
|
||||
color="primary">
|
||||
|
||||
@@ -120,7 +120,7 @@ function EmbedDialogInner({
|
||||
label={_(msg`Copy code`)}
|
||||
color="primary"
|
||||
variant="solid"
|
||||
size="medium"
|
||||
size="large"
|
||||
onPress={() => {
|
||||
ref.current?.focus()
|
||||
ref.current?.setSelection(0, snippet.length)
|
||||
|
||||
@@ -83,7 +83,7 @@ export function EmbedConsentDialog({
|
||||
onPress={onShowAllPress}
|
||||
onAccessibilityEscape={control.close}
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Enable external media</Trans>
|
||||
@@ -95,7 +95,7 @@ export function EmbedConsentDialog({
|
||||
onPress={onShowPress}
|
||||
onAccessibilityEscape={control.close}
|
||||
color="secondary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
||||
@@ -106,7 +106,7 @@ export function EmbedConsentDialog({
|
||||
onAccessibilityEscape={control.close}
|
||||
onPress={onHidePress}
|
||||
color="secondary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="ghost">
|
||||
<ButtonText>
|
||||
<Trans>No thanks</Trans>
|
||||
|
||||
@@ -244,7 +244,7 @@ function ModalError({details, close}: {details?: string; close: () => void}) {
|
||||
label={_(msg`Close dialog`)}
|
||||
onPress={close}
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
|
||||
@@ -264,7 +264,7 @@ function DialogError({details}: {details?: string}) {
|
||||
label={_(msg`Close dialog`)}
|
||||
onPress={() => control.close()}
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
|
||||
@@ -319,7 +319,7 @@ function MutedWordsInner() {
|
||||
<Button
|
||||
disabled={isPending || !field}
|
||||
label={_(msg`Add mute word for configured settings`)}
|
||||
size="medium"
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
style={[]}
|
||||
|
||||
@@ -439,7 +439,7 @@ export function PostInteractionSettingsForm({
|
||||
onPress={onSave}
|
||||
onAccessibilityEscape={control.close}
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
variant="solid"
|
||||
style={a.mt_xl}>
|
||||
<ButtonText>{_(msg`Save`)}</ButtonText>
|
||||
|
||||
@@ -160,7 +160,7 @@ function DialogInner({
|
||||
<Button
|
||||
label={_(msg`Start chatting`)}
|
||||
accessibilityHint={_(msg`Close modal`)}
|
||||
size="medium"
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
onPress={() => control.close()}>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function DateField({
|
||||
<Button
|
||||
label={_(msg`Done`)}
|
||||
onPress={() => control.close()}
|
||||
size="medium"
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid">
|
||||
<ButtonText>
|
||||
|
||||
@@ -112,7 +112,7 @@ function Inner({control}: {control: DialogControlProps}) {
|
||||
onPress={() => control.close()}
|
||||
variant="solid"
|
||||
color={status === 'failure' ? 'secondary' : 'primary'}
|
||||
size="medium"
|
||||
size="large"
|
||||
style={{marginLeft: 'auto'}}>
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
@@ -124,7 +124,7 @@ function Inner({control}: {control: DialogControlProps}) {
|
||||
onPress={onPressResendEmail}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
disabled={sending}>
|
||||
<ButtonText>
|
||||
<Trans>Resend Email</Trans>
|
||||
|
||||
@@ -279,7 +279,7 @@ function AppealForm({
|
||||
testID="backBtn"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
size="large"
|
||||
onPress={onPressBack}
|
||||
label={_(msg`Back`)}>
|
||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||
@@ -288,7 +288,7 @@ function AppealForm({
|
||||
testID="submitBtn"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="medium"
|
||||
size="large"
|
||||
onPress={onSubmit}
|
||||
label={_(msg`Submit`)}>
|
||||
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||
|
||||
Reference in New Issue
Block a user