Skip to content

Commit

Permalink
Remove react-native-appearance dependency and add more test
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrozadotdev committed Apr 29, 2021
1 parent cfae018 commit 61cdf43
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 158 deletions.
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,23 @@

A package that allows you to create React Native StyleSheets with support for Dark/Light/Auto Themes.

- Depends on react-native-appearance to choose the theme based on OS preference(Android 10/iOS 13)
- Simple API
- Fully typed
- Builds on top of StyleSheets and Hooks
- Storybook addon to change Theme Mode

## Installation

**Using Expo**

```
expo install react-native-appearance react-native-themed-stylesheet
```

**Using Yarn**

```
yarn add react-native-appearance react-native-themed-stylesheet
yarn add react-native-themed-stylesheet
```

**Using NPM**

```
npm install --save react-native-appearance react-native-themed-stylesheet
npm install --save react-native-themed-stylesheet
```

## Usage
Expand Down
52 changes: 0 additions & 52 deletions __mocks__/react-native-appearance.tsx

This file was deleted.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-themed-stylesheet",
"version": "0.3.2",
"version": "0.3.3",
"description": "React Native StyleSheets with Theming Support",
"author": "Andre Pedroza",
"license": "MIT",
Expand Down Expand Up @@ -29,8 +29,7 @@
"peerDependencies": {
"@storybook/addons": "*",
"react": "*",
"react-native": "*",
"react-native-appearance": "*"
"react-native": "*"
},
"devDependencies": {
"@babel/core": "^7.12.16",
Expand All @@ -52,7 +51,6 @@
"jest": "^26.6.3",
"react": "16.13.1",
"react-native": "0.63.4",
"react-native-appearance": "~0.3.3",
"react-test-renderer": "16.13.1",
"ts-jest": "^26.5.1",
"typescript": "~4.0.0"
Expand Down
58 changes: 19 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import React, {
useEffect,
useState
} from 'react'
import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from 'react-native'
import { Appearance, AppearanceProvider } from 'react-native-appearance'
import {
ImageStyle,
StyleSheet,
TextStyle,
useColorScheme,
ViewStyle
} from 'react-native'
import merge from 'ts-deepmerge'

export interface BaseTheme {}
Expand Down Expand Up @@ -67,7 +72,7 @@ type ThemeProviderProps = {

const themeContext = createContext<ThemeContext>({} as ThemeContext)

const generateTheme = (mode: ThemeMode, themes: Themes) => {
const generateTheme = (mode: Exclude<ThemeMode, 'auto'>, themes: Themes) => {
if (
typeof themes !== 'object' ||
!themes ||
Expand All @@ -78,42 +83,27 @@ const generateTheme = (mode: ThemeMode, themes: Themes) => {
) {
return {}
}
const constants = 'constants' in themes ? themes['constants'] : {}
const systemColorScheme = Appearance.getColorScheme()
const currentMode =
mode !== 'auto'
? mode
: systemColorScheme !== 'no-preference'
? systemColorScheme
: 'light'
return merge(constants, themes[currentMode])
const constants = ('constants' in themes ? themes['constants'] : {}) || {}
return merge(constants, themes[mode])
}

const RawThemeProvider: React.FC<ThemeProviderProps> = ({
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
children,
mode: initialMode,
themes: initialThemes
}) => {
const colorScheme = useColorScheme()
const [mode, setMode] = useState(initialMode ?? 'auto')
const [themes, setThemes] = useState(initialThemes)
const [theme, setTheme] = useState(generateTheme(mode, themes))
const [theme, setTheme] = useState(
generateTheme(mode !== 'auto' ? mode : colorScheme || 'light', themes)
)
useEffect(() => {
let subscription: any
setTheme(generateTheme(mode, themes))
if (mode === 'auto') {
subscription = Appearance.addChangeListener(({ colorScheme }) => {
setTheme(
generateTheme(
colorScheme !== 'no-preference' ? colorScheme : 'light',
themes
)
)
})
}
return () => {
subscription && subscription.remove()
}
mode !== 'auto' && setTheme(generateTheme(mode, themes))
}, [mode, themes])
useEffect(() => {
mode === 'auto' && setTheme(generateTheme(colorScheme || 'light', themes))
}, [colorScheme, mode, themes])
return createElement(
themeContext.Provider,
{
Expand All @@ -129,16 +119,6 @@ const RawThemeProvider: React.FC<ThemeProviderProps> = ({
)
}

export const ThemeProvider: typeof RawThemeProvider = ({
children,
...props
}) =>
createElement(
AppearanceProvider,
null,
createElement(RawThemeProvider, props, children)
)

export const useMode: UseMode = () => {
const { mode, setMode } = useContext(themeContext)
return [mode, setMode]
Expand Down
14 changes: 14 additions & 0 deletions tests/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useState } from 'react'
import * as ReactNative from 'react-native'
import { Appearance } from '../fixture'

// eslint-disable-next-line import/export
export * from 'react-native'

export const useColorScheme = () => {
const [value, setValue] = useState<'dark' | 'light' | null>(null)
Appearance.listener = setValue
return value
}

export default Object.setPrototypeOf({ useColorScheme }, ReactNative)
13 changes: 13 additions & 0 deletions tests/fixture.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
type AppearanceType = {
listener: null | ((v: 'dark' | 'light' | null) => void)
set: (v: 'dark' | 'light' | null) => void
}

export const Appearance: AppearanceType = {
listener: null,
set (v) {
typeof this.listener === 'function' && this.listener(v)
this.listener = null
}
}

export const themes1 = {
light: {
colors: {
Expand Down
14 changes: 4 additions & 10 deletions tests/with-constants.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { createElement } from 'react'
import { TextStyle } from 'react-native'
import { renderHook, act } from '@testing-library/react-hooks/native'
import { Appearance } from '../__mocks__/react-native-appearance'
import {
ThemeProvider,
useCreateStyles,
useMode,
useTheme,
useThemes
} from '../src/index'
import { createStyles, themes1, themes2 } from './fixture'

import { Appearance, createStyles, themes1, themes2 } from './fixture'
declare module '../src/index' {
type Themes1 = typeof themes1
export interface BaseTheme extends Themes1 {}
}

beforeEach(() => {
Appearance.preference = 'no-preference'
})

describe('Auto Mode', () => {
const wrapper: typeof ThemeProvider = ({ children }) =>
createElement(ThemeProvider, { themes: themes1 }, children)
Expand Down Expand Up @@ -64,14 +58,14 @@ describe('Change System Preference', () => {
test('To "no-preference" to Get Light Mode', () => {
const { result } = renderHook(() => useTheme(), { wrapper })
act(() => {
Appearance.preference = 'no-preference'
Appearance.set(null)
})
expect(result.current.colors.primary).toEqual('#a1a1a1')
})
test('To Dark Mode', () => {
const { result } = renderHook(() => useTheme(), { wrapper })
act(() => {
Appearance.preference = 'dark'
Appearance.set('dark')
})
expect(result.current.colors.primary).toEqual('#a2a2a2')
})
Expand All @@ -85,7 +79,7 @@ describe('Change System Preference', () => {
{ wrapper }
)
act(() => {
Appearance.preference = 'light'
Appearance.set('light')
result.current.setMode('dark')
})
expect(result.current.theme.colors.primary).toEqual('#a2a2a2')
Expand Down
21 changes: 16 additions & 5 deletions tests/with-exceptions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { createElement } from 'react'
import { renderHook } from '@testing-library/react-hooks/native'
import { Appearance } from '../__mocks__/react-native-appearance'
import { ThemeProvider, useTheme } from '../src/index'

beforeEach(() => {
Appearance.preference = 'no-preference'
})

describe('With Exceptions', () => {
test('Themes as null', () => {
const wrapper: typeof ThemeProvider = ({ children }) =>
Expand Down Expand Up @@ -56,4 +51,20 @@ describe('With Exceptions', () => {
const { result } = renderHook(() => useTheme(), { wrapper })
expect(Object.keys(result.current).length).toEqual(0)
})
test('Constants as undefined', () => {
const wrapper: typeof ThemeProvider = ({ children }) =>
createElement(
ThemeProvider,
{
themes: {
constants: undefined,
dark: { color: '#a1a1a1' },
light: { color: '#a2a2a2' }
}
},
children
)
const { result } = renderHook(() => useTheme(), { wrapper })
expect(Object.keys(result.current).length).toEqual(1)
})
})
5 changes: 0 additions & 5 deletions tests/without-constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createElement } from 'react'
import { renderHook } from '@testing-library/react-hooks/native'
import { Appearance } from '../__mocks__/react-native-appearance'
import { ThemeProvider, useTheme } from '../src/index'
import { themes3 } from './fixture'

Expand All @@ -9,10 +8,6 @@ declare module '../src/index' {
export interface BaseTheme extends Themes3 {}
}

beforeEach(() => {
Appearance.preference = 'no-preference'
})

describe('Without Constants', () => {
const wrapper: typeof ThemeProvider = ({ children }) =>
createElement(ThemeProvider, { themes: themes3 }, children)
Expand Down
Loading

0 comments on commit 61cdf43

Please sign in to comment.