about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/alf/atoms.ts36
-rw-r--r--src/components/Link.tsx85
-rw-r--r--src/view/screens/Storybook/Links.tsx7
3 files changed, 95 insertions, 33 deletions
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index bbf7e3243..f75e8ffe0 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -122,6 +122,9 @@ export const atoms = {
   flex_shrink: {
     flexShrink: 1,
   },
+  justify_start: {
+    justifyContent: 'flex-start',
+  },
   justify_center: {
     justifyContent: 'center',
   },
@@ -140,10 +143,31 @@ export const atoms = {
   align_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_left: {
+    textAlign: 'left',
+  },
   text_center: {
     textAlign: 'center',
   },
@@ -195,10 +219,16 @@ export const atoms = {
   font_bold: {
     fontWeight: tokens.fontWeight.semibold,
   },
+  italic: {
+    fontStyle: 'italic',
+  },
 
   /*
    * Border
    */
+  border_0: {
+    borderWidth: 0,
+  },
   border: {
     borderWidth: 1,
   },
@@ -208,6 +238,12 @@ export const atoms = {
   border_b: {
     borderBottomWidth: 1,
   },
+  border_l: {
+    borderLeftWidth: 1,
+  },
+  border_r: {
+    borderRightWidth: 1,
+  },
 
   /*
    * Shadow
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/view/screens/Storybook/Links.tsx b/src/view/screens/Storybook/Links.tsx
index 2828e74bf..3f1806906 100644
--- a/src/view/screens/Storybook/Links.tsx
+++ b/src/view/screens/Storybook/Links.tsx
@@ -19,11 +19,16 @@ export function Links() {
           style={[a.text_md]}>
           External
         </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>
         </InlineLink>
         <InlineLink
           to="https://bsky.social"
+          style={[a.text_md, t.atoms.text_contrast_low]}>
+          External with custom children
+        </InlineLink>
+        <InlineLink
+          to="https://bsky.social"
           warnOnMismatchingTextChild
           style={[a.text_lg]}>
           https://bsky.social