lucy-cli 2.0.0-alpha.1 → 2.0.0-alpha.3
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/dist/init.js +29 -11
- package/files/expo/.prettierrc.js +16 -0
- package/files/expo/.yarnrc.yml +1 -1
- package/files/expo/app/(tabs)/_layout.tsx +38 -33
- package/files/expo/app/(tabs)/explore.tsx +114 -0
- package/files/expo/app/(tabs)/index.tsx +80 -0
- package/files/expo/app/+not-found.tsx +37 -0
- package/files/expo/app/_layout.tsx +39 -30
- package/files/expo/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/files/expo/assets/images/adaptive-icon.png +0 -0
- package/files/expo/assets/images/favicon.png +0 -0
- package/files/expo/assets/images/icon.png +0 -0
- package/files/expo/assets/images/partial-react-logo.png +0 -0
- package/files/expo/assets/images/react-logo.png +0 -0
- package/files/expo/assets/images/react-logo@2x.png +0 -0
- package/files/expo/assets/images/react-logo@3x.png +0 -0
- package/files/expo/assets/images/splash-icon.png +0 -0
- package/files/expo/babel.config.js +7 -6
- package/files/expo/components/Collapsible.tsx +52 -0
- package/files/expo/components/ExternalLink.tsx +32 -0
- package/files/expo/components/HapticTab.tsx +24 -0
- package/files/expo/components/HelloWave.tsx +35 -0
- package/files/expo/components/ParallaxScrollView.tsx +85 -0
- package/files/expo/components/ThemedText.tsx +70 -0
- package/files/expo/components/ThemedView.tsx +23 -0
- package/files/expo/components/ui/IconSymbol.ios.tsx +32 -0
- package/files/expo/components/ui/IconSymbol.tsx +41 -0
- package/files/expo/components/ui/TabBarBackground.ios.tsx +19 -0
- package/files/expo/components/ui/TabBarBackground.tsx +6 -0
- package/files/expo/constants/Colors.ts +17 -17
- package/files/expo/constants/theme.ts +17 -17
- package/files/expo/{eslint.config.js → eslint.config.mjs} +15 -19
- package/files/expo/hooks/useColorScheme.ts +13 -7
- package/files/expo/hooks/useColorScheme.web.ts +12 -9
- package/files/expo/hooks/useThemeColor.ts +19 -10
- package/files/expo/lib/data.ts +36 -33
- package/files/expo/lib/utils/index.ts +7 -2
- package/files/expo/lib/utils/polyfills.ts +1 -1
- package/files/expo/lib/wix/client.ts +3 -5
- package/files/expo/lib/wix/error.ts +3 -0
- package/files/expo/lib/wix/index.ts +1 -0
- package/files/expo/metro.config.js +31 -0
- package/files/expo/patches/@wix-sdk-npm-1.15.24-1adbec98e9.patch +20 -0
- package/files/expo/scripts/reset-project.ts +116 -0
- package/files/expo/tailwind.config.js +61 -196
- package/files/expo/tsconfig.json +31 -26
- package/package.json +1 -1
- package/src/init.ts +42 -16
- package/files/expo/.prettierrc.json +0 -16
- /package/files/expo/{readme.md → README.md} +0 -0
- /package/files/expo/{types/nativewind-env.d.ts → nativewind-env.d.ts} +0 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
import { StyleSheet } from 'react-native';
|
3
|
+
import Animated, { useAnimatedStyle, useSharedValue, withRepeat, withSequence, withTiming } from 'react-native-reanimated';
|
4
|
+
|
5
|
+
import { ThemedText } from '@/components/ThemedText';
|
6
|
+
|
7
|
+
/** Renders an animated waving hand emoji. */
|
8
|
+
export function HelloWave() {
|
9
|
+
const rotationAnimation = useSharedValue(0);
|
10
|
+
|
11
|
+
useEffect(() => {
|
12
|
+
rotationAnimation.value = withRepeat(
|
13
|
+
withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
|
14
|
+
4 // Run the animation 4 times
|
15
|
+
);
|
16
|
+
}, [rotationAnimation]);
|
17
|
+
|
18
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
19
|
+
transform: [{ rotate: `${rotationAnimation.value}deg` }],
|
20
|
+
}));
|
21
|
+
|
22
|
+
return (
|
23
|
+
<Animated.View style={animatedStyle}>
|
24
|
+
<ThemedText style={styles.text}>👋</ThemedText>
|
25
|
+
</Animated.View>
|
26
|
+
);
|
27
|
+
}
|
28
|
+
|
29
|
+
const styles = StyleSheet.create({
|
30
|
+
text: {
|
31
|
+
fontSize: 28,
|
32
|
+
lineHeight: 32,
|
33
|
+
marginTop: -6,
|
34
|
+
},
|
35
|
+
});
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import type { PropsWithChildren, ReactElement } from 'react';
|
2
|
+
import { StyleSheet } from 'react-native';
|
3
|
+
import Animated, { interpolate, useAnimatedRef, useAnimatedStyle, useScrollViewOffset } from 'react-native-reanimated';
|
4
|
+
|
5
|
+
import { ThemedView } from '@/components/ThemedView';
|
6
|
+
import { useBottomTabOverflow } from '@/components/ui/TabBarBackground';
|
7
|
+
import { useColorScheme } from '@/hooks/useColorScheme';
|
8
|
+
|
9
|
+
const HEADER_HEIGHT = 250;
|
10
|
+
|
11
|
+
type Props = PropsWithChildren<{
|
12
|
+
headerImage: ReactElement;
|
13
|
+
headerBackgroundColor: { dark: string; light: string };
|
14
|
+
}>;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Renders a parallax scroll view with a header image and background color.
|
18
|
+
* @param root0 - The props for the ParallaxScrollView component.
|
19
|
+
* @param root0.children - The content to display within the scroll view.
|
20
|
+
* @param root0.headerImage - The image to display in the header, which will have a parallax effect.
|
21
|
+
* @param root0.headerBackgroundColor - The background color for the header, which changes based on the color scheme.
|
22
|
+
* @returns The ParallaxScrollView component.
|
23
|
+
*/
|
24
|
+
export default function ParallaxScrollView({
|
25
|
+
children,
|
26
|
+
headerImage,
|
27
|
+
headerBackgroundColor,
|
28
|
+
}: Props) {
|
29
|
+
const colorScheme = useColorScheme() ?? 'light';
|
30
|
+
const scrollRef = useAnimatedRef<Animated.ScrollView>();
|
31
|
+
const scrollOffset = useScrollViewOffset(scrollRef);
|
32
|
+
const bottom = useBottomTabOverflow();
|
33
|
+
const headerAnimatedStyle = useAnimatedStyle(() => {
|
34
|
+
return {
|
35
|
+
transform: [
|
36
|
+
{
|
37
|
+
translateY: interpolate(
|
38
|
+
scrollOffset.value,
|
39
|
+
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
|
40
|
+
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
|
41
|
+
),
|
42
|
+
},
|
43
|
+
{
|
44
|
+
scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
|
45
|
+
},
|
46
|
+
],
|
47
|
+
};
|
48
|
+
});
|
49
|
+
|
50
|
+
return (
|
51
|
+
<ThemedView style={styles.container}>
|
52
|
+
<Animated.ScrollView
|
53
|
+
ref={scrollRef}
|
54
|
+
scrollEventThrottle={16}
|
55
|
+
scrollIndicatorInsets={{ bottom }}
|
56
|
+
contentContainerStyle={{ paddingBottom: bottom }}>
|
57
|
+
<Animated.View
|
58
|
+
style={[
|
59
|
+
styles.header,
|
60
|
+
{ backgroundColor: headerBackgroundColor[colorScheme] },
|
61
|
+
headerAnimatedStyle,
|
62
|
+
]}>
|
63
|
+
{headerImage}
|
64
|
+
</Animated.View>
|
65
|
+
<ThemedView style={styles.content}>{children}</ThemedView>
|
66
|
+
</Animated.ScrollView>
|
67
|
+
</ThemedView>
|
68
|
+
);
|
69
|
+
}
|
70
|
+
|
71
|
+
const styles = StyleSheet.create({
|
72
|
+
container: {
|
73
|
+
flex: 1,
|
74
|
+
},
|
75
|
+
header: {
|
76
|
+
height: HEADER_HEIGHT,
|
77
|
+
overflow: 'hidden',
|
78
|
+
},
|
79
|
+
content: {
|
80
|
+
flex: 1,
|
81
|
+
padding: 32,
|
82
|
+
gap: 16,
|
83
|
+
overflow: 'hidden',
|
84
|
+
},
|
85
|
+
});
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import { StyleSheet, Text, type TextProps } from 'react-native';
|
2
|
+
|
3
|
+
import { useThemeColor } from '@/hooks/useThemeColor';
|
4
|
+
|
5
|
+
export type ThemedTextProps = TextProps & {
|
6
|
+
lightColor?: string;
|
7
|
+
darkColor?: string;
|
8
|
+
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
|
9
|
+
};
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Renders themed text with different styles based on the type and color scheme.
|
13
|
+
* @param root0 - The props for the themed text component.
|
14
|
+
* @param root0.style - Additional styles to apply to the text.
|
15
|
+
* @param root0.lightColor - The color to use in light mode.
|
16
|
+
* @param root0.darkColor - The color to use in dark mode.
|
17
|
+
* @param root0.type - The type of text to render, which determines the style.
|
18
|
+
* @param root0.rest - Additional props for the text component.
|
19
|
+
* @returns The themed text component.
|
20
|
+
*/
|
21
|
+
export function ThemedText({
|
22
|
+
style,
|
23
|
+
lightColor,
|
24
|
+
darkColor,
|
25
|
+
type = 'default',
|
26
|
+
...rest
|
27
|
+
}: ThemedTextProps) {
|
28
|
+
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
|
29
|
+
|
30
|
+
return (
|
31
|
+
<Text
|
32
|
+
style={[
|
33
|
+
{ color },
|
34
|
+
type === 'default' ? styles.default : undefined,
|
35
|
+
type === 'title' ? styles.title : undefined,
|
36
|
+
type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
|
37
|
+
type === 'subtitle' ? styles.subtitle : undefined,
|
38
|
+
type === 'link' ? styles.link : undefined,
|
39
|
+
style,
|
40
|
+
]}
|
41
|
+
{...rest}
|
42
|
+
/>
|
43
|
+
);
|
44
|
+
}
|
45
|
+
|
46
|
+
const styles = StyleSheet.create({
|
47
|
+
default: {
|
48
|
+
fontSize: 16,
|
49
|
+
lineHeight: 24,
|
50
|
+
},
|
51
|
+
defaultSemiBold: {
|
52
|
+
fontSize: 16,
|
53
|
+
lineHeight: 24,
|
54
|
+
fontWeight: '600',
|
55
|
+
},
|
56
|
+
title: {
|
57
|
+
fontSize: 32,
|
58
|
+
fontWeight: 'bold',
|
59
|
+
lineHeight: 32,
|
60
|
+
},
|
61
|
+
subtitle: {
|
62
|
+
fontSize: 20,
|
63
|
+
fontWeight: 'bold',
|
64
|
+
},
|
65
|
+
link: {
|
66
|
+
lineHeight: 30,
|
67
|
+
fontSize: 16,
|
68
|
+
color: '#0a7ea4',
|
69
|
+
},
|
70
|
+
});
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { View, type ViewProps } from 'react-native';
|
2
|
+
|
3
|
+
import { useThemeColor } from '@/hooks/useThemeColor';
|
4
|
+
|
5
|
+
export type ThemedViewProps = ViewProps & {
|
6
|
+
lightColor?: string;
|
7
|
+
darkColor?: string;
|
8
|
+
};
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Renders a themed view with a background color based on the color scheme.
|
12
|
+
* @param root0 - The props for the themed view component.
|
13
|
+
* @param root0.style - Additional styles to apply to the view.
|
14
|
+
* @param root0.lightColor - The color to use in light mode.
|
15
|
+
* @param root0.darkColor - The color to use in dark mode.
|
16
|
+
* @param root0.rest - Additional props for the view component.
|
17
|
+
* @returns The themed view component.
|
18
|
+
*/
|
19
|
+
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
20
|
+
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
21
|
+
|
22
|
+
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
23
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
3
|
+
|
4
|
+
export function IconSymbol({
|
5
|
+
name,
|
6
|
+
size = 24,
|
7
|
+
color,
|
8
|
+
style,
|
9
|
+
weight = 'regular',
|
10
|
+
}: {
|
11
|
+
name: SymbolViewProps['name'];
|
12
|
+
size?: number;
|
13
|
+
color: string;
|
14
|
+
style?: StyleProp<ViewStyle>;
|
15
|
+
weight?: SymbolWeight;
|
16
|
+
}) {
|
17
|
+
return (
|
18
|
+
<SymbolView
|
19
|
+
weight={weight}
|
20
|
+
tintColor={color}
|
21
|
+
resizeMode="scaleAspectFit"
|
22
|
+
name={name}
|
23
|
+
style={[
|
24
|
+
{
|
25
|
+
width: size,
|
26
|
+
height: size,
|
27
|
+
},
|
28
|
+
style,
|
29
|
+
]}
|
30
|
+
/>
|
31
|
+
);
|
32
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
// Fallback for using MaterialIcons on Android and web.
|
2
|
+
|
3
|
+
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
4
|
+
import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
|
5
|
+
import { ComponentProps } from 'react';
|
6
|
+
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
|
7
|
+
|
8
|
+
type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>;
|
9
|
+
type IconSymbolName = keyof typeof MAPPING;
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Add your SF Symbols to Material Icons mappings here.
|
13
|
+
* - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
|
14
|
+
* - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
|
15
|
+
*/
|
16
|
+
const MAPPING = {
|
17
|
+
'house.fill': 'home',
|
18
|
+
'paperplane.fill': 'send',
|
19
|
+
'chevron.left.forwardslash.chevron.right': 'code',
|
20
|
+
'chevron.right': 'chevron-right',
|
21
|
+
} as IconMapping;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
|
25
|
+
* This ensures a consistent look across platforms, and optimal resource usage.
|
26
|
+
* Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
|
27
|
+
*/
|
28
|
+
export function IconSymbol({
|
29
|
+
name,
|
30
|
+
size = 24,
|
31
|
+
color,
|
32
|
+
style,
|
33
|
+
}: {
|
34
|
+
name: IconSymbolName;
|
35
|
+
size?: number;
|
36
|
+
color: string | OpaqueColorValue;
|
37
|
+
style?: StyleProp<TextStyle>;
|
38
|
+
weight?: SymbolWeight;
|
39
|
+
}) {
|
40
|
+
return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
|
41
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
2
|
+
import { BlurView } from 'expo-blur';
|
3
|
+
import { StyleSheet } from 'react-native';
|
4
|
+
|
5
|
+
export default function BlurTabBarBackground() {
|
6
|
+
return (
|
7
|
+
<BlurView
|
8
|
+
// System chrome material automatically adapts to the system's theme
|
9
|
+
// and matches the native tab bar appearance on iOS.
|
10
|
+
tint="systemChromeMaterial"
|
11
|
+
intensity={100}
|
12
|
+
style={StyleSheet.absoluteFill}
|
13
|
+
/>
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
export function useBottomTabOverflow() {
|
18
|
+
return useBottomTabBarHeight();
|
19
|
+
}
|
@@ -6,22 +6,22 @@
|
|
6
6
|
const tintColorLight = '#0a7ea4';
|
7
7
|
const tintColorDark = '#fff';
|
8
8
|
|
9
|
-
export const
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
9
|
+
export const colors = {
|
10
|
+
light: {
|
11
|
+
text: '#11181C',
|
12
|
+
background: '#fff',
|
13
|
+
tint: tintColorLight,
|
14
|
+
icon: '#687076',
|
15
|
+
tabIconDefault: '#687076',
|
16
|
+
tabIconSelected: tintColorLight,
|
17
|
+
},
|
18
|
+
dark: {
|
19
|
+
text: '#ECEDEE',
|
20
|
+
background: '#151718',
|
21
|
+
tint: tintColorDark,
|
22
|
+
icon: '#9BA1A6',
|
23
|
+
tabIconDefault: '#9BA1A6',
|
24
|
+
tabIconSelected: tintColorDark,
|
25
|
+
},
|
26
26
|
};
|
27
27
|
|
@@ -1,18 +1,18 @@
|
|
1
1
|
export const NAV_THEME = {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
2
|
+
light: {
|
3
|
+
background: 'hsl(0 0% 100%)', // background
|
4
|
+
border: 'hsl(240 5.9% 90%)', // border
|
5
|
+
card: 'hsl(0 0% 100%)', // card
|
6
|
+
notification: 'hsl(0 84.2% 60.2%)', // destructive
|
7
|
+
primary: 'hsl(240 5.9% 10%)', // primary
|
8
|
+
text: 'hsl(240 10% 3.9%)', // foreground
|
9
|
+
},
|
10
|
+
dark: {
|
11
|
+
background: 'hsl(240 10% 3.9%)', // background
|
12
|
+
border: 'hsl(240 3.7% 15.9%)', // border
|
13
|
+
card: 'hsl(240 10% 3.9%)', // card
|
14
|
+
notification: 'hsl(0 72% 51%)', // destructive
|
15
|
+
primary: 'hsl(0 0% 98%)', // primary
|
16
|
+
text: 'hsl(0 0% 98%)', // foreground
|
17
|
+
},
|
18
|
+
};
|
@@ -1,10 +1,8 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
1
2
|
// https://docs.expo.dev/guides/using-eslint/
|
2
|
-
const { defineConfig } = require('eslint/config');
|
3
|
-
const expoConfig = require('eslint-config-expo/flat');
|
4
|
-
|
5
3
|
import eslint from '@eslint/js';
|
4
|
+
import expoConfig from 'eslint-config-expo/flat.js';
|
6
5
|
// @ts-ignore
|
7
|
-
import importPlugin from 'eslint-plugin-import';
|
8
6
|
import jsdoc from 'eslint-plugin-jsdoc';
|
9
7
|
import namedImportSpacing from 'eslint-plugin-named-import-spacing';
|
10
8
|
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
@@ -13,31 +11,28 @@ import tseslint from 'typescript-eslint';
|
|
13
11
|
|
14
12
|
export default tseslint.config(
|
15
13
|
eslint.configs.recommended,
|
14
|
+
// eslint-disable-next-line import/no-named-as-default-member
|
16
15
|
tseslint.configs.recommendedTypeChecked,
|
17
16
|
jsdoc.configs['flat/recommended-typescript'],
|
18
|
-
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
18
|
+
expoConfig,
|
19
19
|
{
|
20
20
|
ignores: ['dist/*'],
|
21
21
|
},
|
22
22
|
{
|
23
23
|
plugins: {
|
24
|
-
'@typescript-eslint': tseslint.plugin,
|
25
24
|
'simple-import-sort': simpleImportSort,
|
26
|
-
import: importPlugin,
|
27
25
|
'named-import-spacing': namedImportSpacing,
|
28
|
-
jsdoc,
|
29
26
|
},
|
30
27
|
settings: {
|
31
28
|
'import/resolver': {
|
32
29
|
typescript: {
|
33
|
-
project:
|
34
|
-
'typescript/tsconfig.json',
|
35
|
-
'lib/tsconfig.json'
|
36
|
-
],
|
30
|
+
project: './tsconfig.json',
|
37
31
|
}
|
38
32
|
}
|
39
33
|
},
|
40
34
|
languageOptions: {
|
35
|
+
// eslint-disable-next-line import/no-named-as-default-member
|
41
36
|
parser: tseslint.parser,
|
42
37
|
parserOptions: {
|
43
38
|
projectService: true,
|
@@ -99,7 +94,7 @@ export default tseslint.config(
|
|
99
94
|
'no-multi-spaces': 'error',
|
100
95
|
'import/newline-after-import': ['error', { count: 1 }],
|
101
96
|
'named-import-spacing/named-import-spacing': 2,
|
102
|
-
'no-unused-vars': 'warn',
|
97
|
+
'@typescript-eslint/no-unused-vars': 'warn',
|
103
98
|
'import/no-unresolved': [0],
|
104
99
|
'no-forbidden-relative-imports': [0],
|
105
100
|
'@typescript-eslint/triple-slash-reference': 'off',
|
@@ -119,8 +114,13 @@ export default tseslint.config(
|
|
119
114
|
'@typescript-eslint/naming-convention': [
|
120
115
|
'error',
|
121
116
|
{
|
122
|
-
selector: ['variable'
|
123
|
-
format: ['camelCase'],
|
117
|
+
selector: ['variable'],
|
118
|
+
format: ['camelCase', 'UPPER_CASE'],
|
119
|
+
leadingUnderscore: 'allow',
|
120
|
+
},
|
121
|
+
{
|
122
|
+
selector: ['function'],
|
123
|
+
format: ['camelCase', 'PascalCase'],
|
124
124
|
leadingUnderscore: 'allow',
|
125
125
|
},
|
126
126
|
{
|
@@ -175,10 +175,6 @@ export default tseslint.config(
|
|
175
175
|
selector: 'typeLike',
|
176
176
|
format: ['PascalCase'],
|
177
177
|
},
|
178
|
-
{
|
179
|
-
selector: 'function',
|
180
|
-
format: ['UPPER_CASE'],
|
181
|
-
},
|
182
178
|
],
|
183
179
|
},
|
184
180
|
},
|
@@ -1,11 +1,17 @@
|
|
1
1
|
import { useColorScheme as useNativewindColorScheme } from 'nativewind';
|
2
2
|
|
3
|
+
/**
|
4
|
+
* Custom hook to get the current color scheme and provide methods to change it.
|
5
|
+
* This hook uses the nativewind's useColorScheme to access the color scheme.
|
6
|
+
* @returns An object containing the current color scheme, a method to set the color scheme,
|
7
|
+
*/
|
3
8
|
export function useColorScheme() {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme();
|
10
|
+
|
11
|
+
return {
|
12
|
+
colorScheme: colorScheme ?? 'dark',
|
13
|
+
isDarkColorScheme: colorScheme === 'dark',
|
14
|
+
setColorScheme,
|
15
|
+
toggleColorScheme,
|
16
|
+
};
|
11
17
|
}
|
@@ -3,19 +3,22 @@ import { useColorScheme as useRNColorScheme } from 'react-native';
|
|
3
3
|
|
4
4
|
/**
|
5
5
|
* To support static rendering, this value needs to be re-calculated on the client side for web
|
6
|
+
* This hook returns the color scheme after the component has mounted, ensuring that the initial render
|
7
|
+
* does not depend on the color scheme.
|
8
|
+
* @returns The current color scheme ('light' or 'dark') after hydration.
|
6
9
|
*/
|
7
10
|
export function useColorScheme() {
|
8
|
-
|
11
|
+
const [hasHydrated, setHasHydrated] = useState(false);
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
useEffect(() => {
|
14
|
+
setHasHydrated(true);
|
15
|
+
}, []);
|
13
16
|
|
14
|
-
|
17
|
+
const colorScheme = useRNColorScheme();
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
if (hasHydrated) {
|
20
|
+
return colorScheme;
|
21
|
+
}
|
19
22
|
|
20
|
-
|
23
|
+
return 'light';
|
21
24
|
}
|
@@ -3,19 +3,28 @@
|
|
3
3
|
* https://docs.expo.dev/guides/color-schemes/
|
4
4
|
*/
|
5
5
|
|
6
|
-
import {
|
6
|
+
import { colors } from '@/constants/Colors';
|
7
|
+
|
7
8
|
import { useColorScheme } from './useColorSchemeRN';
|
8
9
|
|
10
|
+
/**
|
11
|
+
* Returns the theme color based on the current color scheme and the provided color name.
|
12
|
+
* @param props - An object containing light and dark color properties.
|
13
|
+
* @param props.light - The color to use in light mode.
|
14
|
+
* @param props.dark - The color to use in dark mode.
|
15
|
+
* @param colorName - The name of the color to retrieve from the colors object.
|
16
|
+
* @returns The color corresponding to the current color scheme.
|
17
|
+
*/
|
9
18
|
export function useThemeColor(
|
10
|
-
|
11
|
-
|
19
|
+
props: { light?: string; dark?: string },
|
20
|
+
colorName: keyof typeof colors.light
|
12
21
|
) {
|
13
|
-
|
14
|
-
|
22
|
+
const theme = useColorScheme() ?? 'light';
|
23
|
+
const colorFromProps = props[theme];
|
15
24
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
if (colorFromProps) {
|
26
|
+
return colorFromProps;
|
27
|
+
} else {
|
28
|
+
return colors[theme][colorName];
|
29
|
+
}
|
21
30
|
}
|
package/files/expo/lib/data.ts
CHANGED
@@ -1,45 +1,48 @@
|
|
1
1
|
import { Effect, Schedule, Schema } from 'effect';
|
2
|
-
|
2
|
+
|
3
|
+
import { client, ClientError } from './wix';
|
3
4
|
|
4
5
|
const dataSchema = Schema.Struct({
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
source: Schema.String,
|
7
|
+
content: Schema.String,
|
8
|
+
_id: Schema.String,
|
9
|
+
_owner: Schema.String,
|
10
|
+
_createdDate: Schema.Any,
|
11
|
+
_updatedDate: Schema.Any,
|
11
12
|
});
|
12
13
|
|
13
|
-
const COLLECTION_NAME =
|
14
|
+
const COLLECTION_NAME = 'dailySpiritQuotes';
|
14
15
|
|
15
16
|
export const getQuote = () => Effect.gen(function* () {
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
const count = yield* Effect.retry(Effect.tryPromise({
|
18
|
+
try: () => client.items.query(COLLECTION_NAME).count(),
|
19
|
+
catch: (error) => {
|
20
|
+
console.error('Error fetching quotes:', error);
|
21
|
+
Effect.fail(new ClientError());
|
22
|
+
}
|
23
|
+
}), Schedule.fromDelays(50, 100, 200, 400, 800));
|
24
|
+
|
25
|
+
if (count === 0) {
|
26
|
+
return yield* Effect.fail(new Error('No quotes found in the collection.'));
|
27
|
+
}
|
27
28
|
|
28
|
-
|
29
|
+
const randomIndex = Math.floor(Math.random() * count);
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
const data = yield* Effect.retry(Effect.tryPromise({
|
32
|
+
try: () => client.items.query(COLLECTION_NAME).skip(randomIndex).limit(1).find(),
|
33
|
+
catch: (error) => {
|
34
|
+
console.error('Error fetching quotes:', error);
|
35
|
+
Effect.fail(new ClientError());
|
36
|
+
}
|
37
|
+
}), Schedule.fromDelays(50, 100, 200, 400, 800));
|
37
38
|
|
38
|
-
|
39
|
+
yield* Effect.logDebug(`Fetched ${JSON.stringify(data, null, 2)}`);
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
const quote = yield* Schema.decodeUnknown(dataSchema)(data.items[0]);
|
42
|
+
|
43
|
+
return { source: quote.source, content: quote.content };
|
42
44
|
}).pipe(Effect.catchAll((error) => {
|
43
|
-
|
44
|
-
|
45
|
-
})
|
45
|
+
console.error('Failed to fetch quote:', error);
|
46
|
+
|
47
|
+
return Effect.succeed({ source: 'The Lord', content: 'The ways of the Lord are inscrutable.' });
|
48
|
+
}));
|
@@ -1,6 +1,11 @@
|
|
1
|
-
import {
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
3
3
|
|
4
|
+
/**
|
5
|
+
* Combines class names and merges Tailwind CSS classes.
|
6
|
+
* @param inputs - The class names to combine.
|
7
|
+
* @returns A string of combined class names.
|
8
|
+
*/
|
4
9
|
export function cn(...inputs: ClassValue[]) {
|
5
|
-
|
10
|
+
return twMerge(clsx(inputs));
|
6
11
|
}
|