Skip to content

Commit e4c36b5

Browse files
author
Mohit
committed
⚡ v0.1.0 with context
1 parent a1f7fbd commit e4c36b5

28 files changed

Lines changed: 837 additions & 199 deletions

README.md

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# React Native Sugar Style
22

3-
Theme based alternative for React Native StyleSheet. (🧪 Highly Experimental)
3+
Theme based alternative for React Native StyleSheet. (🧪 Experimental)
44

55
| BEFORE | AFTER |
66
| ------------------------------------- | ---------------------------------- |
@@ -10,13 +10,17 @@ Theme based alternative for React Native StyleSheet. (🧪 Highly Experimental)
1010

1111
```
1212
yarn add react-native-sugar-style
13+
```
1314

15+
```
1416
npm i react-native-sugar-style
1517
```
1618

1719
### Usage
1820

19-
**style.tsx** - define a configuration for you theme
21+
STEP 1: _style.tsx_
22+
23+
Define configurations for your theme see [this file](https://github.com/mohit23x/react-native-sugar-style/blob/main/example/style/index.tsx) for a more verbose example
2024

2125
```typescript
2226
import { Sugar, constants } from 'react-native-sugar-style';
@@ -28,23 +32,45 @@ const theme = {
2832
};
2933

3034
export type Theme = typeof theme;
31-
export const { StyleSheet } = new Sugar.init<Theme>(theme);
35+
export const { StyleSheet, ThemeProvider, useTheme } = Sugar.init<Theme>(theme);
3236

3337
export default StyleSheet;
3438
```
3539

36-
**component.tsx** - use StyleSheet as you do normally
40+
STEP 2: **App.tsx** (optional)
41+
42+
Wrap with ThemeProvider, if you are using a single theme then this step is not needed skip to STEP 3
3743

3844
```javascript
3945
import React from 'react';
40-
import { View, Text } from 'react-native';
41-
import StyleSheet from './style';
46+
import {ThemeProvider} from './style';
47+
import Navigation from './navigation';
4248

43-
const Component = () => (
44-
<View style={styles.container}>
45-
<Text style={styles.text}>Hello World</Text>
46-
</View>
49+
const App = () => (
50+
<ThemeProvider>
51+
<Navigation>
52+
</ThemeProvider>
4753
);
54+
```
55+
56+
STEP 3: **component.tsx**
57+
58+
Use StyleSheet as you do normally in react native component
59+
60+
```javascript
61+
import React from 'react';
62+
import { View, Text } from 'react-native';
63+
import { StyleSheet, useTheme } from './style';
64+
65+
const Component = () => {
66+
useTheme();
67+
68+
return (
69+
<View style={styles.container}>
70+
<Text style={styles.text}>Hello World</Text>
71+
</View>
72+
);
73+
};
4874

4975
const styles = StyleSheet.create((theme) => ({
5076
container: {
@@ -59,9 +85,33 @@ const styles = StyleSheet.create((theme) => ({
5985
}));
6086
```
6187

88+
> **NOTE**: if you have a single theme then `useTheme()` hook can be avoided, also if you add `useTheme()` in you navigation screen parent component, then you can avoid using it in child components\*
89+
90+
STEP 4: **anotherComponent.tsx** (optional)
91+
92+
To change the theme you can call build method and it will swap the theme
93+
94+
```javascript
95+
import React from 'react';
96+
import { View, Button } from 'react-native';
97+
import { StyleSheet } from './style';
98+
99+
const Component = () => {
100+
const light = () => StyleSheet.build(lightTheme);
101+
const dark = () => StyleSheet.build(darkTheme);
102+
103+
return (
104+
<View>
105+
<Button onPress={light} title="light" />
106+
<Button onPress={dark} title="dark" />
107+
</View>
108+
);
109+
};
110+
```
111+
62112
### Demo
63113

64-
Scan and run with expo go app, See example folder for a complete setup.
114+
Scan and run with expo go app, run the [example project](https://github.com/mohit23x/react-native-sugar-style/tree/main/example) for a more detailed example.
65115
https://expo.io/@mohit23x/projects/react-native-sugar-style
66116

67117
![Scan QR with expo app](assets/qr.png 'Scan QR')
@@ -85,7 +135,7 @@ Available as **theme.constant**
85135

86136
### Why this package?
87137

88-
[There](https://github.com/vitalets/react-native-extended-stylesheet) [are](https://github.com/wvteijlingen/react-native-themed-styles) [many](https://github.com/wvteijlingen/react-native-themed-styles) [awesome](https://github.com/Shopify/restyle) [solutions](https://github.com/callstack/react-theme-provider) [for](https://www.npmjs.com/package/simple-theme) React Native. Through this package i wanted to explore and experiment a way to achieve a solution which is very similar to the existing react native code pattern, with the ability to get dynamic theme value and can be used in functional and class based components.
138+
[There](https://github.com/vitalets/react-native-extended-stylesheet) [are](https://github.com/wvteijlingen/react-native-themed-styles) [many](https://github.com/wvteijlingen/react-native-themed-styles) [awesome](https://github.com/Shopify/restyle) [solutions](https://github.com/callstack/react-theme-provider) [for](https://www.npmjs.com/package/simple-theme) [styling](https://github.com/nandorojo/dripsy) in React Native. Through this package i wanted to explore and experiment a way to achieve a development experience which is very similar to the existing react native pattern, with the ability to get dynamic theme value and can be used in functional and class based components.
89139

90140
### Acknowledgement
91141

@@ -96,7 +146,4 @@ Special thanks to the Authors of the amazing open source libraries
96146
### Caveats
97147

98148
- May introduce performance issues (not tested)
99-
- Re-rendering on theme change causes mount and un-mounting of parent
100-
- re-renders when same theme is applied again
101-
- Caching not implemented
102-
- Dimension values are not dynamic
149+
- Dimension values are not dynamic (device height/width don't change based on orientation)

assets/after.png

31.7 KB
Loading

build/Constant.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export declare const constants: {
2+
readonly constant: {
3+
readonly height: number;
4+
readonly width: number;
5+
readonly screenHeight: number;
6+
readonly screenWidth: number;
7+
readonly navBarHeight: number;
8+
readonly isNavBarVisible: boolean;
9+
readonly visibleHeight: number;
10+
readonly isIPhoneX: () => boolean;
11+
readonly os: {
12+
readonly android: boolean;
13+
readonly ios: boolean;
14+
readonly web: boolean;
15+
readonly windows: boolean;
16+
};
17+
readonly statusBarHeight: number;
18+
};
19+
};

build/Constant.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.constants = void 0;
4+
const react_native_1 = require("react-native");
5+
const { height, width } = react_native_1.Dimensions.get('window');
6+
/* =========== */
7+
// code credits: https://medium.com/codespace69/reactnative-ios-android-detect-screen-notch-status-bar-device-info-dc11b8c6a6a3
8+
const X_WIDTH = 375;
9+
const X_HEIGHT = 812;
10+
const XSMAX_WIDTH = 414;
11+
const XSMAX_HEIGHT = 896;
12+
const isIPhoneX = () => react_native_1.Platform.OS === 'ios' && !react_native_1.Platform.isPad && !react_native_1.Platform.isTVOS
13+
? (width === X_WIDTH && height === X_HEIGHT) ||
14+
(width === XSMAX_WIDTH && height === XSMAX_HEIGHT)
15+
: false;
16+
const statusBarHeight = react_native_1.Platform.select({
17+
ios: isIPhoneX() ? 44 : 20,
18+
android: react_native_1.StatusBar.currentHeight,
19+
default: 0,
20+
});
21+
/* ====== x ====== */
22+
const { height: screenHeight, width: screenWidth } = react_native_1.Dimensions.get('screen');
23+
const navBarHeight = screenHeight - statusBarHeight - height;
24+
const isNavBarVisible = navBarHeight > 0;
25+
const visibleHeight = height - navBarHeight;
26+
const os = {
27+
android: react_native_1.Platform.OS === 'android',
28+
ios: react_native_1.Platform.OS === 'ios',
29+
web: react_native_1.Platform.OS === 'web',
30+
windows: react_native_1.Platform.OS === 'windows',
31+
};
32+
exports.constants = {
33+
constant: {
34+
height,
35+
width,
36+
screenHeight,
37+
screenWidth,
38+
navBarHeight,
39+
isNavBarVisible,
40+
visibleHeight,
41+
isIPhoneX,
42+
os,
43+
statusBarHeight,
44+
},
45+
};

build/Main.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference types="react" />
2+
import Sugar from './Sugar';
3+
export default class Main {
4+
init<T>(theme: T): {
5+
StyleSheet: Sugar<T>;
6+
ThemeContext: import("react").Context<T>;
7+
ThemeProvider: import("react").ComponentType<{
8+
children: import("react").ReactNode;
9+
sugar?: Sugar<T> | undefined;
10+
}>;
11+
useTheme: () => T;
12+
withTheme: <P extends import("./type").ThemeProp<T>>(WrappedComponent: import("react").ComponentType<P>) => () => JSX.Element;
13+
};
14+
}

build/Main.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const Sugar_1 = require("./Sugar");
4+
const Provider_1 = require("./Provider");
5+
class Main {
6+
init(theme) {
7+
const StyleSheet = new Sugar_1.default(theme);
8+
const { ThemeContext, ThemeProvider, useTheme, withTheme } = Provider_1.themeCreator(StyleSheet, theme);
9+
return { StyleSheet, ThemeContext, ThemeProvider, useTheme, withTheme };
10+
}
11+
}
12+
exports.default = Main;

build/Provider.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as React from 'react';
2+
import Sugar from './Sugar';
3+
import type { ThemeProp } from './type';
4+
export declare function themeCreator<T>(sugar: Sugar<T>, defaultTheme: T): {
5+
ThemeContext: React.Context<T>;
6+
ThemeProvider: React.ComponentType<{
7+
children: React.ReactNode;
8+
sugar?: Sugar<T> | undefined;
9+
}>;
10+
useTheme: () => T;
11+
withTheme: <P extends ThemeProp<T>>(WrappedComponent: React.ComponentType<P>) => () => JSX.Element;
12+
};

build/Provider.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.themeCreator = void 0;
4+
const React = require("react");
5+
/* PROVIDER */
6+
function createThemeProvider(sugar, ThemeContext, defaultTheme) {
7+
const ThemeProvider = ({ children, }) => {
8+
const [theme, setTheme] = React.useState(defaultTheme);
9+
React.useEffect(() => {
10+
sugar.subscribe('build', () => {
11+
setTheme(sugar.theme);
12+
});
13+
}, []);
14+
return (<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>);
15+
};
16+
return ThemeProvider;
17+
}
18+
/* creator */
19+
function themeCreator(sugar, defaultTheme) {
20+
const ThemeContext = React.createContext(defaultTheme);
21+
const ThemeProvider = createThemeProvider(sugar, ThemeContext, defaultTheme);
22+
function useTheme() {
23+
const theme = React.useContext(ThemeContext);
24+
return theme;
25+
}
26+
function withTheme(WrappedComponent) {
27+
return function WithTheme() {
28+
return (<ThemeContext.Consumer>
29+
{(theme) => {
30+
const props = { theme };
31+
return <WrappedComponent {...props}/>;
32+
}}
33+
</ThemeContext.Consumer>);
34+
};
35+
}
36+
return { ThemeContext, ThemeProvider, useTheme, withTheme };
37+
}
38+
exports.themeCreator = themeCreator;

build/Sheet.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Fn, NamedStyles } from './type';
2+
export default class Sheet<T, S extends NamedStyles<S> | NamedStyles<any>> {
3+
result: S;
4+
source: Fn<T, S>;
5+
nativeSheet: S;
6+
globalVars: T | null;
7+
constructor(sourceFn: Fn<T, S>);
8+
calc(globalVars: T): S;
9+
getResult(): S;
10+
clearResult(): void;
11+
calcStyles(): void;
12+
calcStyle(key: string, styleProps: any): void;
13+
calcNative(): void;
14+
}

build/Sheet.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const react_native_1 = require("react-native");
4+
class Sheet {
5+
constructor(sourceFn) {
6+
this.nativeSheet = {};
7+
this.source = sourceFn;
8+
this.globalVars = null;
9+
this.result = {};
10+
}
11+
calc(globalVars) {
12+
this.globalVars = globalVars;
13+
this.clearResult();
14+
this.calcStyles();
15+
this.calcNative();
16+
return this.getResult();
17+
}
18+
getResult() {
19+
return this.result;
20+
}
21+
clearResult() {
22+
// @ts-ignore
23+
Object.keys(this.result).forEach((key) => delete this.result[key]);
24+
}
25+
calcStyles() {
26+
if (this.globalVars) {
27+
const restyle = this.source(this.globalVars);
28+
Object.keys(restyle).forEach((key) => {
29+
// @ts-ignore
30+
const styles = restyle[key];
31+
if (styles && typeof styles === 'object') {
32+
this.calcStyle(key, styles);
33+
}
34+
else {
35+
// @ts-ignore
36+
this.result[key] = styles;
37+
}
38+
});
39+
}
40+
}
41+
calcStyle(key, styleProps) {
42+
// @ts-ignore
43+
this.nativeSheet[key] = styleProps;
44+
}
45+
calcNative() {
46+
if (Object.keys(this.nativeSheet).length) {
47+
const rnStyleSheet = react_native_1.StyleSheet.create(this.nativeSheet);
48+
Object.assign(this.result, rnStyleSheet);
49+
}
50+
}
51+
}
52+
exports.default = Sheet;

0 commit comments

Comments
 (0)