Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Link updates (#2890)

* Link updates, add atoms

* Update comments

* Support download

* Don't open new window for download

authored by

Eric Bailey and committed by
GitHub
1d729721 0ff61e08

+95 -33
+36
src/alf/atoms.ts
··· 122 122 flex_shrink: { 123 123 flexShrink: 1, 124 124 }, 125 + justify_start: { 126 + justifyContent: 'flex-start', 127 + }, 125 128 justify_center: { 126 129 justifyContent: 'center', 127 130 }, ··· 140 143 align_end: { 141 144 alignItems: 'flex-end', 142 145 }, 146 + self_auto: { 147 + alignSelf: 'auto', 148 + }, 149 + self_start: { 150 + alignSelf: 'flex-start', 151 + }, 152 + self_end: { 153 + alignSelf: 'flex-end', 154 + }, 155 + self_center: { 156 + alignSelf: 'center', 157 + }, 158 + self_stretch: { 159 + alignSelf: 'stretch', 160 + }, 161 + self_baseline: { 162 + alignSelf: 'baseline', 163 + }, 143 164 144 165 /* 145 166 * Text 146 167 */ 168 + text_left: { 169 + textAlign: 'left', 170 + }, 147 171 text_center: { 148 172 textAlign: 'center', 149 173 }, ··· 195 219 font_bold: { 196 220 fontWeight: tokens.fontWeight.semibold, 197 221 }, 222 + italic: { 223 + fontStyle: 'italic', 224 + }, 198 225 199 226 /* 200 227 * Border 201 228 */ 229 + border_0: { 230 + borderWidth: 0, 231 + }, 202 232 border: { 203 233 borderWidth: 1, 204 234 }, ··· 207 237 }, 208 238 border_b: { 209 239 borderBottomWidth: 1, 240 + }, 241 + border_l: { 242 + borderLeftWidth: 1, 243 + }, 244 + border_r: { 245 + borderRightWidth: 1, 210 246 }, 211 247 212 248 /*
+53 -32
src/components/Link.tsx
··· 13 13 14 14 import {useInteractionState} from '#/components/hooks/useInteractionState' 15 15 import {isWeb} from '#/platform/detection' 16 - import {useTheme, web, flatten, TextStyleProp} from '#/alf' 16 + import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf' 17 17 import {Button, ButtonProps} from '#/components/Button' 18 18 import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' 19 19 import { ··· 35 35 Parameters<typeof useLinkProps<AllNavigatorParams>>[0], 36 36 'to' 37 37 > & { 38 + testID?: string 39 + 40 + /** 41 + * Label for a11y. Defaults to the href. 42 + */ 43 + label?: string 44 + 38 45 /** 39 46 * The React Navigation `StackAction` to perform when the link is pressed. 40 47 */ ··· 46 53 * Note: atm this only works for `InlineLink`s with a string child. 47 54 */ 48 55 warnOnMismatchingTextChild?: boolean 56 + 57 + /** 58 + * Callback for when the link is pressed. 59 + * 60 + * DO NOT use this for navigation, that's what the `to` prop is for. 61 + */ 62 + onPress?: (e: GestureResponderEvent) => void 63 + 64 + /** 65 + * Web-only attribute. Sets `download` attr on web. 66 + */ 67 + download?: string 49 68 } 50 69 51 70 export function useLink({ ··· 53 72 displayText, 54 73 action = 'push', 55 74 warnOnMismatchingTextChild, 75 + onPress: outerOnPress, 56 76 }: BaseLinkProps & { 57 77 displayText: string 58 78 }) { ··· 66 86 67 87 const onPress = React.useCallback( 68 88 (e: GestureResponderEvent) => { 89 + outerOnPress?.(e) 90 + 69 91 const requiresWarning = Boolean( 70 92 warnOnMismatchingTextChild && 71 93 displayText && ··· 132 154 displayText, 133 155 closeModal, 134 156 openModal, 157 + outerOnPress, 135 158 ], 136 159 ) 137 160 ··· 143 166 } 144 167 145 168 export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> & 146 - Omit<ButtonProps, 'style' | 'onPress' | 'disabled' | 'label'> & { 147 - /** 148 - * Label for a11y. Defaults to the href. 149 - */ 150 - label?: string 151 - /** 152 - * Web-only attribute. Sets `download` attr on web. 153 - */ 154 - download?: string 155 - } 169 + Omit<ButtonProps, 'onPress' | 'disabled' | 'label'> 156 170 157 171 /** 158 172 * A interactive element that renders as a `<a>` tag on the web. On mobile it ··· 166 180 children, 167 181 to, 168 182 action = 'push', 183 + onPress: outerOnPress, 169 184 download, 170 185 ...rest 171 186 }: LinkProps) { ··· 173 188 to, 174 189 displayText: typeof children === 'string' ? children : '', 175 190 action, 191 + onPress: outerOnPress, 176 192 }) 177 193 178 194 return ( 179 195 <Button 180 196 label={href} 181 197 {...rest} 198 + style={[a.justify_start, flatten(rest.style)]} 182 199 role="link" 183 200 accessibilityRole="link" 184 201 href={href} 185 - onPress={onPress} 202 + onPress={download ? undefined : onPress} 186 203 {...web({ 187 204 hrefAttrs: { 188 - target: isExternal ? 'blank' : undefined, 205 + target: download ? undefined : isExternal ? 'blank' : undefined, 189 206 rel: isExternal ? 'noopener noreferrer' : undefined, 190 207 download, 191 208 }, 192 209 dataSet: { 193 - // default to no underline, apply this ourselves 210 + // no underline, only `InlineLink` has underlines 194 211 noUnderline: '1', 195 212 }, 196 213 })}> ··· 200 217 } 201 218 202 219 export type InlineLinkProps = React.PropsWithChildren< 203 - BaseLinkProps & 204 - TextStyleProp & { 205 - /** 206 - * Label for a11y. Defaults to the href. 207 - */ 208 - label?: string 209 - } 220 + BaseLinkProps & TextStyleProp 210 221 > 211 222 212 223 export function InlineLink({ ··· 215 226 action = 'push', 216 227 warnOnMismatchingTextChild, 217 228 style, 229 + onPress: outerOnPress, 230 + download, 218 231 ...rest 219 232 }: InlineLinkProps) { 220 233 const t = useTheme() ··· 224 237 displayText: stringChildren ? children : '', 225 238 action, 226 239 warnOnMismatchingTextChild, 240 + onPress: outerOnPress, 227 241 }) 242 + const { 243 + state: hovered, 244 + onIn: onHoverIn, 245 + onOut: onHoverOut, 246 + } = useInteractionState() 228 247 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 229 248 const { 230 249 state: pressed, 231 250 onIn: onPressIn, 232 251 onOut: onPressOut, 233 252 } = useInteractionState() 253 + const flattenedStyle = flatten(style) 234 254 235 255 return ( 236 256 <TouchableWithoutFeedback 237 257 accessibilityRole="button" 238 - onPress={onPress} 258 + onPress={download ? undefined : onPress} 239 259 onPressIn={onPressIn} 240 260 onPressOut={onPressOut} 241 261 onFocus={onFocus} ··· 245 265 {...rest} 246 266 style={[ 247 267 {color: t.palette.primary_500}, 248 - (focused || pressed) && { 268 + (hovered || focused || pressed) && { 249 269 outline: 0, 250 270 textDecorationLine: 'underline', 251 - textDecorationColor: t.palette.primary_500, 271 + textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, 252 272 }, 253 - flatten(style), 273 + flattenedStyle, 254 274 ]} 255 275 role="link" 276 + onMouseEnter={onHoverIn} 277 + onMouseLeave={onHoverOut} 256 278 accessibilityRole="link" 257 279 href={href} 258 280 {...web({ 259 281 hrefAttrs: { 260 - target: isExternal ? 'blank' : undefined, 282 + target: download ? undefined : isExternal ? 'blank' : undefined, 261 283 rel: isExternal ? 'noopener noreferrer' : undefined, 284 + download, 262 285 }, 263 - dataSet: stringChildren 264 - ? {} 265 - : { 266 - // default to no underline, apply this ourselves 267 - noUnderline: '1', 268 - }, 286 + dataSet: { 287 + // default to no underline, apply this ourselves 288 + noUnderline: '1', 289 + }, 269 290 })}> 270 291 {children} 271 292 </Text>
+6 -1
src/view/screens/Storybook/Links.tsx
··· 19 19 style={[a.text_md]}> 20 20 External 21 21 </InlineLink> 22 - <InlineLink to="https://bsky.social" style={[a.text_md]}> 22 + <InlineLink to="https://bsky.social" style={[a.text_md, t.atoms.text]}> 23 23 <H3>External with custom children</H3> 24 + </InlineLink> 25 + <InlineLink 26 + to="https://bsky.social" 27 + style={[a.text_md, t.atoms.text_contrast_low]}> 28 + External with custom children 24 29 </InlineLink> 25 30 <InlineLink 26 31 to="https://bsky.social"