An ATproto social media client -- with an independent Appview.

chore: sync with upstream (#57)

* Send inferrable interactions to third-party feeds (#9094)

* Fix link crash (#9102)

* fix link crash

* fix link crash

* Log OTA errors properly (#9101)

* Log OTA errors properly

* filter out network errors

* don't send some "activity no longer available" errors (#9100)

* remove root sibling library (#9097)

* Catch errors on geolocation request, reduce Sentry logs (#9098)

* Nightly source-language update

* fix gap on profile (#9081)

* Update admonition component (#9068)

* update admonition component

* fix linting, adominition alighment

* design tweak

* edge cases for admonition, update storybook

* Update src/components/Admonition.tsx

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* fix mobile version

* change button style

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Button tweaks (#9106)

* Tweak button colors

* Semi bold only for tiny buttons

* [Web] Fix thread jumps (#9111)

* [Web] Fix thread jumps

* Comment formatting

---------

Co-authored-by: Eric Bailey <git@esb.lol>

* Add StackedButton component (#9086)

---------

Co-authored-by: dan <dan.abramov@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: pfrazee <1270099+pfrazee@users.noreply.github.com>
Co-authored-by: Chenyu <10610892+BinaryFiddler@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

+253 -104
+1
.eslintrc.js
··· 37 'Toast.Action', 38 'AgeAssuranceAdmonition', 39 'Span', 40 ], 41 impliedTextProps: [], 42 suggestedTextWrappers: {
··· 37 'Toast.Action', 38 'AgeAssuranceAdmonition', 39 'Span', 40 + 'StackedButton', 41 ], 42 impliedTextProps: [], 43 suggestedTextWrappers: {
+44 -24
src/components/Admonition.tsx
··· 3 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 import {Button as BaseButton, type ButtonProps} from '#/components/Button' 6 - import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' 7 - import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye' 8 - import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf' 9 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 10 import {Text as BaseText, type TextProps} from '#/components/Typography' 11 12 export const colors = { 13 - warning: { 14 - light: '#DFBC00', 15 - dark: '#BFAF1F', 16 - }, 17 } 18 19 type Context = { ··· 29 const t = useTheme() 30 const {type} = useContext(Context) 31 const Icon = { 32 - info: InfoIcon, 33 - tip: TipIcon, 34 warning: WarningIcon, 35 - error: ErrorIcon, 36 }[type] 37 const fill = { 38 info: t.atoms.text_contrast_medium.color, 39 tip: t.palette.primary_500, 40 - warning: colors.warning.light, 41 error: t.palette.negative_500, 42 }[type] 43 return <Icon fill={fill} size="md" /> 44 } 45 46 export function Text({ 47 children, 48 style, 49 ...rest 50 }: Pick<TextProps, 'children' | 'style'>) { 51 return ( 52 - <BaseText 53 - {...rest} 54 - style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}> 55 {children} 56 </BaseText> 57 ) ··· 60 export function Button({ 61 children, 62 ...props 63 - }: Omit<ButtonProps, 'size' | 'variant' | 'color'>) { 64 return ( 65 - <BaseButton size="tiny" variant="outline" color="secondary" {...props}> 66 {children} 67 </BaseButton> 68 ) 69 } 70 71 - export function Row({children}: {children: React.ReactNode}) { 72 return ( 73 - <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 74 {children} 75 </View> 76 ) ··· 88 const t = useTheme() 89 const {gtMobile} = useBreakpoints() 90 const borderColor = { 91 - info: t.atoms.border_contrast_low.borderColor, 92 - tip: t.atoms.border_contrast_low.borderColor, 93 - warning: t.atoms.border_contrast_low.borderColor, 94 - error: t.atoms.border_contrast_low.borderColor, 95 }[type] 96 return ( 97 <Context.Provider value={{type}}> 98 <View 99 style={[ 100 gtMobile ? a.p_md : a.p_sm, 101 a.rounded_sm, 102 a.border, 103 - t.atoms.bg_contrast_25, 104 {borderColor}, 105 style, 106 ]}> ··· 123 <Outer type={type} style={style}> 124 <Row> 125 <Icon /> 126 - <Text>{children}</Text> 127 </Row> 128 </Outer> 129 )
··· 3 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 import {Button as BaseButton, type ButtonProps} from '#/components/Button' 6 + import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' 7 + import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' 8 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 9 import {Text as BaseText, type TextProps} from '#/components/Typography' 10 11 export const colors = { 12 + warning: '#FFC404', 13 } 14 15 type Context = { ··· 25 const t = useTheme() 26 const {type} = useContext(Context) 27 const Icon = { 28 + info: CircleInfoIcon, 29 + tip: CircleInfoIcon, 30 warning: WarningIcon, 31 + error: CircleXIcon, 32 }[type] 33 const fill = { 34 info: t.atoms.text_contrast_medium.color, 35 tip: t.palette.primary_500, 36 + warning: colors.warning, 37 error: t.palette.negative_500, 38 }[type] 39 return <Icon fill={fill} size="md" /> 40 } 41 42 + export function Content({ 43 + children, 44 + style, 45 + ...rest 46 + }: { 47 + children: React.ReactNode 48 + style?: StyleProp<ViewStyle> 49 + }) { 50 + return ( 51 + <View 52 + style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]} 53 + {...rest}> 54 + {children} 55 + </View> 56 + ) 57 + } 58 + 59 export function Text({ 60 children, 61 style, 62 ...rest 63 }: Pick<TextProps, 'children' | 'style'>) { 64 return ( 65 + <BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}> 66 {children} 67 </BaseText> 68 ) ··· 71 export function Button({ 72 children, 73 ...props 74 + }: Omit<ButtonProps, 'size' | 'variant'>) { 75 return ( 76 + <BaseButton size="tiny" {...props}> 77 {children} 78 </BaseButton> 79 ) 80 } 81 82 + export function Row({ 83 + children, 84 + style, 85 + }: { 86 + children: React.ReactNode 87 + style?: StyleProp<ViewStyle> 88 + }) { 89 return ( 90 + <View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}> 91 {children} 92 </View> 93 ) ··· 105 const t = useTheme() 106 const {gtMobile} = useBreakpoints() 107 const borderColor = { 108 + info: t.atoms.border_contrast_high.borderColor, 109 + tip: t.palette.primary_500, 110 + warning: colors.warning, 111 + error: t.palette.negative_500, 112 }[type] 113 return ( 114 <Context.Provider value={{type}}> 115 <View 116 style={[ 117 gtMobile ? a.p_md : a.p_sm, 118 + a.p_md, 119 a.rounded_sm, 120 a.border, 121 + t.atoms.bg, 122 {borderColor}, 123 style, 124 ]}> ··· 141 <Outer type={type} style={style}> 142 <Row> 143 <Icon /> 144 + <Content> 145 + <Text>{children}</Text> 146 + </Content> 147 </Row> 148 </Outer> 149 )
+53 -41
src/components/Button.tsx
··· 274 } else if (color === 'primary_subtle') { 275 if (!disabled) { 276 baseStyles.push({ 277 - backgroundColor: select(t.name, { 278 - light: t.palette.primary_50, 279 - dim: t.palette.primary_100, 280 - dark: t.palette.primary_100, 281 - }), 282 }) 283 hoverStyles.push({ 284 - backgroundColor: select(t.name, { 285 - light: t.palette.primary_100, 286 - dim: t.palette.primary_200, 287 - dark: t.palette.primary_200, 288 - }), 289 }) 290 } else { 291 baseStyles.push({ ··· 299 } else if (color === 'negative_subtle') { 300 if (!disabled) { 301 baseStyles.push({ 302 - backgroundColor: select(t.name, { 303 - light: t.palette.negative_50, 304 - dim: t.palette.negative_100, 305 - dark: t.palette.negative_100, 306 - }), 307 }) 308 hoverStyles.push({ 309 - backgroundColor: select(t.name, { 310 - light: t.palette.negative_100, 311 - dim: t.palette.negative_200, 312 - dark: t.palette.negative_200, 313 - }), 314 }) 315 } else { 316 baseStyles.push({ ··· 626 } else if (color === 'primary_subtle') { 627 if (!disabled) { 628 baseStyles.push({ 629 - color: select(t.name, { 630 - light: t.palette.primary_600, 631 - dim: t.palette.primary_800, 632 - dark: t.palette.primary_800, 633 - }), 634 }) 635 } else { 636 baseStyles.push({ 637 - color: select(t.name, { 638 - light: t.palette.primary_200, 639 - dim: t.palette.primary_200, 640 - dark: t.palette.primary_200, 641 - }), 642 }) 643 } 644 } else if (color === 'negative_subtle') { 645 if (!disabled) { 646 baseStyles.push({ 647 - color: select(t.name, { 648 - light: t.palette.negative_600, 649 - dim: t.palette.negative_800, 650 - dark: t.palette.negative_800, 651 - }), 652 }) 653 } else { 654 baseStyles.push({ 655 - color: select(t.name, { 656 - light: t.palette.negative_200, 657 - dim: t.palette.negative_200, 658 - dark: t.palette.negative_200, 659 - }), 660 }) 661 } 662 } ··· 763 } else if (size === 'small') { 764 baseStyles.push(a.text_sm, a.leading_snug, a.font_medium) 765 } else if (size === 'tiny') { 766 - baseStyles.push(a.text_xs, a.leading_snug, a.font_medium) 767 } 768 769 return StyleSheet.flatten(baseStyles) ··· 877 </View> 878 ) 879 }
··· 274 } else if (color === 'primary_subtle') { 275 if (!disabled) { 276 baseStyles.push({ 277 + backgroundColor: t.palette.primary_50, 278 }) 279 hoverStyles.push({ 280 + backgroundColor: t.palette.primary_100, 281 }) 282 } else { 283 baseStyles.push({ ··· 291 } else if (color === 'negative_subtle') { 292 if (!disabled) { 293 baseStyles.push({ 294 + backgroundColor: t.palette.negative_50, 295 }) 296 hoverStyles.push({ 297 + backgroundColor: t.palette.negative_100, 298 }) 299 } else { 300 baseStyles.push({ ··· 610 } else if (color === 'primary_subtle') { 611 if (!disabled) { 612 baseStyles.push({ 613 + color: t.palette.primary_600, 614 }) 615 } else { 616 baseStyles.push({ 617 + color: t.palette.primary_200, 618 }) 619 } 620 } else if (color === 'negative_subtle') { 621 if (!disabled) { 622 baseStyles.push({ 623 + color: t.palette.negative_600, 624 }) 625 } else { 626 baseStyles.push({ 627 + color: t.palette.negative_200, 628 }) 629 } 630 } ··· 731 } else if (size === 'small') { 732 baseStyles.push(a.text_sm, a.leading_snug, a.font_medium) 733 } else if (size === 'tiny') { 734 + baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold) 735 } 736 737 return StyleSheet.flatten(baseStyles) ··· 845 </View> 846 ) 847 } 848 + 849 + export type StackedButtonProps = Omit< 850 + ButtonProps, 851 + keyof VariantProps | 'children' 852 + > & 853 + Pick<VariantProps, 'color'> & { 854 + children: React.ReactNode 855 + icon: React.ComponentType<SVGIconProps> 856 + } 857 + 858 + export function StackedButton({children, ...props}: StackedButtonProps) { 859 + return ( 860 + <Button 861 + {...props} 862 + size="tiny" 863 + style={[ 864 + a.flex_col, 865 + { 866 + height: 72, 867 + paddingHorizontal: 16, 868 + borderRadius: 20, 869 + gap: 4, 870 + }, 871 + props.style, 872 + ]}> 873 + <StackedButtonInnerText icon={props.icon}> 874 + {children} 875 + </StackedButtonInnerText> 876 + </Button> 877 + ) 878 + } 879 + 880 + function StackedButtonInnerText({ 881 + children, 882 + icon: Icon, 883 + }: Pick<StackedButtonProps, 'icon' | 'children'>) { 884 + const textStyles = useSharedButtonTextStyles() 885 + return ( 886 + <> 887 + <Icon width={24} fill={textStyles.color} /> 888 + <ButtonText>{children}</ButtonText> 889 + </> 890 + ) 891 + }
+3 -1
src/components/moderation/LabelsOnMeDialog.tsx
··· 32 33 export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { 34 return ( 35 - <Dialog.Outer control={props.control}> 36 <Dialog.Handle /> 37 <LabelsOnMeDialogInner {...props} /> 38 </Dialog.Outer>
··· 32 33 export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { 34 return ( 35 + <Dialog.Outer 36 + control={props.control} 37 + nativeOptions={{preventExpansion: true}}> 38 <Dialog.Handle /> 39 <LabelsOnMeDialogInner {...props} /> 40 </Dialog.Outer>
+3 -1
src/components/moderation/ModerationDetailsDialog.tsx
··· 24 25 export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { 26 return ( 27 - <Dialog.Outer control={props.control}> 28 <Dialog.Handle /> 29 <ModerationDetailsDialogInner {...props} /> 30 </Dialog.Outer>
··· 24 25 export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { 26 return ( 27 + <Dialog.Outer 28 + control={props.control} 29 + nativeOptions={{preventExpansion: true}}> 30 <Dialog.Handle /> 31 <ModerationDetailsDialogInner {...props} /> 32 </Dialog.Outer>
+2 -1
src/components/moderation/ProfileHeaderAlerts.tsx
··· 6 7 export function ProfileHeaderAlerts({ 8 moderation, 9 }: { 10 moderation: ModerationDecision 11 style?: StyleProp<ViewStyle> ··· 16 } 17 18 return ( 19 - <Pills.Row size="lg"> 20 {modui.alerts.filter(unique).map(cause => ( 21 <Pills.Label 22 size="lg"
··· 6 7 export function ProfileHeaderAlerts({ 8 moderation, 9 + style, 10 }: { 11 moderation: ModerationDecision 12 style?: StyleProp<ViewStyle> ··· 17 } 18 19 return ( 20 + <Pills.Row size="lg" style={style}> 21 {modui.alerts.filter(unique).map(cause => ( 22 <Pills.Label 23 size="lg"
+6 -3
src/components/moderation/ReportDialog/index.tsx
··· 219 <Admonition.Outer type="error"> 220 <Admonition.Row> 221 <Admonition.Icon /> 222 - <Admonition.Text> 223 - <Trans>Something went wrong, please try again</Trans> 224 - </Admonition.Text> 225 <Admonition.Button 226 label={_(msg`Retry loading report options`)} 227 onPress={() => refetchLabelers()}> 228 <ButtonText>
··· 219 <Admonition.Outer type="error"> 220 <Admonition.Row> 221 <Admonition.Icon /> 222 + <Admonition.Content> 223 + <Admonition.Text> 224 + <Trans>Something went wrong, please try again</Trans> 225 + </Admonition.Text> 226 + </Admonition.Content> 227 <Admonition.Button 228 + color="negative_subtle" 229 label={_(msg`Retry loading report options`)} 230 onPress={() => refetchLabelers()}> 231 <ButtonText>
+1 -1
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 620 621 if (!isBackdated) return null 622 623 - const orange = t.name === 'light' ? colors.warning.dark : colors.warning.light 624 625 return ( 626 <>
··· 620 621 if (!isBackdated) return null 622 623 + const orange = colors.warning 624 625 return ( 626 <>
+13 -12
src/screens/PostThread/index.tsx
··· 148 */ 149 const shouldHandleScroll = useRef(true) 150 /** 151 - * Called any time the content size of the list changes, _just_ before paint. 152 * 153 * We want this to fire every time we change params (which will reset 154 * `deferParents` via `onLayout` on the anchor post, due to the key change), ··· 193 * will give us a _positive_ offset, which will scroll the anchor post 194 * back _up_ to the top of the screen. 195 */ 196 - list.scrollToOffset({ 197 - offset: anchorOffsetTop - headerHeight, 198 - }) 199 200 /* 201 - * After the second pass, `deferParents` will be `false`, and we need 202 - * to ensure this doesn't run again until scroll handling is requested 203 - * again via `shouldHandleScroll.current === true` and a params 204 - * change via `prepareForParamsUpdate`. 205 * 206 * The `isRoot` here is needed because if we're looking at the anchor 207 * post, this handler will not fire after `deferParents` is set to 208 * `false`, since there are no parents to render above it. In this case, 209 - * we want to make sure `shouldHandleScroll` is set to `false` so that 210 - * subsequent size changes unrelated to a params change (like pagination) 211 - * do not affect scroll. 212 */ 213 - if (!deferParents || isRoot) shouldHandleScroll.current = false 214 } 215 }) 216
··· 148 */ 149 const shouldHandleScroll = useRef(true) 150 /** 151 + * Called any time the content size of the list changes. Could be a fresh 152 + * render, items being added to the list, or any resize that changes the 153 + * scrollable size of the content. 154 * 155 * We want this to fire every time we change params (which will reset 156 * `deferParents` via `onLayout` on the anchor post, due to the key change), ··· 195 * will give us a _positive_ offset, which will scroll the anchor post 196 * back _up_ to the top of the screen. 197 */ 198 + const offset = anchorOffsetTop - headerHeight 199 + list.scrollToOffset({offset}) 200 201 /* 202 + * After we manage to do a positive adjustment, we need to ensure this 203 + * doesn't run again until scroll handling is requested again via 204 + * `shouldHandleScroll.current === true` and a params change via 205 + * `prepareForParamsUpdate`. 206 * 207 * The `isRoot` here is needed because if we're looking at the anchor 208 * post, this handler will not fire after `deferParents` is set to 209 * `false`, since there are no parents to render above it. In this case, 210 + * we want to make sure `shouldHandleScroll` is set to `false` right away 211 + * so that subsequent size changes unrelated to a params change (like 212 + * pagination) do not affect scroll. 213 */ 214 + if (offset > 0 || isRoot) shouldHandleScroll.current = false 215 } 216 }) 217
+23 -11
src/screens/Profile/Header/Shell.tsx
··· 209 210 {children} 211 212 - {!isPlaceholderProfile && ( 213 - <View 214 - style={[a.px_lg, a.pt_xs, a.pb_sm]} 215 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 216 - {isMe ? ( 217 - <LabelsOnMe type="account" labels={profile.labels} /> 218 - ) : ( 219 - <ProfileHeaderAlerts moderation={moderation} /> 220 - )} 221 - </View> 222 - )} 223 224 <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> 225 <TouchableWithoutFeedback
··· 209 210 {children} 211 212 + {!isPlaceholderProfile && 213 + (isMe ? ( 214 + <LabelsOnMe 215 + type="account" 216 + labels={profile.labels} 217 + style={[ 218 + a.px_lg, 219 + a.pt_xs, 220 + a.pb_sm, 221 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 222 + ]} 223 + /> 224 + ) : ( 225 + <ProfileHeaderAlerts 226 + moderation={moderation} 227 + style={[ 228 + a.px_lg, 229 + a.pt_xs, 230 + a.pb_sm, 231 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 232 + ]} 233 + /> 234 + ))} 235 236 <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> 237 <TouchableWithoutFeedback
+1 -1
src/screens/Settings/AppPasswords.tsx
··· 195 </View> 196 {appPassword.privileged && ( 197 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 198 - <WarningIcon style={[{color: colors.warning[t.scheme]}]} /> 199 <Text style={t.atoms.text_contrast_high}> 200 <Trans>Allows access to direct messages</Trans> 201 </Text>
··· 195 </View> 196 {appPassword.privileged && ( 197 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 198 + <WarningIcon style={[{color: colors.warning}]} /> 199 <Text style={t.atoms.text_contrast_high}> 200 <Trans>Allows access to direct messages</Trans> 201 </Text>
+2 -2
src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx
··· 134 <Admonition.Outer type="tip"> 135 <Admonition.Row> 136 <Admonition.Icon /> 137 - <View style={[a.flex_1, a.gap_sm]}> 138 <Admonition.Text> 139 <Trans> 140 Enable notifications for an account by visiting their ··· 163 . 164 </Trans> 165 </Admonition.Text> 166 - </View> 167 </Admonition.Row> 168 </Admonition.Outer> 169 ) : (
··· 134 <Admonition.Outer type="tip"> 135 <Admonition.Row> 136 <Admonition.Icon /> 137 + <Admonition.Content> 138 <Admonition.Text> 139 <Trans> 140 Enable notifications for an account by visiting their ··· 163 . 164 </Trans> 165 </Admonition.Text> 166 + </Admonition.Content> 167 </Admonition.Row> 168 </Admonition.Outer> 169 ) : (
+2 -3
src/screens/Settings/PrivacyAndSecuritySettings.tsx
··· 1 - import {View} from 'react-native' 2 import {type AppBskyNotificationDeclaration} from '@atproto/api' 3 import {msg, Trans} from '@lingui/macro' 4 import {useLingui} from '@lingui/react' ··· 112 <Admonition.Outer type="tip" style={[a.flex_1]}> 113 <Admonition.Row> 114 <Admonition.Icon /> 115 - <View style={[a.flex_1, a.gap_sm]}> 116 <Admonition.Text> 117 <Trans> 118 Note: Bluesky is an open and public network. This setting ··· 131 <Trans>Learn more about what is public on Bluesky.</Trans> 132 </InlineLinkText> 133 </Admonition.Text> 134 - </View> 135 </Admonition.Row> 136 </Admonition.Outer> 137 </SettingsList.Item>
··· 1 import {type AppBskyNotificationDeclaration} from '@atproto/api' 2 import {msg, Trans} from '@lingui/macro' 3 import {useLingui} from '@lingui/react' ··· 111 <Admonition.Outer type="tip" style={[a.flex_1]}> 112 <Admonition.Row> 113 <Admonition.Icon /> 114 + <Admonition.Content> 115 <Admonition.Text> 116 <Trans> 117 Note: Bluesky is an open and public network. This setting ··· 130 <Trans>Learn more about what is public on Bluesky.</Trans> 131 </InlineLinkText> 132 </Admonition.Text> 133 + </Admonition.Content> 134 </Admonition.Row> 135 </Admonition.Outer> 136 </SettingsList.Item>
+74 -3
src/view/screens/Storybook/Admonitions.tsx
··· 1 - import {View} from 'react-native' 2 3 - import {atoms as a} from '#/alf' 4 - import {Admonition} from '#/components/Admonition' 5 import {InlineLinkText} from '#/components/Link' 6 import {H1} from '#/components/Typography' 7 8 export function Admonitions() { 9 return ( 10 <View style={[a.gap_md]}> 11 <H1>Admonitions</H1> ··· 30 <Admonition type="error"> 31 The quick brown fox jumps over the lazy dog. 32 </Admonition> 33 </View> 34 ) 35 }
··· 1 + import {Text as RNText, View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 5 + import {atoms as a, useTheme} from '#/alf' 6 + import { 7 + Admonition, 8 + Button as AdmonitionButton, 9 + Content as AdmonitionContent, 10 + Icon as AdmonitionIcon, 11 + Outer as AdmonitionOuter, 12 + Row as AdmonitionRow, 13 + Text as AdmonitionText, 14 + } from '#/components/Admonition' 15 + import {ButtonIcon, ButtonText} from '#/components/Button' 16 + import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise' 17 + import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging' 18 import {InlineLinkText} from '#/components/Link' 19 import {H1} from '#/components/Typography' 20 21 export function Admonitions() { 22 + const {_} = useLingui() 23 + const t = useTheme() 24 + 25 return ( 26 <View style={[a.gap_md]}> 27 <H1>Admonitions</H1> ··· 46 <Admonition type="error"> 47 The quick brown fox jumps over the lazy dog. 48 </Admonition> 49 + 50 + <AdmonitionOuter type="error"> 51 + <AdmonitionRow> 52 + <AdmonitionIcon /> 53 + <AdmonitionContent> 54 + <AdmonitionText> 55 + <Trans>Something went wrong, please try again</Trans> 56 + </AdmonitionText> 57 + </AdmonitionContent> 58 + <AdmonitionButton 59 + color="negative_subtle" 60 + label={_(msg`Retry loading report options`)} 61 + onPress={() => {}}> 62 + <ButtonText> 63 + <Trans>Retry</Trans> 64 + </ButtonText> 65 + <ButtonIcon icon={Retry} /> 66 + </AdmonitionButton> 67 + </AdmonitionRow> 68 + </AdmonitionOuter> 69 + 70 + <AdmonitionOuter type="tip"> 71 + <AdmonitionRow> 72 + <AdmonitionIcon /> 73 + <AdmonitionContent> 74 + <AdmonitionText> 75 + <Trans> 76 + Enable notifications for an account by visiting their profile 77 + and pressing the{' '} 78 + <RNText style={[a.font_bold, t.atoms.text_contrast_high]}> 79 + bell icon 80 + </RNText>{' '} 81 + <BellRingingFilledIcon 82 + size="xs" 83 + style={t.atoms.text_contrast_high} 84 + /> 85 + . 86 + </Trans> 87 + </AdmonitionText> 88 + <AdmonitionText> 89 + <Trans> 90 + If you want to restrict who can receive notifications for your 91 + account's activity, you can change this in{' '} 92 + <InlineLinkText 93 + label={_(msg`Privacy and Security settings`)} 94 + to={{screen: 'ActivityPrivacySettings'}} 95 + style={[a.font_bold]}> 96 + Settings &rarr; Privacy and Security 97 + </InlineLinkText> 98 + . 99 + </Trans> 100 + </AdmonitionText> 101 + </AdmonitionContent> 102 + </AdmonitionRow> 103 + </AdmonitionOuter> 104 </View> 105 ) 106 }
+25
src/view/screens/Storybook/Buttons.tsx
··· 8 ButtonIcon, 9 type ButtonSize, 10 ButtonText, 11 } from '#/components/Button' 12 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 13 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' ··· 17 return ( 18 <View style={[a.gap_md]}> 19 <Text style={[a.font_heavy, a.text_5xl]}>Buttons</Text> 20 21 {[ 22 'primary',
··· 8 ButtonIcon, 9 type ButtonSize, 10 ButtonText, 11 + StackedButton, 12 } from '#/components/Button' 13 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 14 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' ··· 18 return ( 19 <View style={[a.gap_md]}> 20 <Text style={[a.font_heavy, a.text_5xl]}>Buttons</Text> 21 + 22 + <View style={[a.flex_row, a.gap_md, a.align_start, {maxWidth: 350}]}> 23 + <StackedButton 24 + label="stacked" 25 + icon={Globe} 26 + color="secondary" 27 + style={[a.flex_1]}> 28 + Bop it 29 + </StackedButton> 30 + <StackedButton 31 + label="stacked" 32 + icon={Globe} 33 + color="negative_subtle" 34 + style={[a.flex_1]}> 35 + Twist it 36 + </StackedButton> 37 + <StackedButton 38 + label="stacked" 39 + icon={Globe} 40 + color="primary" 41 + style={[a.flex_1]}> 42 + Pull it 43 + </StackedButton> 44 + </View> 45 46 {[ 47 'primary',