diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index a2d81fc96a..198e3baa76 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -215,6 +215,7 @@
+
{% block html_head_extra -%}{%- endblock %}
diff --git a/package.json b/package.json
index 837a8d0e45..4a3a2a7dc1 100644
--- a/package.json
+++ b/package.json
@@ -123,7 +123,6 @@
"js-sha256": "^0.9.0",
"jwt-decode": "^4.0.0",
"lande": "^1.0.10",
- "libphonenumber-js": "^1.10.53",
"lodash.chunk": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
@@ -137,7 +136,7 @@
"mobx": "^6.6.1",
"mobx-react-lite": "^3.4.0",
"mobx-utils": "^6.0.6",
- "nanoid": "^5.0.2",
+ "nanoid": "^5.0.5",
"normalize-url": "^8.0.0",
"patch-package": "^6.5.1",
"postinstall-postinstall": "^2.1.0",
@@ -164,6 +163,7 @@
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-svg": "14.1.0",
+ "react-native-ui-text-view": "link:./modules/react-native-ui-text-view",
"react-native-url-polyfill": "^1.3.0",
"react-native-uuid": "^2.0.1",
"react-native-version-number": "^0.3.6",
@@ -178,8 +178,7 @@
"tlds": "^1.234.0",
"use-deep-compare": "^1.1.0",
"zeego": "^1.6.2",
- "zod": "^3.20.2",
- "react-native-ui-text-view": "link:./modules/react-native-ui-text-view"
+ "zod": "^3.20.2"
},
"devDependencies": {
"@atproto/dev-env": "^0.2.28",
diff --git a/src/Splash.tsx b/src/Splash.tsx
index 80d0a66e75..b2381f9232 100644
--- a/src/Splash.tsx
+++ b/src/Splash.tsx
@@ -21,7 +21,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import Svg, {Path, SvgProps} from 'react-native-svg'
import {isAndroid} from '#/platform/detection'
-import {useThemePrefs} from 'state/shell'
import {Logotype} from '#/view/icons/Logotype'
// @ts-ignore
@@ -75,10 +74,8 @@ export function Splash(props: React.PropsWithChildren) {
isLayoutReady &&
reduceMotion !== undefined
- const {colorMode} = useThemePrefs()
const colorScheme = useColorScheme()
- const themeName = colorMode === 'system' ? colorScheme : colorMode
- const isDarkMode = themeName === 'dark'
+ const isDarkMode = colorScheme === 'dark'
const logoAnimation = useAnimatedStyle(() => {
return {
@@ -263,7 +260,12 @@ export function Splash(props: React.PropsWithChildren) {
)}
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index bbf7e32431..f75e8ffe0b 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/Button.tsx b/src/components/Button.tsx
index 68cee43745..e401bda2a5 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 &
- AccessibilityProps &
- VariantProps & {
- testID?: string
- label: string
- style?: StyleProp
- }
->
+export type ButtonState = {
+ hovered: boolean
+ focused: boolean
+ pressed: boolean
+ disabled: boolean
+}
+
+export type ButtonContext = VariantProps & ButtonState
+
+export type ButtonProps = Pick<
+ PressableProps,
+ 'disabled' | 'onPress' | 'testID'
+> &
+ AccessibilityProps &
+ VariantProps & {
+ testID?: string
+ label: string
+ style?: StyleProp
+ children:
+ | React.ReactNode
+ | string
+ | ((context: ButtonContext) => React.ReactNode | string)
+ }
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
-const Context = React.createContext<
- VariantProps & {
- hovered: boolean
- focused: boolean
- pressed: boolean
- disabled: boolean
- }
->({
+const Context = React.createContext({
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(
() => ({
...state,
variant,
@@ -349,6 +364,8 @@ export function Button({
[state, variant, color, size, disabled],
)
+ const flattenedBaseStyles = flatten(baseStyles)
+
return (
{variant === 'gradient' && (
-
+
+
+
)}
{typeof children === 'string' ? (
{children}
+ ) : 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
position?: 'left' | 'right'
+ size?: SVGIconProps['size']
}) {
const {size, disabled} = useButtonContext()
const textStyles = useSharedButtonTextStyles()
@@ -532,7 +562,9 @@ export function ButtonIcon({
},
]}>
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index 763f07ca93..afd30b5ee2 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>[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 &
- Omit & {
- /**
- * Label for a11y. Defaults to the href.
- */
- label?: string
- /**
- * Web-only attribute. Sets `download` attr on web.
- */
- download?: string
- }
+ Omit
/**
* A interactive element that renders as 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 (