Squash merge feat/expo-app: app-expo, .cursor, workflows, package.json, .husky; remove app-android, app-ios, react-app

This commit is contained in:
Kevin
2026-03-19 01:12:17 +08:00
parent 9e4f301ab9
commit b4f4369b7d
544 changed files with 23707 additions and 67151 deletions

View File

@@ -0,0 +1,5 @@
import { useAppSettingsContext } from '@/core/app-settings-context';
export function useAppSettings() {
return useAppSettingsContext();
}

View File

@@ -0,0 +1,95 @@
import { useMemo } from 'react';
import { useWindowDimensions } from 'react-native';
import { Breakpoints } from '@/constants/layout';
export type BreakpointName = keyof typeof Breakpoints | 'base';
type BreakpointMatches = Record<keyof typeof Breakpoints, boolean>;
const BREAKPOINT_ORDER: (keyof typeof Breakpoints)[] = [
'sm',
'md',
'lg',
'xl',
'2xl',
];
const ALL_BREAKPOINTS: BreakpointName[] = [
'base',
'sm',
'md',
'lg',
'xl',
'2xl',
];
/**
* Returns the current breakpoint based on window width (mobile-first).
*
* - `base`: width < 390px (compact phones)
* - `sm`..`2xl`: width >= that breakpoint
*/
export function useBreakpoint(): BreakpointName {
const { width } = useWindowDimensions();
for (let i = BREAKPOINT_ORDER.length - 1; i >= 0; i--) {
const name = BREAKPOINT_ORDER[i];
if (width >= Breakpoints[name]) return name;
}
return 'base';
}
/**
* Returns booleans for each breakpoint (mobile-first: true when width >= that breakpoint).
* Memoized to avoid unnecessary re-renders.
*/
export function useBreakpointMatches(): BreakpointMatches {
const { width } = useWindowDimensions();
return useMemo(
() => ({
sm: width >= Breakpoints.sm,
md: width >= Breakpoints.md,
lg: width >= Breakpoints.lg,
xl: width >= Breakpoints.xl,
'2xl': width >= Breakpoints['2xl'],
}),
[width],
);
}
/**
* Returns true if width >= the given breakpoint (mobile-first).
* Use when you need a one-off check without the full matches object.
*/
export function isBreakpointUp(
width: number,
breakpoint: keyof typeof Breakpoints,
): boolean {
return width >= Breakpoints[breakpoint];
}
/**
* Returns the responsive value for the current breakpoint (mobile-first).
* Picks the value for the largest breakpoint that matches; falls back to smaller breakpoints if not defined.
*
* @example
* const columns = useBreakpointValue({ base: 1, sm: 2, md: 3 });
* // width 350: 1, width 400: 2, width 800: 3
*
* @example
* const padding = useBreakpointValue({ base: 16, md: 24 });
*/
export function useBreakpointValue<T>(
values: Partial<Record<BreakpointName, T>>,
): T | undefined {
const breakpoint = useBreakpoint();
const idx = ALL_BREAKPOINTS.indexOf(breakpoint);
for (let i = idx; i >= 0; i--) {
const v = values[ALL_BREAKPOINTS[i]];
if (v !== undefined) return v;
}
return undefined;
}

View File

@@ -0,0 +1,9 @@
/**
* Re-export NativeWind's useColorScheme for unified light/dark handling.
* Uses Appearance API on native and prefers-color-scheme on web.
* Supports manual override via setColorScheme() for user toggles.
*
* @see https://www.nativewind.dev/docs/core-concepts/dark-mode
* @see https://www.nativewind.dev/docs/api/use-color-scheme
*/
export { useColorScheme } from 'nativewind';

View File

@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react';
import { useColorScheme as useNativeWindColorScheme } from 'nativewind';
/**
* Web override: avoid hydration mismatch during static rendering.
* Returns 'light' until client hydration, then uses NativeWind's value.
*/
export function useColorScheme() {
const [hasHydrated, setHasHydrated] = useState(false);
useEffect(() => setHasHydrated(true), []);
const { colorScheme, setColorScheme, toggleColorScheme } =
useNativeWindColorScheme();
return {
colorScheme: hasHydrated ? (colorScheme ?? 'light') : 'light',
setColorScheme,
toggleColorScheme,
};
}

View File

@@ -0,0 +1,16 @@
import { useAppSettingsContext } from '@/core/app-settings-context';
import { getThemeColors } from '@/constants/theme-bridge';
import { useColorScheme } from '@/hooks/use-color-scheme';
/**
* Returns the current theme's resolved colors from design-tokens.
* Use this for inline styles when NativeWind className is not suitable.
* Resolves themeName (from app settings) + colorScheme (light/dark).
* Must be used within AppSettingsProvider.
*/
export function useThemeColors() {
const { themeName } = useAppSettingsContext();
const { colorScheme } = useColorScheme();
const resolved = colorScheme === 'dark' ? 'dark' : 'light';
return getThemeColors(themeName, resolved);
}