diff --git a/src/App.web.tsx b/src/App.web.tsx index 6c67dc28ba..cdba2c61e2 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -7,6 +7,7 @@ import {RootSiblingParent} from 'react-native-root-siblings' import 'view/icons' +import {ThemeProvider as Nova} from '#/view/nova' import {init as initPersistedState} from '#/state/persisted' import {useColorMode} from 'state/shell' import {Shell} from 'view/shell/index' @@ -28,11 +29,13 @@ import { } from 'state/session' import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import * as persisted from '#/state/persisted' +import {useColorModeTheme} from '#/view/nova/util/useColorModeTheme' function InnerApp() { const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() const colorMode = useColorMode() + const theme = useColorModeTheme(colorMode) // init useEffect(() => { @@ -44,23 +47,25 @@ function InnerApp() { if (isInitialLoad) return null return ( - - - - - {/* All components should be within this provider */} - - - - - - - - - - + + + + + + {/* All components should be within this provider */} + + + + + + + + + + + ) } diff --git a/src/lib/ThemeContext.tsx b/src/lib/ThemeContext.tsx index 483c50c42d..a728a797a9 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -1,13 +1,7 @@ -import {isWeb} from 'platform/detection' import React, {ReactNode, createContext, useContext} from 'react' -import { - AppState, - TextStyle, - useColorScheme as useColorScheme_BUGGY, - ViewStyle, - ColorSchemeName, -} from 'react-native' +import {TextStyle, ViewStyle, ColorSchemeName} from 'react-native' import {darkTheme, defaultTheme} from './themes' +import {useColorScheme_FIXED} from '#/lib/hooks/useColorScheme_FIXED' export type ColorScheme = 'light' | 'dark' @@ -97,32 +91,6 @@ function getTheme(theme: ColorSchemeName) { return theme === 'dark' ? darkTheme : defaultTheme } -/** - * With RN iOS, we can only "trust" the color scheme reported while the app is - * active. This is a workaround until the bug is fixed upstream. - * - * @see https://github.com/bluesky-social/social-app/pull/1417#issuecomment-1719868504 - * @see https://github.com/facebook/react-native/pull/39439 - */ -function useColorScheme_FIXED() { - const colorScheme = useColorScheme_BUGGY() - const [currentColorScheme, setCurrentColorScheme] = - React.useState(colorScheme) - - React.useEffect(() => { - // we don't need to be updating state on web - if (isWeb) return - const subscription = AppState.addEventListener('change', state => { - const isActive = state === 'active' - if (!isActive) return - setCurrentColorScheme(colorScheme) - }) - return () => subscription.remove() - }, [colorScheme]) - - return isWeb ? colorScheme : currentColorScheme -} - export const ThemeProvider: React.FC = ({ theme, children, diff --git a/src/lib/hooks/useColorScheme_FIXED.ts b/src/lib/hooks/useColorScheme_FIXED.ts new file mode 100644 index 0000000000..46b24979c5 --- /dev/null +++ b/src/lib/hooks/useColorScheme_FIXED.ts @@ -0,0 +1,34 @@ +import React from 'react' +import { + AppState, + useColorScheme as useColorScheme_BUGGY, + ColorSchemeName, +} from 'react-native' + +import {isWeb} from '#/platform/detection' + +/** + * With RN iOS, we can only "trust" the color scheme reported while the app is + * active. This is a workaround until the bug is fixed upstream. + * + * @see https://github.com/bluesky-social/social-app/pull/1417#issuecomment-1719868504 + * @see https://github.com/facebook/react-native/pull/39439 + */ +export function useColorScheme_FIXED() { + const colorScheme = useColorScheme_BUGGY() + const [currentColorScheme, setCurrentColorScheme] = + React.useState(colorScheme) + + React.useEffect(() => { + // we don't need to be updating state on web + if (isWeb) return + const subscription = AppState.addEventListener('change', state => { + const isActive = state === 'active' + if (!isActive) return + setCurrentColorScheme(colorScheme) + }) + return () => subscription.remove() + }, [colorScheme]) + + return isWeb ? colorScheme : currentColorScheme +} diff --git a/src/view/nova/components/Box.tsx b/src/view/nova/components/Box.tsx new file mode 100644 index 0000000000..4743bff894 --- /dev/null +++ b/src/view/nova/components/Box.tsx @@ -0,0 +1,3 @@ +import {View} from 'react-native' +import {styled} from '#/view/nova/system' +export const Box = styled(View) diff --git a/src/view/nova/components/Button.tsx b/src/view/nova/components/Button.tsx new file mode 100644 index 0000000000..ae18cb7835 --- /dev/null +++ b/src/view/nova/components/Button.tsx @@ -0,0 +1,9 @@ +import {Pressable} from 'react-native' +import {styled} from '#/view/nova/system' + +export const Button = styled(Pressable, { + px: 'l', + py: 's', + bg: 'l3', + radius: 40, +}) diff --git a/src/view/nova/components/Typography.tsx b/src/view/nova/components/Typography.tsx new file mode 100644 index 0000000000..627f6b8078 --- /dev/null +++ b/src/view/nova/components/Typography.tsx @@ -0,0 +1,74 @@ +import {Text as RNText} from 'react-native' +import {styled} from '#/view/nova/system' +import {web} from '#/view/nova/util/platform' + +export const Text = styled(RNText, { + color: 'l8', + fontSize: 's', +}) + +/** + * @see https://necolas.github.io/react-native-web/docs/accessibility/#semantic-html + * @see https://docs.expo.dev/develop/user-interface/fonts/ + */ +export const H1 = styled(RNText, { + color: 'l8', + fontSize: 'l', + gtMobile: { + fontSize: 'xl', + }, + ...web({ + role: 'heading', + 'aria-level': 1, + }), +}) +export const H2 = styled(RNText, { + color: 'l8', + fontSize: 'm', + gtMobile: { + fontSize: 'l', + }, + ...web({ + role: 'heading', + 'aria-level': 2, + }), +}) +export const H3 = styled(RNText, { + color: 'l8', + fontSize: 'm', + ...web({ + role: 'heading', + 'aria-level': 3, + }), +}) +export const H4 = styled(RNText, { + color: 'l8', + fontSize: 's', + ...web({ + role: 'heading', + 'aria-level': 4, + }), +}) +export const H5 = styled(RNText, { + color: 'l8', + fontSize: 'xs', + ...web({ + role: 'heading', + 'aria-level': 5, + }), +}) +export const H6 = styled(RNText, { + color: 'l8', + fontSize: 'xxs', + ...web({ + role: 'heading', + 'aria-level': 6, + }), +}) +export const P = styled(RNText, { + color: 'l8', + fontSize: 's', + ...web({ + role: 'paragraph', + }), +}) diff --git a/src/view/nova/index.tsx b/src/view/nova/index.tsx index 8123f0d586..34a7cdf5e2 100644 --- a/src/view/nova/index.tsx +++ b/src/view/nova/index.tsx @@ -1,102 +1,4 @@ -import {View, Text as RNText} from 'react-native' - -import {createSystem} from './lib/system' -import {light, dark} from './themes' -import {web} from './lib/utils' - -export * from './lib/utils' - -const { - ThemeProvider, - useTheme, - useTokens, - useBreakpoints, - useStyle, - useStyles, - styled, -} = createSystem({ - light, - dark, -}) - -export { - ThemeProvider, - useTheme, - useTokens, - useBreakpoints, - useStyle, - useStyles, - styled, -} - -export const Box = styled(View) -export const Text = styled(RNText, { - color: 'l8', - fontSize: 's', -}) - -/** - * @see https://necolas.github.io/react-native-web/docs/accessibility/#semantic-html - * @see https://docs.expo.dev/develop/user-interface/fonts/ - */ -export const H1 = styled(RNText, { - color: 'l8', - fontSize: 'l', - gtMobile: { - fontSize: 'xl', - }, - ...web({ - role: 'heading', - 'aria-level': 1, - }), -}) -export const H2 = styled(RNText, { - color: 'l8', - fontSize: 'm', - gtMobile: { - fontSize: 'l', - }, - ...web({ - role: 'heading', - 'aria-level': 2, - }), -}) -export const H3 = styled(RNText, { - color: 'l8', - fontSize: 'm', - ...web({ - role: 'heading', - 'aria-level': 3, - }), -}) -export const H4 = styled(RNText, { - color: 'l8', - fontSize: 's', - ...web({ - role: 'heading', - 'aria-level': 4, - }), -}) -export const H5 = styled(RNText, { - color: 'l8', - fontSize: 'xs', - ...web({ - role: 'heading', - 'aria-level': 5, - }), -}) -export const H6 = styled(RNText, { - color: 'l8', - fontSize: 'xxs', - ...web({ - role: 'heading', - 'aria-level': 6, - }), -}) -export const P = styled(RNText, { - color: 'l8', - fontSize: 's', - ...web({ - role: 'paragraph', - }), -}) +export * from '#/view/nova/system' +export * from '#/view/nova/components/Box' +export * from '#/view/nova/components/Typography' +export * from '#/view/nova/components/Button' diff --git a/src/view/nova/lib/system.tsx b/src/view/nova/lib/system.tsx index adaf7e2a8e..dc0219eb89 100644 --- a/src/view/nova/lib/system.tsx +++ b/src/view/nova/lib/system.tsx @@ -55,11 +55,14 @@ export function createSystem< theme, }: React.PropsWithChildren<{theme: ThemeName}>) => ( + value={React.useMemo( + () => ({ + themeName: theme, + theme: themes[theme], + themes, + }), + [theme], + )}> {children} ) @@ -74,7 +77,7 @@ export function createSystem< function useBreakpoints() { const {theme} = useTheme() - const [breakpoints, setBreakpoints] = React.useState( + const [breakpoints, setBreakpoints] = React.useState(() => theme.getActiveBreakpoints({width: Dimensions.get('window').width}), ) diff --git a/src/view/nova/system.ts b/src/view/nova/system.ts new file mode 100644 index 0000000000..98e0f499b8 --- /dev/null +++ b/src/view/nova/system.ts @@ -0,0 +1,28 @@ +/** + * Library imports + */ +import {createSystem} from './lib/system' +import {light, dark} from './themes' + +const { + ThemeProvider, + useTheme, + useTokens, + useBreakpoints, + useStyle, + useStyles, + styled, +} = createSystem({ + light, + dark, +}) + +export { + ThemeProvider, + useTheme, + useTokens, + useBreakpoints, + useStyle, + useStyles, + styled, +} diff --git a/src/view/nova/themes.ts b/src/view/nova/themes.ts index 1f60621b75..01b3641a74 100644 --- a/src/view/nova/themes.ts +++ b/src/view/nova/themes.ts @@ -16,6 +16,15 @@ const palette = { export const light = createTheme({ tokens: { + space: { + xxs: 2, + xs: 4, + s: 8, + m: 12, + l: 18, + xl: 24, + xxl: 32, + }, color: { primary: palette.blue, l1: palette.white, @@ -118,7 +127,7 @@ export const light = createTheme({ /** * Shorthand for applying `fontSize` and `fontHeight`, according to our type scale. */ - fontSize(value: 'xs' | 's' | 'm' | 'l' | 'xl', tokens) { + fontSize(value: 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl', tokens) { return { fontSize: tokens.fontSize[value], lineHeight: tokens.lineHeight[value], diff --git a/src/view/nova/lib/utils.ts b/src/view/nova/util/platform.ts similarity index 100% rename from src/view/nova/lib/utils.ts rename to src/view/nova/util/platform.ts diff --git a/src/view/nova/util/useColorModeTheme.ts b/src/view/nova/util/useColorModeTheme.ts new file mode 100644 index 0000000000..4d56332b38 --- /dev/null +++ b/src/view/nova/util/useColorModeTheme.ts @@ -0,0 +1,9 @@ +import * as persisted from '#/state/persisted' +import {useColorScheme_FIXED} from '#/lib/hooks/useColorScheme_FIXED' + +export function useColorModeTheme( + theme: persisted.Schema['colorMode'], +): 'light' | 'dark' { + const colorScheme = useColorScheme_FIXED() + return (theme === 'system' ? colorScheme : theme) || 'light' +}