@lunar-kit/core 0.1.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunar-kit/core",
3
- "version": "0.1.2",
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
+ }
@@ -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);
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -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
+ }