Link updates (#2890)

* Link updates, add atoms

* Update comments

* Support download

* Don't open new window for download
zio/stable
Eric Bailey 2024-02-16 13:25:07 -06:00 committed by GitHub
parent 0ff61e08e9
commit 1d729721e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 95 additions and 33 deletions

View File

@ -122,6 +122,9 @@ export const atoms = {
flex_shrink: { flex_shrink: {
flexShrink: 1, flexShrink: 1,
}, },
justify_start: {
justifyContent: 'flex-start',
},
justify_center: { justify_center: {
justifyContent: 'center', justifyContent: 'center',
}, },
@ -140,10 +143,31 @@ export const atoms = {
align_end: { align_end: {
alignItems: 'flex-end', alignItems: 'flex-end',
}, },
self_auto: {
alignSelf: 'auto',
},
self_start: {
alignSelf: 'flex-start',
},
self_end: {
alignSelf: 'flex-end',
},
self_center: {
alignSelf: 'center',
},
self_stretch: {
alignSelf: 'stretch',
},
self_baseline: {
alignSelf: 'baseline',
},
/* /*
* Text * Text
*/ */
text_left: {
textAlign: 'left',
},
text_center: { text_center: {
textAlign: 'center', textAlign: 'center',
}, },
@ -195,10 +219,16 @@ export const atoms = {
font_bold: { font_bold: {
fontWeight: tokens.fontWeight.semibold, fontWeight: tokens.fontWeight.semibold,
}, },
italic: {
fontStyle: 'italic',
},
/* /*
* Border * Border
*/ */
border_0: {
borderWidth: 0,
},
border: { border: {
borderWidth: 1, borderWidth: 1,
}, },
@ -208,6 +238,12 @@ export const atoms = {
border_b: { border_b: {
borderBottomWidth: 1, borderBottomWidth: 1,
}, },
border_l: {
borderLeftWidth: 1,
},
border_r: {
borderRightWidth: 1,
},
/* /*
* Shadow * Shadow

View File

@ -13,7 +13,7 @@ import {sanitizeUrl} from '@braintree/sanitize-url'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
import {isWeb} from '#/platform/detection' 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 {Button, ButtonProps} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import { import {
@ -35,6 +35,13 @@ type BaseLinkProps = Pick<
Parameters<typeof useLinkProps<AllNavigatorParams>>[0], Parameters<typeof useLinkProps<AllNavigatorParams>>[0],
'to' 'to'
> & { > & {
testID?: string
/**
* Label for a11y. Defaults to the href.
*/
label?: string
/** /**
* The React Navigation `StackAction` to perform when the link is pressed. * 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. * Note: atm this only works for `InlineLink`s with a string child.
*/ */
warnOnMismatchingTextChild?: boolean 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({ export function useLink({
@ -53,6 +72,7 @@ export function useLink({
displayText, displayText,
action = 'push', action = 'push',
warnOnMismatchingTextChild, warnOnMismatchingTextChild,
onPress: outerOnPress,
}: BaseLinkProps & { }: BaseLinkProps & {
displayText: string displayText: string
}) { }) {
@ -66,6 +86,8 @@ export function useLink({
const onPress = React.useCallback( const onPress = React.useCallback(
(e: GestureResponderEvent) => { (e: GestureResponderEvent) => {
outerOnPress?.(e)
const requiresWarning = Boolean( const requiresWarning = Boolean(
warnOnMismatchingTextChild && warnOnMismatchingTextChild &&
displayText && displayText &&
@ -132,6 +154,7 @@ export function useLink({
displayText, displayText,
closeModal, closeModal,
openModal, openModal,
outerOnPress,
], ],
) )
@ -143,16 +166,7 @@ export function useLink({
} }
export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> & export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> &
Omit<ButtonProps, 'style' | 'onPress' | 'disabled' | 'label'> & { Omit<ButtonProps, 'onPress' | 'disabled' | 'label'>
/**
* Label for a11y. Defaults to the href.
*/
label?: string
/**
* Web-only attribute. Sets `download` attr on web.
*/
download?: string
}
/** /**
* A interactive element that renders as a `<a>` tag on the web. On mobile it * A interactive element that renders as a `<a>` tag on the web. On mobile it
@ -166,6 +180,7 @@ export function Link({
children, children,
to, to,
action = 'push', action = 'push',
onPress: outerOnPress,
download, download,
...rest ...rest
}: LinkProps) { }: LinkProps) {
@ -173,24 +188,26 @@ export function Link({
to, to,
displayText: typeof children === 'string' ? children : '', displayText: typeof children === 'string' ? children : '',
action, action,
onPress: outerOnPress,
}) })
return ( return (
<Button <Button
label={href} label={href}
{...rest} {...rest}
style={[a.justify_start, flatten(rest.style)]}
role="link" role="link"
accessibilityRole="link" accessibilityRole="link"
href={href} href={href}
onPress={onPress} onPress={download ? undefined : onPress}
{...web({ {...web({
hrefAttrs: { hrefAttrs: {
target: isExternal ? 'blank' : undefined, target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined, rel: isExternal ? 'noopener noreferrer' : undefined,
download, download,
}, },
dataSet: { dataSet: {
// default to no underline, apply this ourselves // no underline, only `InlineLink` has underlines
noUnderline: '1', noUnderline: '1',
}, },
})}> })}>
@ -200,13 +217,7 @@ export function Link({
} }
export type InlineLinkProps = React.PropsWithChildren< export type InlineLinkProps = React.PropsWithChildren<
BaseLinkProps & BaseLinkProps & TextStyleProp
TextStyleProp & {
/**
* Label for a11y. Defaults to the href.
*/
label?: string
}
> >
export function InlineLink({ export function InlineLink({
@ -215,6 +226,8 @@ export function InlineLink({
action = 'push', action = 'push',
warnOnMismatchingTextChild, warnOnMismatchingTextChild,
style, style,
onPress: outerOnPress,
download,
...rest ...rest
}: InlineLinkProps) { }: InlineLinkProps) {
const t = useTheme() const t = useTheme()
@ -224,18 +237,25 @@ export function InlineLink({
displayText: stringChildren ? children : '', displayText: stringChildren ? children : '',
action, action,
warnOnMismatchingTextChild, warnOnMismatchingTextChild,
onPress: outerOnPress,
}) })
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const { const {
state: pressed, state: pressed,
onIn: onPressIn, onIn: onPressIn,
onOut: onPressOut, onOut: onPressOut,
} = useInteractionState() } = useInteractionState()
const flattenedStyle = flatten(style)
return ( return (
<TouchableWithoutFeedback <TouchableWithoutFeedback
accessibilityRole="button" accessibilityRole="button"
onPress={onPress} onPress={download ? undefined : onPress}
onPressIn={onPressIn} onPressIn={onPressIn}
onPressOut={onPressOut} onPressOut={onPressOut}
onFocus={onFocus} onFocus={onFocus}
@ -245,27 +265,28 @@ export function InlineLink({
{...rest} {...rest}
style={[ style={[
{color: t.palette.primary_500}, {color: t.palette.primary_500},
(focused || pressed) && { (hovered || focused || pressed) && {
outline: 0, outline: 0,
textDecorationLine: 'underline', textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500, textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
}, },
flatten(style), flattenedStyle,
]} ]}
role="link" role="link"
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
accessibilityRole="link" accessibilityRole="link"
href={href} href={href}
{...web({ {...web({
hrefAttrs: { hrefAttrs: {
target: isExternal ? 'blank' : undefined, target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : 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} {children}
</Text> </Text>

View File

@ -19,9 +19,14 @@ export function Links() {
style={[a.text_md]}> style={[a.text_md]}>
External External
</InlineLink> </InlineLink>
<InlineLink to="https://bsky.social" style={[a.text_md]}> <InlineLink to="https://bsky.social" style={[a.text_md, t.atoms.text]}>
<H3>External with custom children</H3> <H3>External with custom children</H3>
</InlineLink> </InlineLink>
<InlineLink
to="https://bsky.social"
style={[a.text_md, t.atoms.text_contrast_low]}>
External with custom children
</InlineLink>
<InlineLink <InlineLink
to="https://bsky.social" to="https://bsky.social"
warnOnMismatchingTextChild warnOnMismatchingTextChild