@codeandmoney/soelma 0.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +1 -0
- package/.eslintrc.js +31 -0
- package/.github/workflows/nodejs.yml +33 -0
- package/.size-limit.json +6 -0
- package/CHANGELOG.md +90 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/babel.config.js +3 -0
- package/docs/api.md +103 -0
- package/docs/appearance.md +54 -0
- package/docs/dark-mode.md +46 -0
- package/docs/dimensions.md +29 -0
- package/docs/i18n.md +67 -0
- package/docs/logo.png +0 -0
- package/docs/media-query.md +274 -0
- package/docs/orientation.md +44 -0
- package/docs/safe-area.md +62 -0
- package/docs/testting.md +51 -0
- package/docs/ts.md +127 -0
- package/example/AppStyleX/.watchmanconfig +1 -0
- package/example/AppStyleX/android/build.gradle +26 -0
- package/example/AppStyleX/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/AppStyleX/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/example/AppStyleX/android/gradle.properties +53 -0
- package/example/AppStyleX/android/gradlew +249 -0
- package/example/AppStyleX/android/gradlew.bat +92 -0
- package/example/AppStyleX/android/settings.gradle +12 -0
- package/example/AppStyleX/app.json +28 -0
- package/example/AppStyleX/babel.config.js +3 -0
- package/example/AppStyleX/index.js +9 -0
- package/example/AppStyleX/ios/Podfile +7 -0
- package/example/AppStyleX/ios/Podfile.lock +1252 -0
- package/example/AppStyleX/metro.config.js +54 -0
- package/example/AppStyleX/package.json +43 -0
- package/example/AppStyleX/react-native.config.js +23 -0
- package/example/AppStyleX/src/App.tsx +25 -0
- package/example/AppStyleX/src/BottomNav/index.tsx +32 -0
- package/example/AppStyleX/src/BottomNav/styles.ts +42 -0
- package/example/AppStyleX/src/Circle/index.tsx +53 -0
- package/example/AppStyleX/src/Circle/styles.ts +22 -0
- package/example/AppStyleX/src/Root/index.tsx +41 -0
- package/example/AppStyleX/src/Root/styles.ts +18 -0
- package/example/AppStyleX/src/ToggleButton/index.tsx +66 -0
- package/example/AppStyleX/src/ToggleButton/styles.ts +69 -0
- package/example/AppStyleX/src/style-system/hooks/useAnimatedBgColor.ts +5 -0
- package/example/AppStyleX/src/style-system/hooks/useAnimatedTextColor.ts +5 -0
- package/example/AppStyleX/src/style-system/hooks/useIsDark.ts +8 -0
- package/example/AppStyleX/src/style-system/palette.ts +11 -0
- package/example/AppStyleX/src/style-system/theme.ts +14 -0
- package/example/AppStyleX/src/style-system/utils.ts +11 -0
- package/example/AppStyleX/src/stylex.d.ts +6 -0
- package/example/AppStyleX/tsconfig.json +3 -0
- package/example/AppStyleX/yarn.lock +6767 -0
- package/jest.config.js +19 -0
- package/package.json +59 -0
- package/src/DefaultTheme.ts +4 -0
- package/src/__tests__/createBreakpoints.test.ts +152 -0
- package/src/__tests__/createBreakpointsMatcher.test.ts +188 -0
- package/src/__tests__/createBreakpointsMatcher.types-test.ts +81 -0
- package/src/__tests__/createEventEmitter.test.ts +37 -0
- package/src/__tests__/dark-mode.test.ts +56 -0
- package/src/__tests__/dependencyRegistry.test.ts +16 -0
- package/src/__tests__/dependencyUsage.test.ts +13 -0
- package/src/__tests__/dimensions.test.ts +36 -0
- package/src/__tests__/makeUseStyles.types-test.ts +69 -0
- package/src/__tests__/media-query.test.ts +204 -0
- package/src/__tests__/orientation.test.ts +61 -0
- package/src/__tests__/useTheme.test.ts +26 -0
- package/src/__tests__/withStyles.types-test.tsx +173 -0
- package/src/appearance/consts.ts +1 -0
- package/src/appearance/index.ts +37 -0
- package/src/appearance/init.ts +12 -0
- package/src/context.ts +9 -0
- package/src/createEventEmitter.ts +26 -0
- package/src/dark-mode/consts.ts +1 -0
- package/src/dark-mode/index.ts +29 -0
- package/src/dark-mode/init.ts +19 -0
- package/src/dark-mode/state.ts +5 -0
- package/src/dependencyRegistry.ts +21 -0
- package/src/dependencyUsage.ts +31 -0
- package/src/dimensions/consts.ts +2 -0
- package/src/dimensions/index.ts +20 -0
- package/src/dimensions/init.ts +37 -0
- package/src/dimensions/utils.ts +11 -0
- package/src/i18n.ts +18 -0
- package/src/index.ts +7 -0
- package/src/makeUseStyles/createUseStylesTheme.js +42 -0
- package/src/makeUseStyles/createUseStylesTheme.test.js +137 -0
- package/src/makeUseStyles/createUseStylesWithoutTheme.js +38 -0
- package/src/makeUseStyles/createUseStylesWithoutTheme.test.js +63 -0
- package/src/makeUseStyles/index.d.ts +7 -0
- package/src/makeUseStyles/index.js +12 -0
- package/src/makeUseStyles/index.test.js +28 -0
- package/src/makeUseStyles/test-type.js +28 -0
- package/src/makeUseStyles/utils.js +67 -0
- package/src/media-query/base.ts +43 -0
- package/src/media-query/breakpoints.ts +121 -0
- package/src/media-query/index.ts +12 -0
- package/src/orientation.ts +17 -0
- package/src/safe-area/SafeAreaProvider.tsx +17 -0
- package/src/safe-area/StylexSaveAreaConsumer.ts +23 -0
- package/src/safe-area/consts.ts +1 -0
- package/src/safe-area/eventEmitter.ts +4 -0
- package/src/safe-area/index.tsx +16 -0
- package/src/safe-area/init.tsx +6 -0
- package/src/safe-area/state.ts +12 -0
- package/src/safe-area/types.ts +10 -0
- package/src/useColorTransition.ts +50 -0
- package/src/useTheme.ts +16 -0
- package/src/withStyles.tsx +35 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
const exclusionList = (() => {
|
|
4
|
+
try {
|
|
5
|
+
return require("metro-config/src/defaults/exclusionList");
|
|
6
|
+
} catch (_) {
|
|
7
|
+
// `blacklist` was renamed to `exclusionList` in 0.60
|
|
8
|
+
return require("metro-config/src/defaults/blacklist");
|
|
9
|
+
}
|
|
10
|
+
})();
|
|
11
|
+
|
|
12
|
+
const blockList = exclusionList([
|
|
13
|
+
/node_modules\/.*\/node_modules\/react-native\/.*/,
|
|
14
|
+
|
|
15
|
+
// This stops "react-native run-windows" from causing the metro server to
|
|
16
|
+
// crash if its already running
|
|
17
|
+
new RegExp(`${path.join(__dirname, "windows").replace(/[/\\]+/g, "/")}.*`),
|
|
18
|
+
|
|
19
|
+
// Workaround for `EPERM: operation not permitted, lstat '~\midl-MIDLRT-cl.read.1.tlog'`
|
|
20
|
+
/.*\.tlog/,
|
|
21
|
+
|
|
22
|
+
// Prevent Metro from watching temporary files generated by Visual Studio
|
|
23
|
+
// otherwise it may crash when they are removed when closing a project.
|
|
24
|
+
/.*\/.vs\/.*/,
|
|
25
|
+
|
|
26
|
+
// Workaround for `EBUSY: resource busy or locked, open '~\msbuild.ProjectImports.zip'`
|
|
27
|
+
/.*\.ProjectImports\.zip/,
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const config = {
|
|
31
|
+
resolver: {
|
|
32
|
+
blacklistRE: blockList,
|
|
33
|
+
blockList,
|
|
34
|
+
},
|
|
35
|
+
transformer: {
|
|
36
|
+
getTransformOptions: async () => ({
|
|
37
|
+
transform: {
|
|
38
|
+
experimentalImportSupport: false,
|
|
39
|
+
inlineRequires: false,
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Starting with react-native 0.72, we are required to provide a full config.
|
|
47
|
+
const {
|
|
48
|
+
getDefaultConfig,
|
|
49
|
+
mergeConfig,
|
|
50
|
+
} = require("@react-native/metro-config");
|
|
51
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
52
|
+
} catch (_) {
|
|
53
|
+
module.exports = config;
|
|
54
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "AppStyleX",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"android": "react-native run-android",
|
|
7
|
+
"build:android": "mkdirp dist/res && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
|
|
8
|
+
"build:ios": "mkdirp dist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist",
|
|
9
|
+
"ios": "react-native run-ios",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"start": "react-native start",
|
|
12
|
+
"test": "jest"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"color": "^4.2.3",
|
|
16
|
+
"react": "18.2.0",
|
|
17
|
+
"react-native": "0.73.1",
|
|
18
|
+
"react-native-safe-area-context": "^4.8.1",
|
|
19
|
+
"react-native-stylex": "latest"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@babel/core": "^7.20.0",
|
|
23
|
+
"@babel/preset-env": "^7.20.0",
|
|
24
|
+
"@babel/runtime": "^7.20.0",
|
|
25
|
+
"@react-native/babel-preset": "^0.73.18",
|
|
26
|
+
"@react-native/eslint-config": "^0.73.1",
|
|
27
|
+
"@react-native/metro-config": "^0.73.2",
|
|
28
|
+
"@react-native/typescript-config": "^0.73.1",
|
|
29
|
+
"@types/react": "^18.2.6",
|
|
30
|
+
"@types/react-test-renderer": "^18.0.0",
|
|
31
|
+
"babel-jest": "^29.6.3",
|
|
32
|
+
"eslint": "^8.19.0",
|
|
33
|
+
"jest": "^29.6.3",
|
|
34
|
+
"mkdirp": "^1.0.0",
|
|
35
|
+
"prettier": "2.8.8",
|
|
36
|
+
"react-native-test-app": "^2.5.34",
|
|
37
|
+
"react-test-renderer": "18.2.0",
|
|
38
|
+
"typescript": "5.0.4"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const project = (() => {
|
|
2
|
+
try {
|
|
3
|
+
const { configureProjects } = require("react-native-test-app");
|
|
4
|
+
return configureProjects({
|
|
5
|
+
android: {
|
|
6
|
+
sourceDir: "android",
|
|
7
|
+
},
|
|
8
|
+
ios: {
|
|
9
|
+
sourceDir: "ios",
|
|
10
|
+
},
|
|
11
|
+
windows: {
|
|
12
|
+
sourceDir: "windows",
|
|
13
|
+
solutionFile: "windows/AppStyleX.sln",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
} catch (_) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
...(project ? { project } : undefined),
|
|
23
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, {useState} from 'react';
|
|
2
|
+
import {ThemeProvider} from 'react-native-stylex';
|
|
3
|
+
import {SafeAreaProvider} from 'react-native-safe-area-context';
|
|
4
|
+
import {StylexSaveAreaConsumer} from 'react-native-stylex/safe-area';
|
|
5
|
+
|
|
6
|
+
import {darkTheme, lightTheme} from './style-system/theme';
|
|
7
|
+
|
|
8
|
+
import {Root} from './Root';
|
|
9
|
+
|
|
10
|
+
export const App = () => {
|
|
11
|
+
const [theme, setTheme] = useState(lightTheme);
|
|
12
|
+
const toggleTheme = () =>
|
|
13
|
+
setTheme((currentTheme) =>
|
|
14
|
+
currentTheme === darkTheme ? lightTheme : darkTheme,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<SafeAreaProvider>
|
|
19
|
+
<ThemeProvider value={theme}>
|
|
20
|
+
<Root toggleTheme={toggleTheme} />
|
|
21
|
+
</ThemeProvider>
|
|
22
|
+
<StylexSaveAreaConsumer />
|
|
23
|
+
</SafeAreaProvider>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Animated} from 'react-native';
|
|
3
|
+
import {useColorTransition} from 'react-native-stylex';
|
|
4
|
+
|
|
5
|
+
import {useAnimatedBgColor} from '../style-system/hooks/useAnimatedBgColor';
|
|
6
|
+
import {useAnimatedTextColor} from '../style-system/hooks/useAnimatedTextColor';
|
|
7
|
+
|
|
8
|
+
import {useStyles} from './styles';
|
|
9
|
+
|
|
10
|
+
export const BottomNav = () => {
|
|
11
|
+
const styles = useStyles();
|
|
12
|
+
const bgStyle = useAnimatedBgColor();
|
|
13
|
+
const textStyle = useAnimatedTextColor();
|
|
14
|
+
const skipAnimatedStyle = {
|
|
15
|
+
color: useColorTransition(({palette, utils}) =>
|
|
16
|
+
utils.fade(palette.text, 0.5),
|
|
17
|
+
),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Animated.Text style={[styles.skipBtn, skipAnimatedStyle]}>
|
|
23
|
+
Skip
|
|
24
|
+
</Animated.Text>
|
|
25
|
+
<Animated.View style={[styles.nextButton, bgStyle]}>
|
|
26
|
+
<Animated.Text style={[styles.nextButtonText, textStyle]}>
|
|
27
|
+
→
|
|
28
|
+
</Animated.Text>
|
|
29
|
+
</Animated.View>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import { makeUseStyles } from "react-native-stylex";
|
|
3
|
+
import { getSafeArea } from "react-native-stylex/safe-area";
|
|
4
|
+
|
|
5
|
+
export const useStyles = makeUseStyles(() => ({
|
|
6
|
+
skipBtn: {
|
|
7
|
+
position: "absolute",
|
|
8
|
+
left: 20,
|
|
9
|
+
bottom: 30 + getSafeArea().bottom,
|
|
10
|
+
fontSize: 20,
|
|
11
|
+
},
|
|
12
|
+
nextButton: {
|
|
13
|
+
position: "absolute",
|
|
14
|
+
right: 20,
|
|
15
|
+
bottom: 20 + getSafeArea().bottom,
|
|
16
|
+
width: 48,
|
|
17
|
+
height: 48,
|
|
18
|
+
borderRadius: 48,
|
|
19
|
+
shadowColor: "#000",
|
|
20
|
+
shadowOffset: { width: 0, height: 2 },
|
|
21
|
+
shadowOpacity: 0.23,
|
|
22
|
+
shadowRadius: 2.62,
|
|
23
|
+
elevation: 4,
|
|
24
|
+
},
|
|
25
|
+
nextButtonText: {
|
|
26
|
+
textAlign: "center",
|
|
27
|
+
...Platform.select({
|
|
28
|
+
ios: {
|
|
29
|
+
fontSize: 26,
|
|
30
|
+
lineHeight: 48,
|
|
31
|
+
},
|
|
32
|
+
android: {
|
|
33
|
+
textAlignVertical: "center",
|
|
34
|
+
fontSize: 36,
|
|
35
|
+
},
|
|
36
|
+
web: {
|
|
37
|
+
fontSize: 36,
|
|
38
|
+
lineHeight: 40,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react';
|
|
2
|
+
import {Animated, View} from 'react-native';
|
|
3
|
+
|
|
4
|
+
import {useIsDark} from '../style-system/hooks/useIsDark';
|
|
5
|
+
import {useAnimatedBgColor} from '../style-system/hooks/useAnimatedBgColor';
|
|
6
|
+
|
|
7
|
+
import {useStyles} from './styles';
|
|
8
|
+
|
|
9
|
+
const {Value, timing} = Animated;
|
|
10
|
+
|
|
11
|
+
export const Circle = () => {
|
|
12
|
+
const styles = useStyles();
|
|
13
|
+
const bgStyle = useAnimatedBgColor();
|
|
14
|
+
const isDarkTheme = useIsDark();
|
|
15
|
+
const animatedCircle = useRef(new Value(0));
|
|
16
|
+
const animatedStyle = {
|
|
17
|
+
transform: [
|
|
18
|
+
{
|
|
19
|
+
translateX: animatedCircle.current.interpolate({
|
|
20
|
+
inputRange: [0, 1],
|
|
21
|
+
outputRange: [100, 0],
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
translateY: animatedCircle.current.interpolate({
|
|
26
|
+
inputRange: [0, 1],
|
|
27
|
+
outputRange: [-100, 0],
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
scale: animatedCircle.current.interpolate({
|
|
32
|
+
inputRange: [0, 1],
|
|
33
|
+
outputRange: [0.01, 1],
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
timing(animatedCircle.current, {
|
|
41
|
+
useNativeDriver: false,
|
|
42
|
+
toValue: isDarkTheme ? 1 : 0,
|
|
43
|
+
duration: 250,
|
|
44
|
+
}).start();
|
|
45
|
+
}, [isDarkTheme]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<View style={styles.root}>
|
|
49
|
+
<View style={styles.circle} />
|
|
50
|
+
<Animated.View style={[styles.overlay, animatedStyle, bgStyle]} />
|
|
51
|
+
</View>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {makeUseStyles} from 'react-native-stylex';
|
|
2
|
+
|
|
3
|
+
export const useStyles = makeUseStyles(({palette}) => ({
|
|
4
|
+
root: {
|
|
5
|
+
alignSelf: 'center',
|
|
6
|
+
},
|
|
7
|
+
circle: {
|
|
8
|
+
width: 150,
|
|
9
|
+
height: 150,
|
|
10
|
+
borderRadius: 150,
|
|
11
|
+
backgroundColor: palette.accent,
|
|
12
|
+
marginBottom: 80,
|
|
13
|
+
},
|
|
14
|
+
overlay: {
|
|
15
|
+
position: 'absolute',
|
|
16
|
+
right: -20,
|
|
17
|
+
top: -20,
|
|
18
|
+
width: 145,
|
|
19
|
+
height: 145,
|
|
20
|
+
borderRadius: 150,
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Animated} from 'react-native';
|
|
3
|
+
|
|
4
|
+
import {useAnimatedBgColor} from '../style-system/hooks/useAnimatedBgColor';
|
|
5
|
+
import {useAnimatedTextColor} from '../style-system/hooks/useAnimatedTextColor';
|
|
6
|
+
|
|
7
|
+
import {ToggleButton} from '../ToggleButton';
|
|
8
|
+
import {BottomNav} from '../BottomNav';
|
|
9
|
+
import {Circle} from '../Circle';
|
|
10
|
+
|
|
11
|
+
import {useStyles} from './styles';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
toggleTheme: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Concept: https://dribbble.com/shots/5846239-Light-dark-toggle-switch-InVision-Studio
|
|
18
|
+
export const Root = ({toggleTheme}: Props) => {
|
|
19
|
+
const styles = useStyles();
|
|
20
|
+
const bgStyle = useAnimatedBgColor();
|
|
21
|
+
const textStyle = useAnimatedTextColor();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Animated.View style={[styles.root, bgStyle]}>
|
|
25
|
+
<Circle />
|
|
26
|
+
|
|
27
|
+
<Animated.Text style={[styles.title, textStyle]}>
|
|
28
|
+
Choose a style
|
|
29
|
+
</Animated.Text>
|
|
30
|
+
<Animated.Text style={[styles.text, textStyle]}>
|
|
31
|
+
Pop or subtle. Day or night.
|
|
32
|
+
</Animated.Text>
|
|
33
|
+
<Animated.Text style={[styles.text, textStyle]}>
|
|
34
|
+
Customize your interface.
|
|
35
|
+
</Animated.Text>
|
|
36
|
+
|
|
37
|
+
<ToggleButton onPress={toggleTheme} />
|
|
38
|
+
<BottomNav />
|
|
39
|
+
</Animated.View>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {makeUseStyles} from 'react-native-stylex';
|
|
2
|
+
|
|
3
|
+
export const useStyles = makeUseStyles(() => ({
|
|
4
|
+
root: {
|
|
5
|
+
flex: 1,
|
|
6
|
+
justifyContent: 'center',
|
|
7
|
+
},
|
|
8
|
+
title: {
|
|
9
|
+
textAlign: 'center',
|
|
10
|
+
fontSize: 26,
|
|
11
|
+
fontWeight: '600',
|
|
12
|
+
marginBottom: 16,
|
|
13
|
+
},
|
|
14
|
+
text: {
|
|
15
|
+
textAlign: 'center',
|
|
16
|
+
fontSize: 20,
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react';
|
|
2
|
+
import {Animated, View, Pressable, Text} from 'react-native';
|
|
3
|
+
import {useColorTransition} from 'react-native-stylex';
|
|
4
|
+
|
|
5
|
+
import {useIsDark} from '../style-system/hooks/useIsDark';
|
|
6
|
+
|
|
7
|
+
import {useStyles, ROOT_WIDTH} from './styles';
|
|
8
|
+
|
|
9
|
+
const {Value, spring} = Animated;
|
|
10
|
+
const HALF_ROOT_WIDTH = ROOT_WIDTH / 2;
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
onPress: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ToggleButton = ({onPress}: Props) => {
|
|
17
|
+
const styles = useStyles();
|
|
18
|
+
const isDark = useIsDark();
|
|
19
|
+
const bgStyle = {
|
|
20
|
+
backgroundColor: useColorTransition(({palette}) => palette.bg),
|
|
21
|
+
};
|
|
22
|
+
const animatedOffset = useRef(new Value(0));
|
|
23
|
+
const animatedStyle = {
|
|
24
|
+
transform: [
|
|
25
|
+
{
|
|
26
|
+
translateX: animatedOffset.current.interpolate({
|
|
27
|
+
inputRange: [0, 1],
|
|
28
|
+
outputRange: [0, HALF_ROOT_WIDTH],
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
spring(animatedOffset.current, {
|
|
36
|
+
useNativeDriver: false,
|
|
37
|
+
toValue: isDark ? 1 : 0,
|
|
38
|
+
}).start();
|
|
39
|
+
}, [isDark]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Pressable onPress={onPress}>
|
|
43
|
+
<View style={styles.root}>
|
|
44
|
+
<Animated.View style={[styles.activeBg, animatedStyle, bgStyle]} />
|
|
45
|
+
<View style={styles.labelsRoot}>
|
|
46
|
+
<Text
|
|
47
|
+
style={[
|
|
48
|
+
styles.labelText,
|
|
49
|
+
styles.lightLabelText,
|
|
50
|
+
!isDark && styles.activeLabelText,
|
|
51
|
+
]}>
|
|
52
|
+
Light
|
|
53
|
+
</Text>
|
|
54
|
+
<Text
|
|
55
|
+
style={[
|
|
56
|
+
styles.labelText,
|
|
57
|
+
styles.darkLabelText,
|
|
58
|
+
isDark && styles.activeLabelText,
|
|
59
|
+
]}>
|
|
60
|
+
Dark
|
|
61
|
+
</Text>
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
</Pressable>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {makeUseStyles} from 'react-native-stylex';
|
|
2
|
+
import {StyleSheet, Platform} from 'react-native';
|
|
3
|
+
|
|
4
|
+
export const ROOT_WIDTH = 280;
|
|
5
|
+
|
|
6
|
+
export const useStyles = makeUseStyles(({palette, utils}) => ({
|
|
7
|
+
root: {
|
|
8
|
+
marginTop: 36,
|
|
9
|
+
alignSelf: 'center',
|
|
10
|
+
height: 60,
|
|
11
|
+
borderRadius: 60,
|
|
12
|
+
width: ROOT_WIDTH,
|
|
13
|
+
backgroundColor: utils.isDark(palette.bg)
|
|
14
|
+
? utils.lighten(palette.bg, 0.3)
|
|
15
|
+
: utils.darken(palette.bg, 0.3),
|
|
16
|
+
},
|
|
17
|
+
activeBg: {
|
|
18
|
+
width: '50%',
|
|
19
|
+
height: '100%',
|
|
20
|
+
borderRadius: 60,
|
|
21
|
+
position: 'absolute',
|
|
22
|
+
top: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
backgroundColor: palette.bg,
|
|
25
|
+
|
|
26
|
+
shadowColor: palette.text,
|
|
27
|
+
shadowOffset: {
|
|
28
|
+
width: 0,
|
|
29
|
+
height: 2,
|
|
30
|
+
},
|
|
31
|
+
shadowOpacity: 0.23,
|
|
32
|
+
shadowRadius: 2.62,
|
|
33
|
+
|
|
34
|
+
elevation: 4,
|
|
35
|
+
},
|
|
36
|
+
labelsRoot: {
|
|
37
|
+
...StyleSheet.absoluteFillObject,
|
|
38
|
+
// Hack for Android
|
|
39
|
+
elevation: 5,
|
|
40
|
+
},
|
|
41
|
+
labelText: {
|
|
42
|
+
position: 'absolute',
|
|
43
|
+
height: '100%',
|
|
44
|
+
width: '50%',
|
|
45
|
+
top: 0,
|
|
46
|
+
|
|
47
|
+
fontSize: 20,
|
|
48
|
+
fontWeight: '700',
|
|
49
|
+
color: utils.fade(palette.text, 0.5),
|
|
50
|
+
textAlign: 'center',
|
|
51
|
+
...Platform.select({
|
|
52
|
+
web: {
|
|
53
|
+
lineHeight: 60,
|
|
54
|
+
},
|
|
55
|
+
default: {
|
|
56
|
+
lineHeight: 55,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
lightLabelText: {
|
|
61
|
+
left: 0,
|
|
62
|
+
},
|
|
63
|
+
darkLabelText: {
|
|
64
|
+
right: 0,
|
|
65
|
+
},
|
|
66
|
+
activeLabelText: {
|
|
67
|
+
color: palette.text,
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {lightPalette, darkPalette} from './palette';
|
|
2
|
+
import {utils} from './utils';
|
|
3
|
+
|
|
4
|
+
export const lightTheme = {
|
|
5
|
+
palette: lightPalette,
|
|
6
|
+
utils,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type Theme = typeof lightTheme;
|
|
10
|
+
|
|
11
|
+
export const darkTheme: Theme = {
|
|
12
|
+
palette: darkPalette,
|
|
13
|
+
utils,
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
|
|
3
|
+
export const utils = {
|
|
4
|
+
isDark: (color: string): boolean => Color(color).isDark(),
|
|
5
|
+
fade: (color: string, alpha: number): string =>
|
|
6
|
+
Color(color).alpha(alpha).string(),
|
|
7
|
+
lighten: (color: string, val: number): string =>
|
|
8
|
+
Color(color).lighten(val).string(),
|
|
9
|
+
darken: (color: string, val: number): string =>
|
|
10
|
+
Color(color).darken(val).string(),
|
|
11
|
+
};
|