diff options
author | Minseo Lee <itoupluk427@gmail.com> | 2024-02-19 14:17:59 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-19 14:17:59 +0900 |
commit | 6d422bb583bf8946d92fe270b1fe5c760251f0cc (patch) | |
tree | 11871ebc34d48c5afcac1aa56464f8bffdc9f83a /src/components | |
parent | b3e6f0f29deae515d232d14f099e8c144d870f48 (diff) | |
parent | 7390863a1005eeadbb6dbdcbc47f9cc13298e101 (diff) | |
download | voidsky-6d422bb583bf8946d92fe270b1fe5c760251f0cc.tar.zst |
Merge branch 'main' into patch-3
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/Button.tsx | 100 | ||||
-rw-r--r-- | src/components/Link.tsx | 85 | ||||
-rw-r--r-- | src/components/Loader.tsx | 13 |
3 files changed, 130 insertions, 68 deletions
diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 68cee4374..e401bda2a 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -27,7 +27,7 @@ export type ButtonColor = | 'gradient_sunset' | 'gradient_nordic' | 'gradient_bonfire' -export type ButtonSize = 'small' | 'large' +export type ButtonSize = 'tiny' | 'small' | 'large' export type ButtonShape = 'round' | 'square' | 'default' export type VariantProps = { /** @@ -48,25 +48,32 @@ export type VariantProps = { shape?: ButtonShape } -export type ButtonProps = React.PropsWithChildren< - Pick<PressableProps, 'disabled' | 'onPress'> & - AccessibilityProps & - VariantProps & { - testID?: string - label: string - style?: StyleProp<ViewStyle> - } -> -export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean} +export type ButtonState = { + hovered: boolean + focused: boolean + pressed: boolean + disabled: boolean +} + +export type ButtonContext = VariantProps & ButtonState -const Context = React.createContext< +export type ButtonProps = Pick< + PressableProps, + 'disabled' | 'onPress' | 'testID' +> & + AccessibilityProps & VariantProps & { - hovered: boolean - focused: boolean - pressed: boolean - disabled: boolean + testID?: string + label: string + style?: StyleProp<ViewStyle> + children: + | React.ReactNode + | string + | ((context: ButtonContext) => React.ReactNode | string) } ->({ +export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean} + +const Context = React.createContext<VariantProps & ButtonState>({ hovered: false, focused: false, pressed: false, @@ -277,6 +284,8 @@ export function Button({ baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_md) } else if (size === 'small') { baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm) + } else if (size === 'tiny') { + baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs) } } else if (shape === 'round' || shape === 'square') { if (size === 'large') { @@ -287,12 +296,18 @@ export function Button({ } } else if (size === 'small') { baseStyles.push({height: 40, width: 40}) + } else if (size === 'tiny') { + baseStyles.push({height: 20, width: 20}) } if (shape === 'round') { baseStyles.push(a.rounded_full) } else if (shape === 'square') { - baseStyles.push(a.rounded_sm) + if (size === 'tiny') { + baseStyles.push(a.rounded_xs) + } else { + baseStyles.push(a.rounded_sm) + } } } @@ -338,7 +353,7 @@ export function Button({ } }, [variant, color]) - const context = React.useMemo( + const context = React.useMemo<ButtonContext>( () => ({ ...state, variant, @@ -349,6 +364,8 @@ export function Button({ [state, variant, color, size, disabled], ) + const flattenedBaseStyles = flatten(baseStyles) + return ( <Pressable role="button" @@ -362,15 +379,14 @@ export function Button({ disabled: disabled || false, }} style={[ - flatten(style), a.flex_row, a.align_center, a.justify_center, - a.overflow_hidden, a.justify_center, - ...baseStyles, + flattenedBaseStyles, ...(state.hovered || state.pressed ? hoverStyles : []), ...(state.focused ? focusStyles : []), + flatten(style), ]} onPressIn={onPressIn} onPressOut={onPressOut} @@ -379,21 +395,31 @@ export function Button({ onFocus={onFocus} onBlur={onBlur}> {variant === 'gradient' && ( - <LinearGradient - colors={ - state.hovered || state.pressed || state.focused - ? gradientHoverColors - : gradientColors - } - locations={gradientLocations} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[a.absolute, a.inset_0]} - /> + <View + style={[ + a.absolute, + a.inset_0, + a.overflow_hidden, + {borderRadius: flattenedBaseStyles.borderRadius}, + ]}> + <LinearGradient + colors={ + state.hovered || state.pressed || state.focused + ? gradientHoverColors + : gradientColors + } + locations={gradientLocations} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[a.absolute, a.inset_0]} + /> + </View> )} <Context.Provider value={context}> {typeof children === 'string' ? ( <ButtonText>{children}</ButtonText> + ) : typeof children === 'function' ? ( + children(context) ) : ( children )} @@ -493,6 +519,8 @@ export function useSharedButtonTextStyles() { if (size === 'large') { baseStyles.push(a.text_md, android({paddingBottom: 1})) + } else if (size === 'tiny') { + baseStyles.push(a.text_xs, android({paddingBottom: 1})) } else { baseStyles.push(a.text_sm, android({paddingBottom: 1})) } @@ -514,9 +542,11 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { export function ButtonIcon({ icon: Comp, position, + size: iconSize, }: { icon: React.ComponentType<SVGIconProps> position?: 'left' | 'right' + size?: SVGIconProps['size'] }) { const {size, disabled} = useButtonContext() const textStyles = useSharedButtonTextStyles() @@ -532,7 +562,9 @@ export function ButtonIcon({ }, ]}> <Comp - size={size === 'large' ? 'md' : 'sm'} + size={ + iconSize ?? (size === 'large' ? 'md' : size === 'tiny' ? 'xs' : 'sm') + } style={[{color: textStyles.color, pointerEvents: 'none'}]} /> </View> diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 763f07ca9..afd30b5ee 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -13,7 +13,7 @@ import {sanitizeUrl} from '@braintree/sanitize-url' import {useInteractionState} from '#/components/hooks/useInteractionState' import {isWeb} from '#/platform/detection' -import {useTheme, web, flatten, TextStyleProp} from '#/alf' +import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf' import {Button, ButtonProps} from '#/components/Button' import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' import { @@ -35,6 +35,13 @@ type BaseLinkProps = Pick< Parameters<typeof useLinkProps<AllNavigatorParams>>[0], 'to' > & { + testID?: string + + /** + * Label for a11y. Defaults to the href. + */ + label?: string + /** * The React Navigation `StackAction` to perform when the link is pressed. */ @@ -46,6 +53,18 @@ type BaseLinkProps = Pick< * Note: atm this only works for `InlineLink`s with a string child. */ warnOnMismatchingTextChild?: boolean + + /** + * Callback for when the link is pressed. + * + * DO NOT use this for navigation, that's what the `to` prop is for. + */ + onPress?: (e: GestureResponderEvent) => void + + /** + * Web-only attribute. Sets `download` attr on web. + */ + download?: string } export function useLink({ @@ -53,6 +72,7 @@ export function useLink({ displayText, action = 'push', warnOnMismatchingTextChild, + onPress: outerOnPress, }: BaseLinkProps & { displayText: string }) { @@ -66,6 +86,8 @@ export function useLink({ const onPress = React.useCallback( (e: GestureResponderEvent) => { + outerOnPress?.(e) + const requiresWarning = Boolean( warnOnMismatchingTextChild && displayText && @@ -132,6 +154,7 @@ export function useLink({ displayText, closeModal, openModal, + outerOnPress, ], ) @@ -143,16 +166,7 @@ export function useLink({ } export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> & - Omit<ButtonProps, 'style' | 'onPress' | 'disabled' | 'label'> & { - /** - * Label for a11y. Defaults to the href. - */ - label?: string - /** - * Web-only attribute. Sets `download` attr on web. - */ - download?: string - } + Omit<ButtonProps, 'onPress' | 'disabled' | 'label'> /** * A interactive element that renders as a `<a>` tag on the web. On mobile it @@ -166,6 +180,7 @@ export function Link({ children, to, action = 'push', + onPress: outerOnPress, download, ...rest }: LinkProps) { @@ -173,24 +188,26 @@ export function Link({ to, displayText: typeof children === 'string' ? children : '', action, + onPress: outerOnPress, }) return ( <Button label={href} {...rest} + style={[a.justify_start, flatten(rest.style)]} role="link" accessibilityRole="link" href={href} - onPress={onPress} + onPress={download ? undefined : onPress} {...web({ hrefAttrs: { - target: isExternal ? 'blank' : undefined, + target: download ? undefined : isExternal ? 'blank' : undefined, rel: isExternal ? 'noopener noreferrer' : undefined, download, }, dataSet: { - // default to no underline, apply this ourselves + // no underline, only `InlineLink` has underlines noUnderline: '1', }, })}> @@ -200,13 +217,7 @@ export function Link({ } export type InlineLinkProps = React.PropsWithChildren< - BaseLinkProps & - TextStyleProp & { - /** - * Label for a11y. Defaults to the href. - */ - label?: string - } + BaseLinkProps & TextStyleProp > export function InlineLink({ @@ -215,6 +226,8 @@ export function InlineLink({ action = 'push', warnOnMismatchingTextChild, style, + onPress: outerOnPress, + download, ...rest }: InlineLinkProps) { const t = useTheme() @@ -224,18 +237,25 @@ export function InlineLink({ displayText: stringChildren ? children : '', action, warnOnMismatchingTextChild, + onPress: outerOnPress, }) + const { + state: hovered, + onIn: onHoverIn, + onOut: onHoverOut, + } = useInteractionState() const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const { state: pressed, onIn: onPressIn, onOut: onPressOut, } = useInteractionState() + const flattenedStyle = flatten(style) return ( <TouchableWithoutFeedback accessibilityRole="button" - onPress={onPress} + onPress={download ? undefined : onPress} onPressIn={onPressIn} onPressOut={onPressOut} onFocus={onFocus} @@ -245,27 +265,28 @@ export function InlineLink({ {...rest} style={[ {color: t.palette.primary_500}, - (focused || pressed) && { + (hovered || focused || pressed) && { outline: 0, textDecorationLine: 'underline', - textDecorationColor: t.palette.primary_500, + textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, }, - flatten(style), + flattenedStyle, ]} role="link" + onMouseEnter={onHoverIn} + onMouseLeave={onHoverOut} accessibilityRole="link" href={href} {...web({ hrefAttrs: { - target: isExternal ? 'blank' : undefined, + target: download ? undefined : isExternal ? 'blank' : undefined, rel: isExternal ? 'noopener noreferrer' : undefined, + download, + }, + dataSet: { + // default to no underline, apply this ourselves + noUnderline: '1', }, - dataSet: stringChildren - ? {} - : { - // default to no underline, apply this ourselves - noUnderline: '1', - }, })}> {children} </Text> diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx index bbe4e2f75..b9f399f95 100644 --- a/src/components/Loader.tsx +++ b/src/components/Loader.tsx @@ -7,11 +7,12 @@ import Animated, { withTiming, } from 'react-native-reanimated' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme, flatten} from '#/alf' import {Props, useCommonSVGProps} from '#/components/icons/common' import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' export function Loader(props: Props) { + const t = useTheme() const common = useCommonSVGProps(props) const rotation = useSharedValue(0) @@ -35,7 +36,15 @@ export function Loader(props: Props) { {width: common.size, height: common.size}, animatedStyles, ]}> - <Icon {...props} style={[a.absolute, a.inset_0, props.style]} /> + <Icon + {...props} + style={[ + a.absolute, + a.inset_0, + t.atoms.text_contrast_high, + flatten(props.style), + ]} + /> </Animated.View> ) } |