Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unistyles provider with no breaking changes #337

Merged
merged 3 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/expo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const App: React.FunctionComponent = () => (
<Stack.Screen name={DemoNames.NoThemes} component={Screens.NoThemesScreen} />
<Stack.Screen name={DemoNames.SingleTheme} component={Screens.SingleThemeScreen} />
<Stack.Screen name={DemoNames.TwoThemes} component={Screens.TwoThemesScreen} />
<Stack.Screen name={DemoNames.TwoThemesWithProvider} component={Screens.TwoThemesWithProviderScreen} />
<Stack.Screen name={DemoNames.LightDarkThemes} component={Screens.LightDarkThemesScreen} />
<Stack.Screen name={DemoNames.MultipleThemes} component={Screens.MultipleThemesScreen} />
<Stack.Screen name={DemoNames.MultipleThemesAdaptive} component={Screens.MultipleThemesAdaptiveScreen} />
Expand Down
4 changes: 3 additions & 1 deletion examples/expo/src/common/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export enum DemoNames {
UpdateTheme = 'UpdateThemeScreen',
AndroidStatusBarNavigationBar = 'AndroidStatusBarNavigationBarScreen',
Layout = 'LayoutScreen',
Keyboard = 'KeyboardScreen'
Keyboard = 'KeyboardScreen',
TwoThemesWithProvider = 'TwoThemesWithProvider',
}

export type DemoStackParams = {
Expand Down Expand Up @@ -66,6 +67,7 @@ export type DemoStackParams = {
[DemoNames.AndroidStatusBarNavigationBar]: undefined,
[DemoNames.Layout]: undefined,
[DemoNames.Keyboard]: undefined
[DemoNames.TwoThemesWithProvider]: undefined,
}

export type NavigationProps<S extends DemoNames = DemoNames.Home> = NavigationProp<DemoStackParams, S>
17 changes: 17 additions & 0 deletions examples/expo/src/examples/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ export const HomeScreen = () => {
navigation.navigate(DemoNames.TwoThemes)
}}
/>
<DemoLink
description="Two themes with provider"
onPress={() => {
UnistylesRegistry
.addThemes({
light: lightTheme,
premium: premiumTheme
// we need to cast it to UnistylesThemes as we already registered 3 themes with TypeScript under styles/index.ts,
// but we want to demonstrate how to register two themes
} as UnistylesThemes)
.addConfig({
initialTheme: 'light'
})

navigation.navigate(DemoNames.TwoThemesWithProvider)
}}
/>
<DemoLink
description="Light/Dark themes"
onPress={() => {
Expand Down
60 changes: 60 additions & 0 deletions examples/expo/src/examples/TwoThemesWithProviderScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react'
import { Text, View } from 'react-native'
import { createStyleSheet, useStyles, UnistylesRuntime, useInitialTheme, UnistylesProvider } from 'react-native-unistyles'
import { Button, DemoScreen } from '../components'

export const TwoThemesWithProviderScreen: React.FunctionComponent = () => {
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved
return (
<UnistylesProvider>
<TwoThemesWithProviderScreenContent />
</UnistylesProvider>
)
}
const TwoThemesWithProviderScreenContent: React.FunctionComponent = () => {
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved
// if you have 2 or more themes, you need to select one of them
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved
// keep in mind that everything is synchronous and so fast, that you can set it in the same screen 🔥
// two more notes:
// call it before useStyles
// if you won't do that you will get UNISTYLES_THEME_NOT_FOUND error
useInitialTheme('premium')

// you can also skip useInitialTheme and set the theme during UnistylesRegistry.addConfig({}) call

const { styles, theme } = useStyles(stylesheet)

return (
<DemoScreen>
<View style={styles.container}>
<Text style={styles.text}>
This screen has two themes registered with `UnistylesRegistry.addThemes` function.
</Text>
<Text style={styles.text}>
It also shows a way to switch the theme from anywhere of your app. You can do that importing `UnistylesRuntime` and calling `setTheme` function.
</Text>
<Text style={styles.text}>
This screen uses {UnistylesRuntime.themeName} theme.
</Text>
<Button
title="Switch theme"
color={theme.colors.accent}
onPress={() => UnistylesRuntime.setTheme(UnistylesRuntime.themeName === 'light' ? 'premium' : 'light')}
/>
</View>
</DemoScreen>
)
}

const stylesheet = createStyleSheet(theme => ({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
backgroundColor: theme.colors.backgroundColor,
rowGap: 20
},
text: {
textAlign: 'center',
color: theme.colors.typography
}
}))
1 change: 1 addition & 0 deletions examples/expo/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export { UpdateThemeScreen } from './UpdateThemeScreen'
export { AndroidStatusBarNavigationBarScreen } from './AndroidStatusBarNavigationBarScreen'
export { LayoutScreen } from './LayoutScreen'
export { KeyboardScreen } from './KeyboardScreen'
export { TwoThemesWithProviderScreen } from './TwoThemesWithProviderScreen'
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved
91 changes: 91 additions & 0 deletions src/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useEffect, useState } from 'react'
import type { UnistylesBreakpoints } from './global'
import { unistyles } from './core'
import type {
ScreenDimensions,
ScreenInsets,
ScreenSize,
UnistylesEvents,
UnistylesMobileLayoutEvent,
UnistylesTheme,
UnistylesThemeEvent
} from './types'
import { UnistylesEventType } from './common'
import { NativeEventEmitter, NativeModules } from 'react-native'
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved

export type TUnistylesContext = {
plugins: Array<string>;
theme: UnistylesTheme;
layout: {
screen: ScreenSize;
statusBar: ScreenDimensions;
navigationBar: ScreenDimensions;
insets: ScreenInsets;
breakpoint: keyof UnistylesBreakpoints;
orientation: 'landscape' | 'portrait';
jpudysz marked this conversation as resolved.
Show resolved Hide resolved
};
}

const unistylesEvents = new NativeEventEmitter(NativeModules.Unistyles)

export const UnistylesContext = React.createContext<TUnistylesContext | undefined>(undefined)

export const UnistylesProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState(unistyles.registry.getTheme(unistyles.runtime.themeName))
const [plugins, setPlugins] = useState(unistyles.runtime.enabledPlugins)
const [layout, setLayout] = useState({
breakpoint: unistyles.runtime.breakpoint,
orientation: unistyles.runtime.orientation,
screen: {
width: unistyles.runtime.screen.width,
height: unistyles.runtime.screen.height
},
statusBar: {
width: unistyles.runtime.statusBar.width,
height: unistyles.runtime.statusBar.height
},
navigationBar: {
width: unistyles.runtime.navigationBar.width,
height: unistyles.runtime.navigationBar.height
},
insets: {
top: unistyles.runtime.insets.top,
bottom: unistyles.runtime.insets.bottom,
left: unistyles.runtime.insets.left,
right: unistyles.runtime.insets.right
}
})

useEffect(() => {
const subscription = unistylesEvents.addListener('__unistylesOnChange', (event: UnistylesEvents) => {
switch (event.type) {
case UnistylesEventType.Theme: {
const themeEvent = event as UnistylesThemeEvent

return setTheme(unistyles.registry.getTheme(themeEvent.payload.themeName))
}
case UnistylesEventType.Layout: {
const layoutEvent = event as UnistylesMobileLayoutEvent

return setLayout({
breakpoint: layoutEvent.payload.breakpoint,
orientation: layoutEvent.payload.orientation,
screen: layoutEvent.payload.screen,
statusBar: layoutEvent.payload.statusBar,
insets: layoutEvent.payload.insets,
navigationBar: layoutEvent.payload.navigationBar
})
}
case UnistylesEventType.Plugin: {
return setPlugins(unistyles.runtime.enabledPlugins)
}
default:
return
}
})

return subscription.remove
}, [])

return <UnistylesContext.Provider value={{ theme, layout, plugins }}>{children}</UnistylesContext.Provider>
yousefelgoharyx marked this conversation as resolved.
Show resolved Hide resolved
}
16 changes: 15 additions & 1 deletion src/hooks/useUnistyles.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { NativeEventEmitter, NativeModules } from 'react-native'
import { useEffect, useState } from 'react'
import { useContext, useEffect, useState } from 'react'
import { unistyles } from '../core'
import { UnistylesEventType } from '../common'
import type { UnistylesEvents, UnistylesMobileLayoutEvent, UnistylesThemeEvent } from '../types'
import { UnistylesContext } from '../context'

