@lunar-kit/core 0.1.1 → 0.1.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/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/package.json +8 -2
- package/src/hooks/useTheme.ts +23 -0
- package/src/hooks/useThemeColors.ts +14 -0
- package/src/hooks/useToolbar.react-navigation.tsx +61 -0
- package/src/hooks/useToolbar.tsx +61 -0
- package/src/lib/theme.ts +88 -0
- package/src/lib/utils.ts +6 -0
- package/src/navigation/react-navigation/base-router.tsx +15 -0
- package/src/providers/theme-provider.tsx +36 -0
- package/src/stores/theme.ts +24 -0
- package/src/tailwind.config.js +55 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
declare const REGISTRY_URL = "https://raw.githubusercontent.com/yourusername/lunar-kit/main/packages/core/src/registry";
|
|
2
2
|
declare const LOCAL_REGISTRY_PATH: string;
|
|
3
3
|
declare const LOCAL_COMPONENTS_PATH: string;
|
|
4
|
+
declare const LOCAL_SOURCE_PATH: string;
|
|
4
5
|
|
|
5
|
-
export { LOCAL_COMPONENTS_PATH, LOCAL_REGISTRY_PATH, REGISTRY_URL };
|
|
6
|
+
export { LOCAL_COMPONENTS_PATH, LOCAL_REGISTRY_PATH, LOCAL_SOURCE_PATH, REGISTRY_URL };
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@ var __dirname = dirname(__filename);
|
|
|
7
7
|
var REGISTRY_URL = "https://raw.githubusercontent.com/yourusername/lunar-kit/main/packages/core/src/registry";
|
|
8
8
|
var LOCAL_REGISTRY_PATH = path.join(__dirname, "..", "src", "registry");
|
|
9
9
|
var LOCAL_COMPONENTS_PATH = path.join(__dirname, "..", "src", "components");
|
|
10
|
+
var LOCAL_SOURCE_PATH = path.join(__dirname, "..", "src");
|
|
10
11
|
export {
|
|
11
12
|
LOCAL_COMPONENTS_PATH,
|
|
12
13
|
LOCAL_REGISTRY_PATH,
|
|
14
|
+
LOCAL_SOURCE_PATH,
|
|
13
15
|
REGISTRY_URL
|
|
14
16
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunar-kit/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Shared registry and components for Lunar Kit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"src/registry",
|
|
13
|
-
"src/components"
|
|
13
|
+
"src/components",
|
|
14
|
+
"src/providers",
|
|
15
|
+
"src/hooks",
|
|
16
|
+
"src/stores",
|
|
17
|
+
"src/lib",
|
|
18
|
+
"src/navigation",
|
|
19
|
+
"src/tailwind.config.js"
|
|
14
20
|
],
|
|
15
21
|
"keywords": [
|
|
16
22
|
"react-native",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// hooks/use-theme.ts
|
|
2
|
+
import { useThemeStore } from '@/stores';
|
|
3
|
+
import { useColorScheme as useDeviceColorScheme } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function useTheme() {
|
|
6
|
+
const deviceTheme = useDeviceColorScheme();
|
|
7
|
+
const { theme, setTheme } = useThemeStore();
|
|
8
|
+
|
|
9
|
+
const activeColorScheme =
|
|
10
|
+
theme === 'system'
|
|
11
|
+
? deviceTheme === 'dark'
|
|
12
|
+
? 'dark'
|
|
13
|
+
: 'light'
|
|
14
|
+
: theme === 'dark'
|
|
15
|
+
? 'dark'
|
|
16
|
+
: 'light';
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
theme,
|
|
20
|
+
colorScheme: activeColorScheme,
|
|
21
|
+
setTheme,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useColorScheme } from 'nativewind';
|
|
3
|
+
import { lightThemeColors, darkThemeColors } from '@/lib/theme';
|
|
4
|
+
|
|
5
|
+
export function useThemeColors() {
|
|
6
|
+
const { colorScheme } = useColorScheme();
|
|
7
|
+
|
|
8
|
+
const colors = useMemo(() => {
|
|
9
|
+
const result = colorScheme === 'dark' ? darkThemeColors : lightThemeColors;
|
|
10
|
+
return result;
|
|
11
|
+
}, [colorScheme]);
|
|
12
|
+
|
|
13
|
+
return { colors, colorScheme: colorScheme ?? 'light' };
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useNavigation } from 'expo-router';
|
|
3
|
+
import { Pressable, Text } from 'react-native';
|
|
4
|
+
import { useThemeColors } from './useThemeColors';
|
|
5
|
+
|
|
6
|
+
interface HeaderOptions {
|
|
7
|
+
headerShown?: boolean;
|
|
8
|
+
title?: string;
|
|
9
|
+
headerLeft?: () => React.ReactNode;
|
|
10
|
+
headerRight?: () => React.ReactNode;
|
|
11
|
+
headerStyle?: {
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
};
|
|
14
|
+
headerTintColor?: string;
|
|
15
|
+
headerTitleStyle?: {
|
|
16
|
+
fontWeight?: string;
|
|
17
|
+
fontSize?: number;
|
|
18
|
+
};
|
|
19
|
+
headerShadowVisible?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UseToolbarOptions {
|
|
23
|
+
title?: string;
|
|
24
|
+
backHandle?: () => void;
|
|
25
|
+
right?: React.ReactNode;
|
|
26
|
+
left?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useToolbar(options: UseToolbarOptions) {
|
|
30
|
+
const navigation = useNavigation();
|
|
31
|
+
const { colors } = useThemeColors();
|
|
32
|
+
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
navigation.setOptions({
|
|
35
|
+
headerShown: true,
|
|
36
|
+
title: options.title || '',
|
|
37
|
+
headerStyle: {
|
|
38
|
+
backgroundColor: colors.background,
|
|
39
|
+
},
|
|
40
|
+
headerTintColor: colors.foreground,
|
|
41
|
+
headerTitleStyle: {
|
|
42
|
+
fontWeight: '600',
|
|
43
|
+
fontSize: 18,
|
|
44
|
+
},
|
|
45
|
+
headerShadowVisible: false,
|
|
46
|
+
...(options.backHandle !== undefined && {
|
|
47
|
+
headerLeft: () => (
|
|
48
|
+
<Pressable onPress={options.backHandle} className="ml-4">
|
|
49
|
+
<Text style={{ fontSize: 18, color: colors.foreground }}>←</Text>
|
|
50
|
+
</Pressable>
|
|
51
|
+
),
|
|
52
|
+
}),
|
|
53
|
+
...(options.right && {
|
|
54
|
+
headerRight: () => <>{options.right}</>,
|
|
55
|
+
}),
|
|
56
|
+
...(options.left && {
|
|
57
|
+
headerLeft: () => <>{options.left}</>,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
}, [navigation, colors.background, colors.foreground, options.title, options.backHandle, options.right, options.left]);
|
|
61
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useNavigation } from 'expo-router';
|
|
3
|
+
import { Pressable, Text } from 'react-native';
|
|
4
|
+
import { useThemeColors } from './useThemeColors';
|
|
5
|
+
|
|
6
|
+
interface HeaderOptions {
|
|
7
|
+
headerShown?: boolean;
|
|
8
|
+
title?: string;
|
|
9
|
+
headerLeft?: () => React.ReactNode;
|
|
10
|
+
headerRight?: () => React.ReactNode;
|
|
11
|
+
headerStyle?: {
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
};
|
|
14
|
+
headerTintColor?: string;
|
|
15
|
+
headerTitleStyle?: {
|
|
16
|
+
fontWeight?: string;
|
|
17
|
+
fontSize?: number;
|
|
18
|
+
};
|
|
19
|
+
headerShadowVisible?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UseToolbarOptions {
|
|
23
|
+
title?: string;
|
|
24
|
+
backHandle?: () => void;
|
|
25
|
+
right?: React.ReactNode;
|
|
26
|
+
left?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useToolbar(options: UseToolbarOptions) {
|
|
30
|
+
const navigation = useNavigation();
|
|
31
|
+
const { colors } = useThemeColors();
|
|
32
|
+
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
navigation.setOptions({
|
|
35
|
+
headerShown: true,
|
|
36
|
+
title: options.title || '',
|
|
37
|
+
headerStyle: {
|
|
38
|
+
backgroundColor: colors.background,
|
|
39
|
+
},
|
|
40
|
+
headerTintColor: colors.foreground,
|
|
41
|
+
headerTitleStyle: {
|
|
42
|
+
fontWeight: '600',
|
|
43
|
+
fontSize: 18,
|
|
44
|
+
},
|
|
45
|
+
headerShadowVisible: false,
|
|
46
|
+
...(options.backHandle !== undefined && {
|
|
47
|
+
headerLeft: () => (
|
|
48
|
+
<Pressable onPress={options.backHandle} className="ml-4">
|
|
49
|
+
<Text style={{ fontSize: 18, color: colors.foreground }}>←</Text>
|
|
50
|
+
</Pressable>
|
|
51
|
+
),
|
|
52
|
+
}),
|
|
53
|
+
...(options.right && {
|
|
54
|
+
headerRight: () => <>{options.right}</>,
|
|
55
|
+
}),
|
|
56
|
+
...(options.left && {
|
|
57
|
+
headerLeft: () => <>{options.left}</>,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
}, [navigation, colors.background, colors.foreground, options.title, options.backHandle, options.right, options.left]);
|
|
61
|
+
}
|
package/src/lib/theme.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// lib/theme.ts
|
|
2
|
+
import { vars } from 'nativewind';
|
|
3
|
+
|
|
4
|
+
// Define HSL values sebagai constants
|
|
5
|
+
const lightThemeVars = {
|
|
6
|
+
'--background': '0 0% 100%',
|
|
7
|
+
'--foreground': '222.2 84% 4.9%',
|
|
8
|
+
'--card': '0 0% 100%',
|
|
9
|
+
'--card-foreground': '222.2 84% 4.9%',
|
|
10
|
+
'--primary': '221.2 83.2% 53.3%',
|
|
11
|
+
'--primary-foreground': '210 40% 98%',
|
|
12
|
+
'--secondary': '210 40% 96.1%',
|
|
13
|
+
'--secondary-foreground': '222.2 47.4% 11.2%',
|
|
14
|
+
'--muted': '210 40% 96.1%',
|
|
15
|
+
'--muted-foreground': '215.4 16.3% 46.9%',
|
|
16
|
+
'--accent': '210 40% 96.1%',
|
|
17
|
+
'--accent-foreground': '222.2 47.4% 11.2%',
|
|
18
|
+
'--destructive': '0 84.2% 60.2%',
|
|
19
|
+
'--destructive-foreground': '210 40% 98%',
|
|
20
|
+
'--border': '214.3 31.8% 91.4%',
|
|
21
|
+
'--input': '214.3 31.8% 91.4%',
|
|
22
|
+
'--ring': '221.2 83.2% 53.3%',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const darkThemeVars = {
|
|
26
|
+
'--background': '222.2 84% 4.9%',
|
|
27
|
+
'--foreground': '210 40% 98%',
|
|
28
|
+
'--card': '222.2 84% 4.9%',
|
|
29
|
+
'--card-foreground': '210 40% 98%',
|
|
30
|
+
'--primary': '210 40% 98%',
|
|
31
|
+
'--primary-foreground': '222.2 47.4% 11.2%',
|
|
32
|
+
'--secondary': '217.2 32.6% 17.5%',
|
|
33
|
+
'--secondary-foreground': '210 40% 98%',
|
|
34
|
+
'--muted': '217.2 32.6% 17.5%',
|
|
35
|
+
'--muted-foreground': '215 20.2% 65.1%',
|
|
36
|
+
'--accent': '217.2 32.6% 17.5%',
|
|
37
|
+
'--accent-foreground': '210 40% 98%',
|
|
38
|
+
'--destructive': '0 84% 60%',
|
|
39
|
+
'--destructive-foreground': '210 40% 98%',
|
|
40
|
+
'--border': '217.2 32.6% 17.5%',
|
|
41
|
+
'--input': '217.2 32.6% 17.5%',
|
|
42
|
+
'--ring': '224.3 76.3% 48%',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Export vars untuk NativeWind
|
|
46
|
+
export const lightTheme = vars(lightThemeVars);
|
|
47
|
+
export const darkTheme = vars(darkThemeVars);
|
|
48
|
+
|
|
49
|
+
// Helper function untuk convert HSL string ke hex
|
|
50
|
+
function hslToHex(hsl: string): string {
|
|
51
|
+
const [h, s, l] = hsl.split(' ').map(parseFloat);
|
|
52
|
+
const a = (s * Math.min(l, 100 - l)) / 10000;
|
|
53
|
+
const f = (n: number) => {
|
|
54
|
+
const k = (n + h / 30) % 12;
|
|
55
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
56
|
+
return Math.round((255 * color) / 100)
|
|
57
|
+
.toString(16)
|
|
58
|
+
.padStart(2, '0');
|
|
59
|
+
};
|
|
60
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Helper untuk convert theme vars object ke hex colors
|
|
64
|
+
function convertThemeToHex(themeVars: Record<string, string>) {
|
|
65
|
+
return {
|
|
66
|
+
background: hslToHex(themeVars['--background']),
|
|
67
|
+
foreground: hslToHex(themeVars['--foreground']),
|
|
68
|
+
card: hslToHex(themeVars['--card']),
|
|
69
|
+
cardForeground: hslToHex(themeVars['--card-foreground']),
|
|
70
|
+
primary: hslToHex(themeVars['--primary']),
|
|
71
|
+
primaryForeground: hslToHex(themeVars['--primary-foreground']),
|
|
72
|
+
secondary: hslToHex(themeVars['--secondary']),
|
|
73
|
+
secondaryForeground: hslToHex(themeVars['--secondary-foreground']),
|
|
74
|
+
muted: hslToHex(themeVars['--muted']),
|
|
75
|
+
mutedForeground: hslToHex(themeVars['--muted-foreground']),
|
|
76
|
+
accent: hslToHex(themeVars['--accent']),
|
|
77
|
+
accentForeground: hslToHex(themeVars['--accent-foreground']),
|
|
78
|
+
destructive: hslToHex(themeVars['--destructive']),
|
|
79
|
+
destructiveForeground: hslToHex(themeVars['--destructive-foreground']),
|
|
80
|
+
border: hslToHex(themeVars['--border']),
|
|
81
|
+
input: hslToHex(themeVars['--input']),
|
|
82
|
+
ring: hslToHex(themeVars['--ring']),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Export hex colors - auto-generated dari vars
|
|
87
|
+
export const lightThemeColors = convertThemeToHex(lightThemeVars);
|
|
88
|
+
export const darkThemeColors = convertThemeToHex(darkThemeVars);
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
2
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
3
|
+
import HomeScreen from './screens/HomeScreen';
|
|
4
|
+
|
|
5
|
+
const Stack = createNativeStackNavigator();
|
|
6
|
+
|
|
7
|
+
export default function Navigation() {
|
|
8
|
+
return (
|
|
9
|
+
<NavigationContainer>
|
|
10
|
+
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|
11
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
12
|
+
</Stack.Navigator>
|
|
13
|
+
</NavigationContainer>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// providers/theme-provider.tsx
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import { StatusBar, View } from 'react-native';
|
|
4
|
+
import { useColorScheme as useDeviceColorScheme } from 'react-native';
|
|
5
|
+
import { useColorScheme } from 'nativewind';
|
|
6
|
+
import { lightTheme, darkTheme } from '@/lib/theme';
|
|
7
|
+
import { useThemeStore } from '@/stores';
|
|
8
|
+
|
|
9
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
10
|
+
const deviceTheme = useDeviceColorScheme();
|
|
11
|
+
const { setColorScheme } = useColorScheme();
|
|
12
|
+
const theme = useThemeStore((state) => state.theme);
|
|
13
|
+
|
|
14
|
+
const activeColorScheme =
|
|
15
|
+
theme === 'system'
|
|
16
|
+
? deviceTheme ?? 'light'
|
|
17
|
+
: theme;
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setColorScheme(activeColorScheme);
|
|
21
|
+
}, [activeColorScheme, setColorScheme]);
|
|
22
|
+
|
|
23
|
+
const themeVars = activeColorScheme === 'dark' ? darkTheme : lightTheme;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<StatusBar
|
|
28
|
+
barStyle={activeColorScheme === 'dark' ? 'light-content' : 'dark-content'}
|
|
29
|
+
animated
|
|
30
|
+
/>
|
|
31
|
+
<View style={themeVars} className="flex-1 bg-background text-foreground">
|
|
32
|
+
{children}
|
|
33
|
+
</View>
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// stores/theme-store.ts
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
4
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
5
|
+
|
|
6
|
+
type Theme = 'light' | 'dark' | 'system';
|
|
7
|
+
|
|
8
|
+
interface ThemeState {
|
|
9
|
+
theme: Theme;
|
|
10
|
+
setTheme: (theme: Theme) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useThemeStore = create<ThemeState>()(
|
|
14
|
+
persist(
|
|
15
|
+
(set) => ({
|
|
16
|
+
theme: 'system',
|
|
17
|
+
setTheme: (theme) => set({ theme }),
|
|
18
|
+
}),
|
|
19
|
+
{
|
|
20
|
+
name: 'theme-storage',
|
|
21
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: [
|
|
4
|
+
"./App.{js,jsx,ts,tsx}",
|
|
5
|
+
"./app/**/*.{js,jsx,ts,tsx}",
|
|
6
|
+
"./src/**/*.{js,jsx,ts,tsx}"
|
|
7
|
+
],
|
|
8
|
+
presets: [require("nativewind/preset")],
|
|
9
|
+
darkMode: "class",
|
|
10
|
+
theme: {
|
|
11
|
+
extend: {
|
|
12
|
+
colors: {
|
|
13
|
+
border: 'hsl(var(--border))',
|
|
14
|
+
input: 'hsl(var(--input))',
|
|
15
|
+
ring: 'hsl(var(--ring))',
|
|
16
|
+
background: 'hsl(var(--background))',
|
|
17
|
+
foreground: 'hsl(var(--foreground))',
|
|
18
|
+
primary: {
|
|
19
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
20
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
21
|
+
},
|
|
22
|
+
secondary: {
|
|
23
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
24
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
25
|
+
},
|
|
26
|
+
destructive: {
|
|
27
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
28
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
29
|
+
},
|
|
30
|
+
muted: {
|
|
31
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
32
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
33
|
+
},
|
|
34
|
+
accent: {
|
|
35
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
36
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
37
|
+
},
|
|
38
|
+
card: {
|
|
39
|
+
DEFAULT: 'hsl(var(--card))',
|
|
40
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
plugins: [
|
|
46
|
+
function ({ addBase }) {
|
|
47
|
+
addBase({
|
|
48
|
+
// Default untuk semua element
|
|
49
|
+
'*': {
|
|
50
|
+
color: 'hsl(var(--foreground))',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|