[Neue] Base (#5395)
* Add fontScale, gate it, fix some computes * Add inter, integrate * Clean up * Apply to old Text component * Use numeric weight * Cleanup * Clean up appearance settings * Global tracking * Fix regular italic variant * Refactor settings and fontScale values * Remove flags * Get rid of lower weight font usage * Remove gate from settings * Refactor appearance settings for reuse * Add neue type nux * Update defaults * Load fonts, add fallback families * Load fonts via plugin in production * Fixes * Fix for web * Nits --------- Co-authored-by: Hailey <me@haileyok.com>zio/dev^2
parent
fb3be79820
commit
cbc7cd0808
|
@ -230,6 +230,31 @@ module.exports = function (config) {
|
||||||
'./plugins/shareExtension/withShareExtensions.js',
|
'./plugins/shareExtension/withShareExtensions.js',
|
||||||
'./plugins/notificationsExtension/withNotificationsExtension.js',
|
'./plugins/notificationsExtension/withNotificationsExtension.js',
|
||||||
'./plugins/withAppDelegateReferrer.js',
|
'./plugins/withAppDelegateReferrer.js',
|
||||||
|
[
|
||||||
|
'expo-font',
|
||||||
|
{
|
||||||
|
fonts: [
|
||||||
|
// './assets/fonts/inter/Inter-Thin.otf',
|
||||||
|
// './assets/fonts/inter/Inter-ThinItalic.otf',
|
||||||
|
// './assets/fonts/inter/Inter-ExtraLight.otf',
|
||||||
|
// './assets/fonts/inter/Inter-ExtraLightItalic.otf',
|
||||||
|
// './assets/fonts/inter/Inter-Light.otf',
|
||||||
|
// './assets/fonts/inter/Inter-LightItalic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-Regular.otf',
|
||||||
|
'./assets/fonts/inter/Inter-Italic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-Medium.otf',
|
||||||
|
'./assets/fonts/inter/Inter-MediumItalic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-SemiBold.otf',
|
||||||
|
'./assets/fonts/inter/Inter-SemiBoldItalic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-Bold.otf',
|
||||||
|
'./assets/fonts/inter/Inter-BoldItalic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-ExtraBold.otf',
|
||||||
|
'./assets/fonts/inter/Inter-ExtraBoldItalic.otf',
|
||||||
|
'./assets/fonts/inter/Inter-Black.otf',
|
||||||
|
'./assets/fonts/inter/Inter-BlackItalic.otf',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
extra: {
|
extra: {
|
||||||
eas: {
|
eas: {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M9 5a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2h-5v14a1 1 0 1 1-2 0V6h-5a1 1 0 0 1-1-1Zm-3.073 7v8a1 1 0 1 0 2 0v-8H12a1 1 0 1 0 0-2H6.971a1.015 1.015 0 0 0-.089 0H2a1 1 0 1 0 0 2h3.927Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 317 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3.65 17.247c-.242.832-.632 1.178-1.325 1.178-.814 0-1.325-.476-1.325-1.23 0-.216.06-.51.173-.831L4.586 7.07c.364-1.014.979-1.482 1.966-1.482 1.022 0 1.629.45 2.001 1.473l3.43 9.303c.121.337.165.571.165.831 0 .72-.546 1.23-1.308 1.23-.736 0-1.126-.338-1.36-1.152l-.658-1.975H4.309l-.658 1.95ZM6.5 8.152l-1.62 5.12h3.335l-1.654-5.12H6.5Zm13.005 8.688c-.52.988-1.68 1.568-2.84 1.568-1.768 0-3.11-1.144-3.11-2.815 0-1.69 1.299-2.668 3.62-2.807l2.34-.138v-.615c0-.867-.607-1.369-1.56-1.369-.771 0-1.239.251-1.802.979-.277.312-.597.468-1.004.468-.615 0-1.057-.399-1.057-.97 0-.2.043-.382.13-.572.433-1.109 1.923-1.793 3.845-1.793 2.383 0 3.933 1.23 3.933 3.1v5.293c0 .84-.511 1.273-1.23 1.273-.684 0-1.16-.38-1.213-1.126v-.476h-.052Zm-3.43-1.386c0 .693.572 1.126 1.42 1.126 1.11 0 2.02-.719 2.02-1.723v-.676l-1.959.121c-.944.07-1.48.494-1.48 1.152Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 986 B |
|
@ -124,6 +124,7 @@
|
||||||
"expo-dev-client": "^4.0.14",
|
"expo-dev-client": "^4.0.14",
|
||||||
"expo-device": "~6.0.2",
|
"expo-device": "~6.0.2",
|
||||||
"expo-file-system": "^17.0.1",
|
"expo-file-system": "^17.0.1",
|
||||||
|
"expo-font": "~12.0.10",
|
||||||
"expo-haptics": "^13.0.1",
|
"expo-haptics": "^13.0.1",
|
||||||
"expo-image": "~1.12.9",
|
"expo-image": "~1.12.9",
|
||||||
"expo-image-manipulator": "^12.0.5",
|
"expo-image-manipulator": "^12.0.5",
|
||||||
|
|
|
@ -55,7 +55,7 @@ import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
||||||
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {Shell} from '#/view/shell'
|
import {Shell} from '#/view/shell'
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf, useFonts} from '#/alf'
|
||||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {NuxDialogs} from '#/components/dialogs/nuxs'
|
import {NuxDialogs} from '#/components/dialogs/nuxs'
|
||||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||||
|
@ -106,62 +106,60 @@ function InnerApp() {
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alf theme={theme}>
|
<StatsigProvider
|
||||||
<ThemeProvider theme={theme}>
|
// Resets the entire tree below when it changes:
|
||||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
key={currentAccount?.did}>
|
||||||
<RootSiblingParent>
|
<Alf theme={theme}>
|
||||||
<VideoVolumeProvider>
|
<ThemeProvider theme={theme}>
|
||||||
<React.Fragment
|
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||||
// Resets the entire tree below when it changes:
|
<RootSiblingParent>
|
||||||
key={currentAccount?.did}>
|
<VideoVolumeProvider>
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<MessagesProvider>
|
||||||
<MessagesProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<LabelDefsProvider>
|
||||||
<LabelDefsProvider>
|
<ModerationOptsProvider>
|
||||||
<ModerationOptsProvider>
|
<LoggedOutViewProvider>
|
||||||
<LoggedOutViewProvider>
|
<SelectedFeedProvider>
|
||||||
<SelectedFeedProvider>
|
<HiddenRepliesProvider>
|
||||||
<HiddenRepliesProvider>
|
<UnreadNotifsProvider>
|
||||||
<UnreadNotifsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<MutedThreadsProvider>
|
||||||
<MutedThreadsProvider>
|
<ProgressGuideProvider>
|
||||||
<ProgressGuideProvider>
|
<GestureHandlerRootView style={s.h100pct}>
|
||||||
<GestureHandlerRootView
|
<TestCtrls />
|
||||||
style={s.h100pct}>
|
<Shell />
|
||||||
<TestCtrls />
|
<NuxDialogs />
|
||||||
<Shell />
|
</GestureHandlerRootView>
|
||||||
<NuxDialogs />
|
</ProgressGuideProvider>
|
||||||
</GestureHandlerRootView>
|
</MutedThreadsProvider>
|
||||||
</ProgressGuideProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</MutedThreadsProvider>
|
</UnreadNotifsProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</HiddenRepliesProvider>
|
||||||
</UnreadNotifsProvider>
|
</SelectedFeedProvider>
|
||||||
</HiddenRepliesProvider>
|
</LoggedOutViewProvider>
|
||||||
</SelectedFeedProvider>
|
</ModerationOptsProvider>
|
||||||
</LoggedOutViewProvider>
|
</LabelDefsProvider>
|
||||||
</ModerationOptsProvider>
|
</MessagesProvider>
|
||||||
</LabelDefsProvider>
|
|
||||||
</MessagesProvider>
|
|
||||||
</StatsigProvider>
|
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</React.Fragment>
|
</VideoVolumeProvider>
|
||||||
</VideoVolumeProvider>
|
</RootSiblingParent>
|
||||||
</RootSiblingParent>
|
</Splash>
|
||||||
</Splash>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</Alf>
|
||||||
</Alf>
|
</StatsigProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isReady, setReady] = useState(false)
|
const [isReady, setReady] = useState(false)
|
||||||
|
const [loaded] = useFonts()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initPersistedState().then(() => setReady(true))
|
initPersistedState().then(() => setReady(true))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady || !loaded) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/Video
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ToastContainer} from '#/view/com/util/Toast.web'
|
import {ToastContainer} from '#/view/com/util/Toast.web'
|
||||||
import {Shell} from '#/view/shell/index'
|
import {Shell} from '#/view/shell/index'
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf, useFonts} from '#/alf'
|
||||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {NuxDialogs} from '#/components/dialogs/nuxs'
|
import {NuxDialogs} from '#/components/dialogs/nuxs'
|
||||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||||
|
@ -96,62 +96,61 @@ function InnerApp() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardProvider enabled={false}>
|
<KeyboardProvider enabled={false}>
|
||||||
<Alf theme={theme}>
|
<StatsigProvider
|
||||||
<ThemeProvider theme={theme}>
|
// Resets the entire tree below when it changes:
|
||||||
<RootSiblingParent>
|
key={currentAccount?.did}>
|
||||||
<VideoVolumeProvider>
|
<Alf theme={theme}>
|
||||||
<ActiveVideoProvider>
|
<ThemeProvider theme={theme}>
|
||||||
<React.Fragment
|
<RootSiblingParent>
|
||||||
// Resets the entire tree below when it changes:
|
<VideoVolumeProvider>
|
||||||
key={currentAccount?.did}>
|
<ActiveVideoProvider>
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<MessagesProvider>
|
||||||
<MessagesProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<LabelDefsProvider>
|
||||||
<LabelDefsProvider>
|
<ModerationOptsProvider>
|
||||||
<ModerationOptsProvider>
|
<LoggedOutViewProvider>
|
||||||
<LoggedOutViewProvider>
|
<SelectedFeedProvider>
|
||||||
<SelectedFeedProvider>
|
<HiddenRepliesProvider>
|
||||||
<HiddenRepliesProvider>
|
<UnreadNotifsProvider>
|
||||||
<UnreadNotifsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<MutedThreadsProvider>
|
||||||
<MutedThreadsProvider>
|
<SafeAreaProvider>
|
||||||
<SafeAreaProvider>
|
<ProgressGuideProvider>
|
||||||
<ProgressGuideProvider>
|
<Shell />
|
||||||
<Shell />
|
<NuxDialogs />
|
||||||
<NuxDialogs />
|
</ProgressGuideProvider>
|
||||||
</ProgressGuideProvider>
|
</SafeAreaProvider>
|
||||||
</SafeAreaProvider>
|
</MutedThreadsProvider>
|
||||||
</MutedThreadsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</UnreadNotifsProvider>
|
||||||
</UnreadNotifsProvider>
|
</HiddenRepliesProvider>
|
||||||
</HiddenRepliesProvider>
|
</SelectedFeedProvider>
|
||||||
</SelectedFeedProvider>
|
</LoggedOutViewProvider>
|
||||||
</LoggedOutViewProvider>
|
</ModerationOptsProvider>
|
||||||
</ModerationOptsProvider>
|
</LabelDefsProvider>
|
||||||
</LabelDefsProvider>
|
</MessagesProvider>
|
||||||
</MessagesProvider>
|
|
||||||
</StatsigProvider>
|
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</React.Fragment>
|
<ToastContainer />
|
||||||
<ToastContainer />
|
</ActiveVideoProvider>
|
||||||
</ActiveVideoProvider>
|
</VideoVolumeProvider>
|
||||||
</VideoVolumeProvider>
|
</RootSiblingParent>
|
||||||
</RootSiblingParent>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</Alf>
|
||||||
</Alf>
|
</StatsigProvider>
|
||||||
</KeyboardProvider>
|
</KeyboardProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isReady, setReady] = useState(false)
|
const [isReady, setReady] = useState(false)
|
||||||
|
const [loaded] = useFonts()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initPersistedState().then(() => setReady(true))
|
initPersistedState().then(() => setReady(true))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady || !loaded) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,43 +225,43 @@ export const atoms = {
|
||||||
},
|
},
|
||||||
text_2xs: {
|
text_2xs: {
|
||||||
fontSize: tokens.fontSize._2xs,
|
fontSize: tokens.fontSize._2xs,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_xs: {
|
text_xs: {
|
||||||
fontSize: tokens.fontSize.xs,
|
fontSize: tokens.fontSize.xs,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_sm: {
|
text_sm: {
|
||||||
fontSize: tokens.fontSize.sm,
|
fontSize: tokens.fontSize.sm,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_md: {
|
text_md: {
|
||||||
fontSize: tokens.fontSize.md,
|
fontSize: tokens.fontSize.md,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_lg: {
|
text_lg: {
|
||||||
fontSize: tokens.fontSize.lg,
|
fontSize: tokens.fontSize.lg,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_xl: {
|
text_xl: {
|
||||||
fontSize: tokens.fontSize.xl,
|
fontSize: tokens.fontSize.xl,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_2xl: {
|
text_2xl: {
|
||||||
fontSize: tokens.fontSize._2xl,
|
fontSize: tokens.fontSize._2xl,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_3xl: {
|
text_3xl: {
|
||||||
fontSize: tokens.fontSize._3xl,
|
fontSize: tokens.fontSize._3xl,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_4xl: {
|
text_4xl: {
|
||||||
fontSize: tokens.fontSize._4xl,
|
fontSize: tokens.fontSize._4xl,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
text_5xl: {
|
text_5xl: {
|
||||||
fontSize: tokens.fontSize._5xl,
|
fontSize: tokens.fontSize._5xl,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
leading_tight: {
|
leading_tight: {
|
||||||
lineHeight: 1.15,
|
lineHeight: 1.15,
|
||||||
|
@ -273,10 +273,7 @@ export const atoms = {
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
},
|
},
|
||||||
tracking_normal: {
|
tracking_normal: {
|
||||||
letterSpacing: 0,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
|
||||||
tracking_wide: {
|
|
||||||
letterSpacing: 0.25,
|
|
||||||
},
|
},
|
||||||
font_normal: {
|
font_normal: {
|
||||||
fontWeight: tokens.fontWeight.normal,
|
fontWeight: tokens.fontWeight.normal,
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
import {useFonts as defaultUseFonts} from 'expo-font'
|
||||||
|
|
||||||
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
|
import {Device, device} from '#/storage'
|
||||||
|
|
||||||
|
const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
|
||||||
|
|
||||||
|
const factor = 0.0625 // 1 - (15/16)
|
||||||
|
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
|
||||||
|
'-2': 1 - factor * 3,
|
||||||
|
'-1': 1 - factor * 2,
|
||||||
|
'0': 1 - factor * 1, // default
|
||||||
|
'1': 1,
|
||||||
|
'2': 1 + factor * 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeFontScaleMultiplier(scale: Device['fontScale']) {
|
||||||
|
return fontScaleMultipliers[scale]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFontScale() {
|
||||||
|
return device.get(['fontScale']) ?? '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFontScale(fontScale: Device['fontScale']) {
|
||||||
|
device.set(['fontScale'], fontScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFontFamily() {
|
||||||
|
return device.get(['fontFamily']) || 'theme'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFontFamily(fontFamily: Device['fontFamily']) {
|
||||||
|
device.set(['fontFamily'], fontFamily)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unused fonts are commented out, but the files are there if we need them.
|
||||||
|
*/
|
||||||
|
export function useFonts() {
|
||||||
|
/**
|
||||||
|
* For native, the `expo-font` config plugin embeds the fonts in the
|
||||||
|
* application binary. But `expo-font` isn't supported on web, so we fall
|
||||||
|
* back to async loading here.
|
||||||
|
*/
|
||||||
|
if (isNative) return [true, null]
|
||||||
|
return defaultUseFonts({
|
||||||
|
// 'Inter-Thin': require('../../assets/fonts/inter/Inter-Thin.otf'),
|
||||||
|
// 'Inter-ThinItalic': require('../../assets/fonts/inter/Inter-ThinItalic.otf'),
|
||||||
|
// 'Inter-ExtraLight': require('../../assets/fonts/inter/Inter-ExtraLight.otf'),
|
||||||
|
// 'Inter-ExtraLightItalic': require('../../assets/fonts/inter/Inter-ExtraLightItalic.otf'),
|
||||||
|
// 'Inter-Light': require('../../assets/fonts/inter/Inter-Light.otf'),
|
||||||
|
// 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
|
||||||
|
'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
|
||||||
|
'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
|
||||||
|
'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
|
||||||
|
'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
|
||||||
|
'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
|
||||||
|
'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
|
||||||
|
'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
|
||||||
|
'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
|
||||||
|
'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
|
||||||
|
'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
|
||||||
|
'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
|
||||||
|
'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unused fonts are commented out, but the files are there if we need them.
|
||||||
|
*/
|
||||||
|
export function applyFonts(
|
||||||
|
style: Record<string, any>,
|
||||||
|
fontFamily: 'system' | 'theme',
|
||||||
|
) {
|
||||||
|
if (fontFamily === 'theme') {
|
||||||
|
style.fontFamily =
|
||||||
|
{
|
||||||
|
// '100': 'Inter-Thin',
|
||||||
|
// '200': 'Inter-ExtraLight',
|
||||||
|
// '300': 'Inter-Light',
|
||||||
|
'100': 'Inter-Regular',
|
||||||
|
'200': 'Inter-Regular',
|
||||||
|
'300': 'Inter-Regular',
|
||||||
|
'400': 'Inter-Regular',
|
||||||
|
'500': 'Inter-Medium',
|
||||||
|
'600': 'Inter-SemiBold',
|
||||||
|
'700': 'Inter-Bold',
|
||||||
|
'800': 'Inter-ExtraBold',
|
||||||
|
'900': 'Inter-Black',
|
||||||
|
}[style.fontWeight as string] || 'Inter-Regular'
|
||||||
|
|
||||||
|
if (style.fontStyle === 'italic') {
|
||||||
|
if (style.fontFamily === 'Inter-Regular') {
|
||||||
|
style.fontFamily = 'Inter-Italic'
|
||||||
|
} else {
|
||||||
|
style.fontFamily += 'Italic'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback families only supported on web
|
||||||
|
if (isWeb) {
|
||||||
|
style.fontFamily += `, ${FAMILIES}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback families only supported on web
|
||||||
|
if (isWeb) {
|
||||||
|
style.fontFamily = style.fontFamily || FAMILIES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,47 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useMediaQuery} from 'react-responsive'
|
import {useMediaQuery} from 'react-responsive'
|
||||||
|
|
||||||
|
import {
|
||||||
|
computeFontScaleMultiplier,
|
||||||
|
getFontFamily,
|
||||||
|
getFontScale,
|
||||||
|
setFontFamily as persistFontFamily,
|
||||||
|
setFontScale as persistFontScale,
|
||||||
|
} from '#/alf/fonts'
|
||||||
import {createThemes, defaultTheme} from '#/alf/themes'
|
import {createThemes, defaultTheme} from '#/alf/themes'
|
||||||
import {Theme, ThemeName} from '#/alf/types'
|
import {Theme, ThemeName} from '#/alf/types'
|
||||||
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
|
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
|
||||||
|
import {Device} from '#/storage'
|
||||||
|
|
||||||
export {atoms} from '#/alf/atoms'
|
export {atoms} from '#/alf/atoms'
|
||||||
|
export * from '#/alf/fonts'
|
||||||
export * as tokens from '#/alf/tokens'
|
export * as tokens from '#/alf/tokens'
|
||||||
export * from '#/alf/types'
|
export * from '#/alf/types'
|
||||||
export * from '#/alf/util/flatten'
|
export * from '#/alf/util/flatten'
|
||||||
export * from '#/alf/util/platform'
|
export * from '#/alf/util/platform'
|
||||||
export * from '#/alf/util/themeSelector'
|
export * from '#/alf/util/themeSelector'
|
||||||
|
|
||||||
/*
|
export type Alf = {
|
||||||
* Context
|
|
||||||
*/
|
|
||||||
export const Context = React.createContext<{
|
|
||||||
themeName: ThemeName
|
themeName: ThemeName
|
||||||
theme: Theme
|
theme: Theme
|
||||||
themes: ReturnType<typeof createThemes>
|
themes: ReturnType<typeof createThemes>
|
||||||
}>({
|
fonts: {
|
||||||
|
scale: Exclude<Device['fontScale'], undefined>
|
||||||
|
scaleMultiplier: number
|
||||||
|
family: Device['fontFamily']
|
||||||
|
setFontScale: (fontScale: Exclude<Device['fontScale'], undefined>) => void
|
||||||
|
setFontFamily: (fontFamily: Device['fontFamily']) => void
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Feature flags or other gated options
|
||||||
|
*/
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
export const Context = React.createContext<Alf>({
|
||||||
themeName: 'light',
|
themeName: 'light',
|
||||||
theme: defaultTheme,
|
theme: defaultTheme,
|
||||||
themes: createThemes({
|
themes: createThemes({
|
||||||
|
@ -29,12 +51,48 @@ export const Context = React.createContext<{
|
||||||
positive: GREEN_HUE,
|
positive: GREEN_HUE,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
fonts: {
|
||||||
|
scale: getFontScale(),
|
||||||
|
scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
|
||||||
|
family: getFontFamily(),
|
||||||
|
setFontScale: () => {},
|
||||||
|
setFontFamily: () => {},
|
||||||
|
},
|
||||||
|
flags: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
export function ThemeProvider({
|
export function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
theme: themeName,
|
theme: themeName,
|
||||||
}: React.PropsWithChildren<{theme: ThemeName}>) {
|
}: React.PropsWithChildren<{theme: ThemeName}>) {
|
||||||
|
const [fontScale, setFontScale] = React.useState<Alf['fonts']['scale']>(() =>
|
||||||
|
getFontScale(),
|
||||||
|
)
|
||||||
|
const [fontScaleMultiplier, setFontScaleMultiplier] = React.useState(() =>
|
||||||
|
computeFontScaleMultiplier(fontScale),
|
||||||
|
)
|
||||||
|
const setFontScaleAndPersist = React.useCallback<
|
||||||
|
Alf['fonts']['setFontScale']
|
||||||
|
>(
|
||||||
|
fontScale => {
|
||||||
|
setFontScale(fontScale)
|
||||||
|
persistFontScale(fontScale)
|
||||||
|
setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
|
||||||
|
},
|
||||||
|
[setFontScale],
|
||||||
|
)
|
||||||
|
const [fontFamily, setFontFamily] = React.useState<Alf['fonts']['family']>(
|
||||||
|
() => getFontFamily(),
|
||||||
|
)
|
||||||
|
const setFontFamilyAndPersist = React.useCallback<
|
||||||
|
Alf['fonts']['setFontFamily']
|
||||||
|
>(
|
||||||
|
fontFamily => {
|
||||||
|
setFontFamily(fontFamily)
|
||||||
|
persistFontFamily(fontFamily)
|
||||||
|
},
|
||||||
|
[setFontFamily],
|
||||||
|
)
|
||||||
const themes = React.useMemo(() => {
|
const themes = React.useMemo(() => {
|
||||||
return createThemes({
|
return createThemes({
|
||||||
hues: {
|
hues: {
|
||||||
|
@ -44,28 +102,47 @@ export function ThemeProvider({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
const theme = themes[themeName]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={React.useMemo(
|
value={React.useMemo<Alf>(
|
||||||
() => ({
|
() => ({
|
||||||
themes,
|
themes,
|
||||||
themeName: themeName,
|
themeName: themeName,
|
||||||
theme: theme,
|
theme: themes[themeName],
|
||||||
|
fonts: {
|
||||||
|
scale: fontScale,
|
||||||
|
scaleMultiplier: fontScaleMultiplier,
|
||||||
|
family: fontFamily,
|
||||||
|
setFontScale: setFontScaleAndPersist,
|
||||||
|
setFontFamily: setFontFamilyAndPersist,
|
||||||
|
},
|
||||||
|
flags: {},
|
||||||
}),
|
}),
|
||||||
[theme, themeName, themes],
|
[
|
||||||
|
themeName,
|
||||||
|
themes,
|
||||||
|
fontScale,
|
||||||
|
setFontScaleAndPersist,
|
||||||
|
fontFamily,
|
||||||
|
setFontFamilyAndPersist,
|
||||||
|
fontScaleMultiplier,
|
||||||
|
],
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{children}
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAlf() {
|
||||||
|
return React.useContext(Context)
|
||||||
|
}
|
||||||
|
|
||||||
export function useTheme(theme?: ThemeName) {
|
export function useTheme(theme?: ThemeName) {
|
||||||
const ctx = React.useContext(Context)
|
const alf = useAlf()
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return theme ? ctx.themes[theme] : ctx.theme
|
return theme ? alf.themes[theme] : alf.theme
|
||||||
}, [theme, ctx])
|
}, [theme, alf])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBreakpoints() {
|
export function useBreakpoints() {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import {Platform} from 'react-native'
|
||||||
|
|
||||||
|
export const TRACKING = Platform.OS === 'android' ? 0.1 : 0
|
||||||
|
|
||||||
export const color = {
|
export const color = {
|
||||||
temp_purple: 'rgb(105 0 255)',
|
temp_purple: 'rgb(105 0 255)',
|
||||||
temp_purple_dark: 'rgb(83 0 202)',
|
temp_purple_dark: 'rgb(83 0 202)',
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
PressableProps,
|
PressableProps,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
|
||||||
TextProps,
|
TextProps,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
View,
|
View,
|
||||||
|
@ -17,7 +16,7 @@ import {LinearGradient} from 'expo-linear-gradient'
|
||||||
|
|
||||||
import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf'
|
import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf'
|
||||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
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 ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
|
||||||
export type ButtonColor =
|
export type ButtonColor =
|
||||||
|
@ -635,14 +634,7 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
|
||||||
const textStyles = useSharedButtonTextStyles()
|
const textStyles = useSharedButtonTextStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text {...rest} style={[a.font_bold, a.text_center, textStyles, style]}>
|
||||||
{...rest}
|
|
||||||
style={normalizeTextStyles([
|
|
||||||
a.font_bold,
|
|
||||||
a.text_center,
|
|
||||||
textStyles,
|
|
||||||
style,
|
|
||||||
])}>
|
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||||
export * from '#/components/Dialog/types'
|
export * from '#/components/Dialog/types'
|
||||||
|
export * from '#/components/Dialog/utils'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const Input = createInput(BottomSheetTextInput)
|
export const Input = createInput(BottomSheetTextInput)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||||
export * from '#/components/Dialog/types'
|
export * from '#/components/Dialog/types'
|
||||||
|
export * from '#/components/Dialog/utils'
|
||||||
export {Input} from '#/components/forms/TextField'
|
export {Input} from '#/components/forms/TextField'
|
||||||
|
|
||||||
const stopPropagation = (e: any) => e.stopPropagation()
|
const stopPropagation = (e: any) => e.stopPropagation()
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {DialogControlProps} from '#/components/Dialog/types'
|
||||||
|
|
||||||
|
export function useAutoOpen(control: DialogControlProps, showTimeout?: number) {
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (showTimeout) {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
control.open()
|
||||||
|
}, showTimeout)
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
control.open()
|
||||||
|
}
|
||||||
|
}, [control, showTimeout])
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
|
||||||
import {UITextView} from 'react-native-uitextview'
|
import {UITextView} from 'react-native-uitextview'
|
||||||
|
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {atoms, flatten, useTheme, web} from '#/alf'
|
import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
|
||||||
|
|
||||||
export type TextProps = RNTextProps & {
|
export type TextProps = RNTextProps & {
|
||||||
/**
|
/**
|
||||||
|
@ -34,19 +34,30 @@ export function leading<
|
||||||
* If the `lineHeight` value is > 2, we assume it's an absolute value and
|
* If the `lineHeight` value is > 2, we assume it's an absolute value and
|
||||||
* returns it as-is.
|
* returns it as-is.
|
||||||
*/
|
*/
|
||||||
export function normalizeTextStyles(styles: StyleProp<TextStyle>) {
|
export function normalizeTextStyles(
|
||||||
|
styles: StyleProp<TextStyle>,
|
||||||
|
{
|
||||||
|
fontScale,
|
||||||
|
fontFamily,
|
||||||
|
}: {
|
||||||
|
fontScale: number
|
||||||
|
fontFamily: Alf['fonts']['family']
|
||||||
|
} & Pick<Alf, 'flags'>,
|
||||||
|
) {
|
||||||
const s = flatten(styles)
|
const s = flatten(styles)
|
||||||
// should always be defined on these components
|
// 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) {
|
||||||
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
|
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) {
|
} else if (!isNative) {
|
||||||
s.lineHeight = s.fontSize
|
s.lineHeight = s.fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyFonts(s, fontFamily)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,8 +65,13 @@ export function normalizeTextStyles(styles: StyleProp<TextStyle>) {
|
||||||
* Our main text component. Use this most of the time.
|
* Our main text component. Use this most of the time.
|
||||||
*/
|
*/
|
||||||
export function Text({style, selectable, ...rest}: TextProps) {
|
export function Text({style, selectable, ...rest}: TextProps) {
|
||||||
|
const {fonts, flags} = useAlf()
|
||||||
const t = useTheme()
|
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: fonts.scaleMultiplier,
|
||||||
|
fontFamily: fonts.family,
|
||||||
|
flags,
|
||||||
|
})
|
||||||
|
|
||||||
return <UITextView selectable={selectable} uiTextView style={s} {...rest} />
|
return <UITextView selectable={selectable} uiTextView style={s} {...rest} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {AppearanceToggleButtonGroup} from '#/screens/Settings/AppearanceSettings'
|
||||||
|
import {atoms as a, useAlf, useTheme} from '#/alf'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
|
||||||
|
import {Divider} from '#/components/Divider'
|
||||||
|
import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
|
||||||
|
import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function NeueTypography() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const nuxDialogs = useNuxDialogContext()
|
||||||
|
const control = Dialog.useDialogControl()
|
||||||
|
const {fonts} = useAlf()
|
||||||
|
|
||||||
|
Dialog.useAutoOpen(control, 3e3)
|
||||||
|
|
||||||
|
const onClose = React.useCallback(() => {
|
||||||
|
nuxDialogs.dismissActiveNux()
|
||||||
|
}, [nuxDialogs])
|
||||||
|
|
||||||
|
const onChangeFontFamily = React.useCallback(
|
||||||
|
(values: string[]) => {
|
||||||
|
const next = values[0] === 'system' ? 'system' : 'theme'
|
||||||
|
fonts.setFontFamily(next)
|
||||||
|
},
|
||||||
|
[fonts],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onChangeFontScale = React.useCallback(
|
||||||
|
(values: string[]) => {
|
||||||
|
const next = values[0] || ('0' as any)
|
||||||
|
fonts.setFontScale(next)
|
||||||
|
},
|
||||||
|
[fonts],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control} onClose={onClose}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
|
||||||
|
<Dialog.ScrollableInner label={_(msg`Introducing new font settings`)}>
|
||||||
|
<View style={[a.gap_xl]}>
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<Text style={[a.text_3xl, {fontWeight: '900'}]}>
|
||||||
|
<Trans>Introducing new font settings ✨</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_lg, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
To the ensure the best possible experience, we're introducing a
|
||||||
|
new theme font, along with adjustable font sizing settings.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans>
|
||||||
|
Defaults are shown below. You can edit these in your Appearance
|
||||||
|
Settings later.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<AppearanceToggleButtonGroup
|
||||||
|
title={_(msg`Font`)}
|
||||||
|
description={_(
|
||||||
|
msg`For the best experience, we recommend using the theme font.`,
|
||||||
|
)}
|
||||||
|
icon={Aa}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: _(msg`System`),
|
||||||
|
name: 'system',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Theme`),
|
||||||
|
name: 'theme',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
values={[fonts.family]}
|
||||||
|
onChange={onChangeFontFamily}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AppearanceToggleButtonGroup
|
||||||
|
title={_(msg`Font size`)}
|
||||||
|
icon={TextSize}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: _(msg`Smaller`),
|
||||||
|
name: '-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Default`),
|
||||||
|
name: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Larger`),
|
||||||
|
name: '1',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
values={[fonts.scale]}
|
||||||
|
onChange={onChangeFontScale}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AppBskyActorDefs} from '@atproto/api'
|
||||||
|
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
@ -8,9 +9,16 @@ import {
|
||||||
useRemoveNuxsMutation,
|
useRemoveNuxsMutation,
|
||||||
useUpsertNuxMutation,
|
useUpsertNuxMutation,
|
||||||
} from '#/state/queries/nuxs'
|
} from '#/state/queries/nuxs'
|
||||||
import {useSession} from '#/state/session'
|
import {
|
||||||
|
usePreferencesQuery,
|
||||||
|
UsePreferencesQueryResponse,
|
||||||
|
} from '#/state/queries/preferences'
|
||||||
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
|
import {SessionAccount, useSession} from '#/state/session'
|
||||||
import {useOnboardingState} from '#/state/shell'
|
import {useOnboardingState} from '#/state/shell'
|
||||||
|
import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography'
|
||||||
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
|
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
|
||||||
|
// NUXs
|
||||||
import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
|
import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
|
||||||
import {IS_DEV} from '#/env'
|
import {IS_DEV} from '#/env'
|
||||||
|
|
||||||
|
@ -21,11 +29,27 @@ type Context = {
|
||||||
|
|
||||||
const queuedNuxs: {
|
const queuedNuxs: {
|
||||||
id: Nux
|
id: Nux
|
||||||
enabled?: (props: {gate: ReturnType<typeof useGate>}) => boolean
|
enabled?: (props: {
|
||||||
|
gate: ReturnType<typeof useGate>
|
||||||
|
currentAccount: SessionAccount
|
||||||
|
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
||||||
|
preferences: UsePreferencesQueryResponse
|
||||||
|
}) => boolean
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
id: Nux.TenMillionDialog,
|
id: Nux.TenMillionDialog,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: Nux.NeueTypography,
|
||||||
|
enabled(props) {
|
||||||
|
if (props.currentProfile.createdAt) {
|
||||||
|
if (new Date(props.currentProfile.createdAt) < new Date('2024-09-25')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const Context = React.createContext<Context>({
|
const Context = React.createContext<Context>({
|
||||||
|
@ -38,12 +62,31 @@ export function useNuxDialogContext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NuxDialogs() {
|
export function NuxDialogs() {
|
||||||
const {hasSession} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const onboardingState = useOnboardingState()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
return hasSession && !onboardingState.isActive ? <Inner /> : null
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
|
const onboardingActive = useOnboardingState().isActive
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
!currentAccount || !preferences || !profile || onboardingActive
|
||||||
|
return !isLoading ? (
|
||||||
|
<Inner
|
||||||
|
currentAccount={currentAccount}
|
||||||
|
currentProfile={profile}
|
||||||
|
preferences={preferences}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inner() {
|
function Inner({
|
||||||
|
currentAccount,
|
||||||
|
currentProfile,
|
||||||
|
preferences,
|
||||||
|
}: {
|
||||||
|
currentAccount: SessionAccount
|
||||||
|
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
||||||
|
preferences: UsePreferencesQueryResponse
|
||||||
|
}) {
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
const {nuxs} = useNuxs()
|
const {nuxs} = useNuxs()
|
||||||
const [snoozed, setSnoozed] = React.useState(() => {
|
const [snoozed, setSnoozed] = React.useState(() => {
|
||||||
|
@ -80,10 +123,19 @@ function Inner() {
|
||||||
const nux = nuxs.find(nux => nux.id === id)
|
const nux = nuxs.find(nux => nux.id === id)
|
||||||
|
|
||||||
// check if completed first
|
// check if completed first
|
||||||
if (nux && nux.completed) continue
|
if (nux && nux.completed) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// then check gate (track exposure)
|
// then check gate (track exposure)
|
||||||
if (enabled && !enabled({gate})) continue
|
if (
|
||||||
|
enabled &&
|
||||||
|
!enabled({gate, currentAccount, currentProfile, preferences})
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`NUX dialogs: activating '${id}' NUX`)
|
||||||
|
|
||||||
// we have a winner
|
// we have a winner
|
||||||
setActiveNux(id)
|
setActiveNux(id)
|
||||||
|
@ -104,7 +156,16 @@ function Inner() {
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, [nuxs, snoozed, snoozeNuxDialog, upsertNux, gate])
|
}, [
|
||||||
|
nuxs,
|
||||||
|
snoozed,
|
||||||
|
snoozeNuxDialog,
|
||||||
|
upsertNux,
|
||||||
|
gate,
|
||||||
|
currentAccount,
|
||||||
|
currentProfile,
|
||||||
|
preferences,
|
||||||
|
])
|
||||||
|
|
||||||
const ctx = React.useMemo(() => {
|
const ctx = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -116,6 +177,7 @@ function Inner() {
|
||||||
return (
|
return (
|
||||||
<Context.Provider value={ctx}>
|
<Context.Provider value={ctx}>
|
||||||
{activeNux === Nux.TenMillionDialog && <TenMillion />}
|
{activeNux === Nux.TenMillionDialog && <TenMillion />}
|
||||||
|
{activeNux === Nux.NeueTypography && <NeueTypography />}
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const TextSize_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M9 5a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2h-5v14a1 1 0 1 1-2 0V6h-5a1 1 0 0 1-1-1Zm-3.073 7v8a1 1 0 1 0 2 0v-8H12a1 1 0 1 0 0-2H6.971a1.015 1.015 0 0 0-.089 0H2a1 1 0 1 0 0 2h3.927Z',
|
||||||
|
})
|
|
@ -0,0 +1,5 @@
|
||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const TitleCase_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M3.65 17.247c-.242.832-.632 1.178-1.325 1.178-.814 0-1.325-.476-1.325-1.23 0-.216.06-.51.173-.831L4.586 7.07c.364-1.014.979-1.482 1.966-1.482 1.022 0 1.629.45 2.001 1.473l3.43 9.303c.121.337.165.571.165.831 0 .72-.546 1.23-1.308 1.23-.736 0-1.126-.338-1.36-1.152l-.658-1.975H4.309l-.658 1.95ZM6.5 8.152l-1.62 5.12h3.335l-1.654-5.12H6.5Zm13.005 8.688c-.52.988-1.68 1.568-2.84 1.568-1.768 0-3.11-1.144-3.11-2.815 0-1.69 1.299-2.668 3.62-2.807l2.34-.138v-.615c0-.867-.607-1.369-1.56-1.369-.771 0-1.239.251-1.802.979-.277.312-.597.468-1.004.468-.615 0-1.057-.399-1.057-.97 0-.2.043-.382.13-.572.433-1.109 1.923-1.793 3.845-1.793 2.383 0 3.933 1.23 3.933 3.1v5.293c0 .84-.511 1.273-1.23 1.273-.684 0-1.16-.38-1.213-1.126v-.476h-.052Zm-3.43-1.386c0 .693.572 1.126 1.42 1.126 1.11 0 2.02-.719 2.02-1.723v-.676l-1.959.121c-.944.07-1.48.494-1.48 1.152Z',
|
||||||
|
})
|
|
@ -79,13 +79,13 @@ export const s = StyleSheet.create({
|
||||||
|
|
||||||
// font weights
|
// font weights
|
||||||
fw600: {fontWeight: '600'},
|
fw600: {fontWeight: '600'},
|
||||||
bold: {fontWeight: 'bold'},
|
bold: {fontWeight: '700'},
|
||||||
fw500: {fontWeight: '500'},
|
fw500: {fontWeight: '500'},
|
||||||
semiBold: {fontWeight: '500'},
|
semiBold: {fontWeight: '500'},
|
||||||
fw400: {fontWeight: '400'},
|
fw400: {fontWeight: '400'},
|
||||||
normal: {fontWeight: '400'},
|
normal: {fontWeight: '400'},
|
||||||
fw300: {fontWeight: '300'},
|
fw300: {fontWeight: '400'},
|
||||||
light: {fontWeight: '300'},
|
light: {fontWeight: '400'},
|
||||||
fw200: {fontWeight: '200'},
|
fw200: {fontWeight: '200'},
|
||||||
|
|
||||||
// text decoration
|
// text decoration
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
|
|
||||||
|
import {tokens} from '#/alf'
|
||||||
import {darkPalette, dimPalette, lightPalette} from '#/alf/themes'
|
import {darkPalette, dimPalette, lightPalette} from '#/alf/themes'
|
||||||
import {colors} from './styles'
|
import {colors} from './styles'
|
||||||
import type {Theme} from './ThemeContext'
|
import type {Theme} from './ThemeContext'
|
||||||
|
@ -88,163 +89,163 @@ export const defaultTheme: Theme = {
|
||||||
typography: {
|
typography: {
|
||||||
'2xl-thin': {
|
'2xl-thin': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'2xl': {
|
'2xl': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'2xl-medium': {
|
'2xl-medium': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'2xl-bold': {
|
'2xl-bold': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'2xl-heavy': {
|
'2xl-heavy': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
'xl-thin': {
|
'xl-thin': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
xl: {
|
xl: {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'xl-medium': {
|
'xl-medium': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'xl-bold': {
|
'xl-bold': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'xl-heavy': {
|
'xl-heavy': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
'lg-thin': {
|
'lg-thin': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'lg-medium': {
|
'lg-medium': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'lg-bold': {
|
'lg-bold': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'lg-heavy': {
|
'lg-heavy': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
'md-thin': {
|
'md-thin': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'md-medium': {
|
'md-medium': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'md-bold': {
|
'md-bold': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'md-heavy': {
|
'md-heavy': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
'sm-thin': {
|
'sm-thin': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'sm-medium': {
|
'sm-medium': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'sm-bold': {
|
'sm-bold': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'sm-heavy': {
|
'sm-heavy': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
'xs-thin': {
|
'xs-thin': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '300',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
xs: {
|
xs: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'xs-medium': {
|
'xs-medium': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'xs-bold': {
|
'xs-bold': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
'xs-heavy': {
|
'xs-heavy': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
|
|
||||||
'title-2xl': {
|
'title-2xl': {
|
||||||
fontSize: 34,
|
fontSize: 34,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'title-xl': {
|
'title-xl': {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
'title-lg': {
|
'title-lg': {
|
||||||
|
@ -254,32 +255,32 @@ export const defaultTheme: Theme = {
|
||||||
title: {
|
title: {
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
letterSpacing: 0.15,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
'title-sm': {
|
'title-sm': {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: 0.15,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
'post-text': {
|
'post-text': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.2,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'post-text-lg': {
|
'post-text-lg': {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
letterSpacing: 0.2,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'button-lg': {
|
'button-lg': {
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: 0.5,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: 0.5,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
mono: {
|
mono: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
|
@ -14,17 +14,21 @@ import {s} from '#/lib/styles'
|
||||||
import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
|
import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
|
||||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, native, useTheme} from '#/alf'
|
import {atoms as a, native, useAlf, useTheme} from '#/alf'
|
||||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||||
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
|
import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
|
||||||
import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
|
import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
|
||||||
|
import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
|
||||||
|
import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
|
||||||
export function AppearanceSettingsScreen({}: Props) {
|
export function AppearanceSettingsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
const {isTabletOrMobile} = useWebMediaQueries()
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
|
const {fonts} = useAlf()
|
||||||
|
|
||||||
const {colorMode, darkTheme} = useThemePrefs()
|
const {colorMode, darkTheme} = useThemePrefs()
|
||||||
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
||||||
|
@ -54,6 +58,22 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||||
[setDarkTheme, darkTheme],
|
[setDarkTheme, darkTheme],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onChangeFontFamily = useCallback(
|
||||||
|
(values: string[]) => {
|
||||||
|
const next = values[0] === 'system' ? 'system' : 'theme'
|
||||||
|
fonts.setFontFamily(next)
|
||||||
|
},
|
||||||
|
[fonts],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onChangeFontScale = useCallback(
|
||||||
|
(values: string[]) => {
|
||||||
|
const next = values[0] || ('0' as any)
|
||||||
|
fonts.setFontScale(next)
|
||||||
|
},
|
||||||
|
[fonts],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutAnimationConfig skipExiting skipEntering>
|
<LayoutAnimationConfig skipExiting skipEntering>
|
||||||
<View testID="preferencesThreadsScreen" style={s.hContentRegion}>
|
<View testID="preferencesThreadsScreen" style={s.hContentRegion}>
|
||||||
|
@ -71,65 +91,143 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||||
</View>
|
</View>
|
||||||
</SimpleViewHeader>
|
</SimpleViewHeader>
|
||||||
|
|
||||||
<View style={[a.p_xl, a.gap_lg]}>
|
<View style={[a.gap_3xl, a.pt_xl, a.px_xl]}>
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
<View style={[a.gap_lg]}>
|
||||||
<PhoneIcon style={t.atoms.text} />
|
<AppearanceToggleButtonGroup
|
||||||
<Text style={a.text_md}>
|
title={_(msg`Color mode`)}
|
||||||
<Trans>Mode</Trans>
|
icon={PhoneIcon}
|
||||||
</Text>
|
items={[
|
||||||
</View>
|
{
|
||||||
<ToggleButton.Group
|
label: _(msg`System`),
|
||||||
label={_(msg`Dark mode`)}
|
name: 'system',
|
||||||
values={[colorMode]}
|
},
|
||||||
onChange={onChangeAppearance}>
|
{
|
||||||
<ToggleButton.Button label={_(msg`System`)} name="system">
|
label: _(msg`Light`),
|
||||||
<ToggleButton.ButtonText>
|
name: 'light',
|
||||||
<Trans>System</Trans>
|
},
|
||||||
</ToggleButton.ButtonText>
|
{
|
||||||
</ToggleButton.Button>
|
label: _(msg`Dark`),
|
||||||
<ToggleButton.Button label={_(msg`Light`)} name="light">
|
name: 'dark',
|
||||||
<ToggleButton.ButtonText>
|
},
|
||||||
<Trans>Light</Trans>
|
]}
|
||||||
</ToggleButton.ButtonText>
|
values={[colorMode]}
|
||||||
</ToggleButton.Button>
|
onChange={onChangeAppearance}
|
||||||
<ToggleButton.Button label={_(msg`Dark`)} name="dark">
|
/>
|
||||||
<ToggleButton.ButtonText>
|
|
||||||
<Trans>Dark</Trans>
|
|
||||||
</ToggleButton.ButtonText>
|
|
||||||
</ToggleButton.Button>
|
|
||||||
</ToggleButton.Group>
|
|
||||||
{colorMode !== 'light' && (
|
|
||||||
<Animated.View
|
|
||||||
entering={native(FadeInDown)}
|
|
||||||
exiting={native(FadeOutDown)}
|
|
||||||
style={[a.mt_md, a.gap_lg]}>
|
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
|
||||||
<MoonIcon style={t.atoms.text} />
|
|
||||||
<Text style={a.text_md}>
|
|
||||||
<Trans>Dark theme</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<ToggleButton.Group
|
{colorMode !== 'light' && (
|
||||||
label={_(msg`Dark theme`)}
|
<Animated.View
|
||||||
values={[darkTheme ?? 'dim']}
|
entering={native(FadeInDown)}
|
||||||
onChange={onChangeDarkTheme}>
|
exiting={native(FadeOutDown)}>
|
||||||
<ToggleButton.Button label={_(msg`Dim`)} name="dim">
|
<AppearanceToggleButtonGroup
|
||||||
<ToggleButton.ButtonText>
|
title={_(msg`Dark theme`)}
|
||||||
<Trans>Dim</Trans>
|
icon={MoonIcon}
|
||||||
</ToggleButton.ButtonText>
|
items={[
|
||||||
</ToggleButton.Button>
|
{
|
||||||
<ToggleButton.Button label={_(msg`Dark`)} name="dark">
|
label: _(msg`Dim`),
|
||||||
<ToggleButton.ButtonText>
|
name: 'dim',
|
||||||
<Trans>Dark</Trans>
|
},
|
||||||
</ToggleButton.ButtonText>
|
{
|
||||||
</ToggleButton.Button>
|
label: _(msg`Dark`),
|
||||||
</ToggleButton.Group>
|
name: 'dark',
|
||||||
</Animated.View>
|
},
|
||||||
)}
|
]}
|
||||||
|
values={[darkTheme ?? 'dim']}
|
||||||
|
onChange={onChangeDarkTheme}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AppearanceToggleButtonGroup
|
||||||
|
title={_(msg`Font`)}
|
||||||
|
description={_(
|
||||||
|
msg`For the best experience, we recommend using the theme font.`,
|
||||||
|
)}
|
||||||
|
icon={Aa}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: _(msg`System`),
|
||||||
|
name: 'system',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Theme`),
|
||||||
|
name: 'theme',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
values={[fonts.family]}
|
||||||
|
onChange={onChangeFontFamily}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AppearanceToggleButtonGroup
|
||||||
|
title={_(msg`Font size`)}
|
||||||
|
icon={TextSize}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: _(msg`Smaller`),
|
||||||
|
name: '-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Default`),
|
||||||
|
name: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _(msg`Larger`),
|
||||||
|
name: '1',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
values={[fonts.scale]}
|
||||||
|
onChange={onChangeFontScale}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</LayoutAnimationConfig>
|
</LayoutAnimationConfig>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function AppearanceToggleButtonGroup({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon: Icon,
|
||||||
|
items,
|
||||||
|
values,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
icon: React.ComponentType<SVGIconProps>
|
||||||
|
items: {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
}[]
|
||||||
|
values: string[]
|
||||||
|
onChange: (values: string[]) => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<View style={[a.gap_xs]}>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||||
|
<Icon style={t.atoms.text} />
|
||||||
|
<Text style={[a.text_md, a.font_bold]}>{title}</Text>
|
||||||
|
</View>
|
||||||
|
{description && (
|
||||||
|
<Text
|
||||||
|
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<ToggleButton.Group label={title} values={values} onChange={onChange}>
|
||||||
|
{items.map(item => (
|
||||||
|
<ToggleButton.Button
|
||||||
|
key={item.name}
|
||||||
|
label={item.label}
|
||||||
|
name={item.name}>
|
||||||
|
<ToggleButton.ButtonText>{item.label}</ToggleButton.ButtonText>
|
||||||
|
</ToggleButton.Button>
|
||||||
|
))}
|
||||||
|
</ToggleButton.Group>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -4,15 +4,22 @@ import {BaseNux} from '#/state/queries/nuxs/types'
|
||||||
|
|
||||||
export enum Nux {
|
export enum Nux {
|
||||||
TenMillionDialog = 'TenMillionDialog',
|
TenMillionDialog = 'TenMillionDialog',
|
||||||
|
NeueTypography = 'NeueTypography',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nuxNames = new Set(Object.values(Nux))
|
export const nuxNames = new Set(Object.values(Nux))
|
||||||
|
|
||||||
export type AppNux = BaseNux<{
|
export type AppNux =
|
||||||
id: Nux.TenMillionDialog
|
| BaseNux<{
|
||||||
data: undefined
|
id: Nux.TenMillionDialog
|
||||||
}>
|
data: undefined
|
||||||
|
}>
|
||||||
|
| BaseNux<{
|
||||||
|
id: Nux.NeueTypography
|
||||||
|
data: undefined
|
||||||
|
}>
|
||||||
|
|
||||||
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
||||||
[Nux.TenMillionDialog]: undefined,
|
[Nux.TenMillionDialog]: undefined,
|
||||||
|
[Nux.NeueTypography]: undefined,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import {MMKV} from 'react-native-mmkv'
|
||||||
|
|
||||||
import {Device} from '#/storage/schema'
|
import {Device} from '#/storage/schema'
|
||||||
|
|
||||||
|
export * from '#/storage/schema'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic storage class. DO NOT use this directly. Instead, use the exported
|
* Generic storage class. DO NOT use this directly. Instead, use the exported
|
||||||
* storage instances below.
|
* storage instances below.
|
||||||
|
|
|
@ -2,5 +2,7 @@
|
||||||
* Device data that's specific to the device and does not vary based account
|
* Device data that's specific to the device and does not vary based account
|
||||||
*/
|
*/
|
||||||
export type Device = {
|
export type Device = {
|
||||||
|
fontScale: '-2' | '-1' | '0' | '1' | '2'
|
||||||
|
fontFamily: 'system' | 'theme'
|
||||||
lastNuxDialog: string | undefined
|
lastNuxDialog: string | undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Text as RNText, TextProps} from 'react-native'
|
import {StyleSheet, Text as RNText, TextProps} from 'react-native'
|
||||||
import {UITextView} from 'react-native-uitextview'
|
import {UITextView} from 'react-native-uitextview'
|
||||||
|
|
||||||
import {lh, s} from 'lib/styles'
|
import {lh, s} from 'lib/styles'
|
||||||
import {TypographyVariant, useTheme} from 'lib/ThemeContext'
|
import {TypographyVariant, useTheme} from 'lib/ThemeContext'
|
||||||
import {isIOS, isWeb} from 'platform/detection'
|
import {isIOS, isWeb} from 'platform/detection'
|
||||||
|
import {applyFonts, useAlf} from '#/alf'
|
||||||
|
|
||||||
export type CustomTextProps = TextProps & {
|
export type CustomTextProps = TextProps & {
|
||||||
type?: TypographyVariant
|
type?: TypographyVariant
|
||||||
|
@ -32,11 +33,28 @@ export function Text({
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const typography = theme.typography[type]
|
const typography = theme.typography[type]
|
||||||
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
|
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
|
||||||
|
const {fonts} = useAlf()
|
||||||
|
|
||||||
if (selectable && isIOS) {
|
if (selectable && isIOS) {
|
||||||
|
const flattened = StyleSheet.flatten([
|
||||||
|
s.black,
|
||||||
|
typography,
|
||||||
|
lineHeightStyle,
|
||||||
|
style,
|
||||||
|
])
|
||||||
|
|
||||||
|
applyFonts(flattened, fonts.family)
|
||||||
|
|
||||||
|
// should always be defined on `typography`
|
||||||
|
// @ts-ignore
|
||||||
|
if (flattened.fontSize) {
|
||||||
|
// @ts-ignore
|
||||||
|
flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UITextView
|
<UITextView
|
||||||
style={[s.black, typography, lineHeightStyle, style]}
|
style={flattened}
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
uiTextView
|
uiTextView
|
||||||
{...props}>
|
{...props}>
|
||||||
|
@ -45,15 +63,26 @@ export function Text({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flattened = StyleSheet.flatten([
|
||||||
|
s.black,
|
||||||
|
typography,
|
||||||
|
isWeb && fontFamilyStyle,
|
||||||
|
lineHeightStyle,
|
||||||
|
style,
|
||||||
|
])
|
||||||
|
|
||||||
|
applyFonts(flattened, fonts.family)
|
||||||
|
|
||||||
|
// should always be defined on `typography`
|
||||||
|
// @ts-ignore
|
||||||
|
if (flattened.fontSize) {
|
||||||
|
// @ts-ignore
|
||||||
|
flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RNText
|
<RNText
|
||||||
style={[
|
style={flattened}
|
||||||
s.black,
|
|
||||||
typography,
|
|
||||||
isWeb && fontFamilyStyle,
|
|
||||||
lineHeightStyle,
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
// @ts-ignore web only -esb
|
// @ts-ignore web only -esb
|
||||||
dataSet={Object.assign({tooltip: title}, dataSet || {})}
|
dataSet={Object.assign({tooltip: title}, dataSet || {})}
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
|
|
|
@ -12219,6 +12219,13 @@ expo-file-system@^17.0.1, expo-file-system@~17.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-17.0.1.tgz#b9f8af8c1c06ec71d96fd7a0d2567fa9e1c88f15"
|
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-17.0.1.tgz#b9f8af8c1c06ec71d96fd7a0d2567fa9e1c88f15"
|
||||||
integrity sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==
|
integrity sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==
|
||||||
|
|
||||||
|
expo-font@~12.0.10:
|
||||||
|
version "12.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.10.tgz#62deaf1f46159d7839f01305f44079268781b1db"
|
||||||
|
integrity sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==
|
||||||
|
dependencies:
|
||||||
|
fontfaceobserver "^2.1.0"
|
||||||
|
|
||||||
expo-font@~12.0.5:
|
expo-font@~12.0.5:
|
||||||
version "12.0.5"
|
version "12.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.5.tgz#3451c2bd3f98859b127a6484d3474a292889b93f"
|
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.5.tgz#3451c2bd3f98859b127a6484d3474a292889b93f"
|
||||||
|
|
Loading…
Reference in New Issue