const unistylesEvents = new NativeEventEmitter(NativeModules.Unistyles)

export const useUnistyles = () => {
const unistylesContext = useContext(UnistylesContext);
const [plugins, setPlugins] = useState(unistyles.runtime.enabledPlugins)
const [theme, setTheme] = useState(unistyles.registry.getTheme(unistyles.runtime.themeName))
const [layout, setLayout] = useState({
Expand All @@ -33,6 +35,10 @@ export const useUnistyles = () => {
})

useEffect(() => {
if (unistylesContext !== undefined) {
return
}

const subscription = unistylesEvents.addListener(
'__unistylesOnChange',
(event: UnistylesEvents) => {
Expand Down Expand Up @@ -66,6 +72,14 @@ export const useUnistyles = () => {
return subscription.remove
}, [])

if (unistylesContext !== undefined) {
jpudysz marked this conversation as resolved.
Show resolved Hide resolved
return {
plugins: unistylesContext.plugins,
theme: unistylesContext.theme,
layout: unistylesContext.layout
}
}

return {
plugins,
theme,
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { UnistylesThemes, UnistylesBreakpoints } from './global'
import { ScreenOrientation, AndroidContentSizeCategory, IOSContentSizeCategory } from './common'
import { useStyles } from './useStyles'
import { createStyleSheet } from './createStyleSheet'

import { UnistylesProvider } from './context'
/**
* Utility to interact with the Unistyles
* (should be called only once)
Expand Down Expand Up @@ -40,7 +40,8 @@ export {
AndroidContentSizeCategory,
IOSContentSizeCategory,
UnistylesRegistry,
UnistylesRuntime
UnistylesRuntime,
UnistylesProvider
}

export type {
Expand Down
Loading