diff --git a/src/App.native.tsx b/src/App.native.tsx index 83f133e990..b488c5fe88 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -107,51 +107,46 @@ function InnerApp() { }, [_]) return ( - - - - - - + + + + + + - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index ff9944fa4a..a7a99ed3eb 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -92,48 +92,44 @@ function InnerApp() { return ( - - - - - + + + + + - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + ) } diff --git a/src/alf/index.tsx b/src/alf/index.tsx index d699de6a5b..963daab2a7 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -1,9 +1,11 @@ import React from 'react' import {useMediaQuery} from 'react-responsive' +import {useGate} from '#/lib/statsig/statsig' import {createThemes, defaultTheme} from '#/alf/themes' import {Theme, ThemeName} from '#/alf/types' import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration' +import * as fontScaling from '#/alf/util/fontScaling' export {atoms} from '#/alf/atoms' export * as tokens from '#/alf/tokens' @@ -12,14 +14,21 @@ export * from '#/alf/util/flatten' export * from '#/alf/util/platform' export * from '#/alf/util/themeSelector' -/* - * Context - */ -export const Context = React.createContext<{ +export type Alf = { themeName: ThemeName theme: Theme themes: ReturnType -}>({ + fontScale: number + setFontScale: (fontScale: number) => void + flags: { + neue: boolean + } +} + +/* + * Context + */ +export const Context = React.createContext({ themeName: 'light', theme: defaultTheme, themes: createThemes({ @@ -29,12 +38,27 @@ export const Context = React.createContext<{ positive: GREEN_HUE, }, }), + fontScale: fontScaling.get(), + setFontScale: () => {}, + flags: { + neue: false, + }, }) export function ThemeProvider({ children, theme: themeName, }: React.PropsWithChildren<{theme: ThemeName}>) { + const gate = useGate() + const [neue] = React.useState(() => gate('typography_neue')) + const [fontScale, setFontScale] = React.useState(() => fontScaling.get()) + const setFontScaleAndPersist = React.useCallback( + (fontScale: number) => { + setFontScale(fontScale) + fontScaling.set(fontScale) + }, + [setFontScale], + ) const themes = React.useMemo(() => { return createThemes({ hues: { @@ -44,28 +68,41 @@ export function ThemeProvider({ }, }) }, []) - const theme = themes[themeName] return ( ( () => ({ themes, themeName: themeName, - theme: theme, + theme: themes[themeName], + fontScale, + setFontScale: setFontScaleAndPersist, + flags: { + neue, + }, }), - [theme, themeName, themes], + [themeName, themes, fontScale, setFontScaleAndPersist, neue], )}> {children} ) } +export function useAlf() { + return React.useContext(Context) +} + +export function useFontScale() { + const {fontScale, setFontScale} = useAlf() + return {fontScale, setFontScale} +} + export function useTheme(theme?: ThemeName) { - const ctx = React.useContext(Context) + const alf = useAlf() return React.useMemo(() => { - return theme ? ctx.themes[theme] : ctx.theme - }, [theme, ctx]) + return theme ? alf.themes[theme] : alf.theme + }, [theme, alf]) } export function useBreakpoints() { diff --git a/src/alf/util/fontScaling.ts b/src/alf/util/fontScaling.ts new file mode 100644 index 0000000000..000d1b1689 --- /dev/null +++ b/src/alf/util/fontScaling.ts @@ -0,0 +1,10 @@ +import {device} from '#/storage' + +export function set(fontScale: number) { + device.set(['fontScale'], fontScale) +} + +export function get() { + const fontScale = device.get(['fontScale']) || 1 + return fontScale +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx index d65444e1f7..704aa9d987 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -7,7 +7,6 @@ import { PressableProps, StyleProp, StyleSheet, - Text, TextProps, TextStyle, View, @@ -17,7 +16,7 @@ import {LinearGradient} from 'expo-linear-gradient' import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf' import {Props as SVGIconProps} from '#/components/icons/common' -import {normalizeTextStyles} from '#/components/Typography' +import {Text} from '#/components/Typography' export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient' export type ButtonColor = @@ -635,14 +634,7 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { const textStyles = useSharedButtonTextStyles() return ( - + {children} ) diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 31dd931c6a..5cdcd7d7c2 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -3,7 +3,7 @@ import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native' import {UITextView} from 'react-native-uitextview' import {isNative} from '#/platform/detection' -import {atoms, flatten, useTheme, web} from '#/alf' +import {atoms, flatten, useAlf, useTheme, web} from '#/alf' export type TextProps = RNTextProps & { /** @@ -34,14 +34,17 @@ export function leading< * If the `lineHeight` value is > 2, we assume it's an absolute value and * returns it as-is. */ -export function normalizeTextStyles(styles: StyleProp) { +export function normalizeTextStyles( + styles: StyleProp, + {fontScale}: {fontScale: number}, +) { const s = flatten(styles) // should always be defined on these components - const fontSize = s.fontSize || atoms.text_md.fontSize + s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale if (s?.lineHeight) { if (s.lineHeight !== 0 && s.lineHeight <= 2) { - s.lineHeight = Math.round(fontSize * s.lineHeight) + s.lineHeight = Math.round(s.fontSize * s.lineHeight) } } else if (!isNative) { s.lineHeight = s.fontSize @@ -54,8 +57,11 @@ export function normalizeTextStyles(styles: StyleProp) { * Our main text component. Use this most of the time. */ export function Text({style, selectable, ...rest}: TextProps) { + const {fontScale} = useAlf() const t = useTheme() - const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)]) + const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)], { + fontScale, + }) return } diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 909b93e6be..5c260633b8 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,5 +1,6 @@ export type Gate = // Keep this alphabetic please. | 'debug_show_feedcontext' + | 'typography_neue' | 'suggested_feeds_interstitial' | 'ten_million_dialog' diff --git a/src/storage/schema.ts b/src/storage/schema.ts index bc41fd3edf..6ef042e021 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -2,5 +2,6 @@ * Device data that's specific to the device and does not vary based account */ export type Device = { + fontScale: number | undefined lastNuxDialog: string | undefined }