template_0205

This commit is contained in:
Sofio
2026-02-05 13:16:05 +08:00
commit d93e4d9c9f
197 changed files with 52810 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
const palette = {
neutral100: "#FFFFFF",
neutral200: "#F4F2F1",
neutral300: "#D7CEC9",
neutral400: "#B6ACA6",
neutral500: "#978F8A",
neutral600: "#564E4A",
neutral700: "#3C3836",
neutral800: "#191015",
neutral900: "#000000",
primary100: "#DBEAFE",
primary200: "#BFDBFE",
primary300: "#93C5FD",
primary400: "#60A5FA",
primary500: "#3B82F6",
primary600: "#2563EB",
secondary100: "#DCDDE9",
secondary200: "#BCC0D6",
secondary300: "#9196B9",
secondary400: "#626894",
secondary500: "#41476E",
accent100: "#FFEED4",
accent200: "#FFE1B2",
accent300: "#FDD495",
accent400: "#FBC878",
accent500: "#FFBB50",
angry100: "#F2D6CD",
angry500: "#C03403",
overlay20: "rgba(25, 16, 21, 0.2)",
overlay50: "rgba(25, 16, 21, 0.5)",
} as const
export const colors = {
/**
* The palette is available to use, but prefer using the name.
* This is only included for rare, one-off cases. Try to use
* semantic names as much as possible.
*/
palette,
/**
* A helper for making something see-thru.
*/
transparent: "rgba(0, 0, 0, 0)",
/**
* The default text color in many components.
*/
text: palette.neutral800,
/**
* Secondary text information.
*/
textDim: palette.neutral600,
/**
* The default color of the screen background.
*/
background: palette.neutral200,
/**
* The default border color.
*/
border: palette.neutral400,
/**
* The main tinting color.
*/
tint: palette.primary500,
/**
* The inactive tinting color.
*/
tintInactive: palette.neutral300,
/**
* A subtle color used for lines.
*/
separator: palette.neutral300,
/**
* Error messages.
*/
error: palette.angry500,
/**
* Error Background.
*/
errorBackground: palette.angry100,
} as const

View File

@@ -0,0 +1,50 @@
const palette = {
neutral900: "#FFFFFF",
neutral800: "#F4F2F1",
neutral700: "#D7CEC9",
neutral600: "#B6ACA6",
neutral500: "#978F8A",
neutral400: "#564E4A",
neutral300: "#3C3836",
neutral200: "#191015",
neutral100: "#000000",
primary600: "#DBEAFE",
primary500: "#93C5FD",
primary400: "#60A5FA",
primary300: "#3B82F6",
primary200: "#2563EB",
primary100: "#1D4ED8",
secondary500: "#DCDDE9",
secondary400: "#BCC0D6",
secondary300: "#9196B9",
secondary200: "#626894",
secondary100: "#41476E",
accent500: "#FFEED4",
accent400: "#FFE1B2",
accent300: "#FDD495",
accent200: "#FBC878",
accent100: "#FFBB50",
angry100: "#F2D6CD",
angry500: "#C03403",
overlay20: "rgba(25, 16, 21, 0.2)",
overlay50: "rgba(25, 16, 21, 0.5)",
} as const
export const colors = {
palette,
transparent: "rgba(0, 0, 0, 0)",
text: palette.neutral800,
textDim: palette.neutral600,
background: palette.neutral200,
border: palette.neutral400,
tint: palette.primary500,
tintInactive: palette.neutral300,
separator: palette.neutral300,
error: palette.angry500,
errorBackground: palette.angry100,
} as const

View File

@@ -0,0 +1,145 @@
import {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useMemo,
} from "react"
import { StyleProp, useColorScheme } from "react-native"
import {
DarkTheme as NavDarkTheme,
DefaultTheme as NavDefaultTheme,
Theme as NavTheme,
} from "@react-navigation/native"
import { useMMKVString } from "react-native-mmkv"
import { storage } from "@/utils/storage"
import { setImperativeTheming } from "./context.utils"
import { darkTheme, lightTheme } from "./theme"
import type {
AllowedStylesT,
ImmutableThemeContextModeT,
Theme,
ThemeContextModeT,
ThemedFnT,
ThemedStyle,
} from "./types"
export type ThemeContextType = {
navigationTheme: NavTheme
setThemeContextOverride: (newTheme: ThemeContextModeT) => void
theme: Theme
themeContext: ImmutableThemeContextModeT
themed: ThemedFnT
}
export const ThemeContext = createContext<ThemeContextType | null>(null)
export interface ThemeProviderProps {
initialContext?: ThemeContextModeT
}
/**
* The ThemeProvider is the heart and soul of the design token system. It provides a context wrapper
* for your entire app to consume the design tokens as well as global functionality like the app's theme.
*
* To get started, you want to wrap your entire app's JSX hierarchy in `ThemeProvider`
* and then use the `useAppTheme()` hook to access the theme context.
*
* Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/Theming/
*/
export const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({
children,
initialContext,
}) => {
// The operating system theme:
const systemColorScheme = useColorScheme()
// Our saved theme context: can be "light", "dark", or undefined (system theme)
const [themeScheme, setThemeScheme] = useMMKVString("ignite.themeScheme", storage)
/**
* This function is used to set the theme context and is exported from the useAppTheme() hook.
* - setThemeContextOverride("dark") sets the app theme to dark no matter what the system theme is.
* - setThemeContextOverride("light") sets the app theme to light no matter what the system theme is.
* - setThemeContextOverride(undefined) the app will follow the operating system theme.
*/
const setThemeContextOverride = useCallback(
(newTheme: ThemeContextModeT) => {
setThemeScheme(newTheme)
},
[setThemeScheme],
)
/**
* initialContext is the theme context passed in from the app.tsx file and always takes precedence.
* themeScheme is the value from MMKV. If undefined, we fall back to the system theme
* systemColorScheme is the value from the device. If undefined, we fall back to "light"
*/
const themeContext: ImmutableThemeContextModeT = useMemo(() => {
const t = initialContext || themeScheme || (!!systemColorScheme ? systemColorScheme : "light")
return t === "dark" ? "dark" : "light"
}, [initialContext, themeScheme, systemColorScheme])
const navigationTheme: NavTheme = useMemo(() => {
switch (themeContext) {
case "dark":
return NavDarkTheme
default:
return NavDefaultTheme
}
}, [themeContext])
const theme: Theme = useMemo(() => {
switch (themeContext) {
case "dark":
return darkTheme
default:
return lightTheme
}
}, [themeContext])
useEffect(() => {
setImperativeTheming(theme)
}, [theme])
const themed = useCallback(
<T,>(styleOrStyleFn: AllowedStylesT<T>) => {
const flatStyles = [styleOrStyleFn].flat(3) as (ThemedStyle<T> | StyleProp<T>)[]
const stylesArray = flatStyles.map((f) => {
if (typeof f === "function") {
return (f as ThemedStyle<T>)(theme)
} else {
return f
}
})
// Flatten the array of styles into a single object
return Object.assign({}, ...stylesArray) as T
},
[theme],
)
const value = {
navigationTheme,
theme,
themeContext,
setThemeContextOverride,
themed,
}
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
}
/**
* This is the primary hook that you will use to access the theme context in your components.
* Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/useAppTheme.tsx/
*/
export const useAppTheme = () => {
const context = useContext(ThemeContext)
if (!context) {
throw new Error("useAppTheme must be used within an ThemeProvider")
}
return context
}

View File

@@ -0,0 +1,25 @@
import type { Theme } from "./types"
const systemui = require("expo-system-ui")
/**
* Set the system UI background color to the given color. This is only available if the app has
* installed expo-system-ui.
*
* @param color The color to set the system UI background to
*/
export const setSystemUIBackgroundColor = (color: string) => {
if (systemui) {
systemui.setBackgroundColorAsync(color)
}
}
/**
* Set the app's native background color to match the theme.
* This is only available if the app has installed expo-system-ui
*
* @param theme The theme object to use for the background color
*/
export const setImperativeTheming = (theme: Theme) => {
setSystemUIBackgroundColor(theme.colors.background)
}

View File

@@ -0,0 +1,16 @@
import { s } from "@/utils/responsive"
/**
Use these spacings for margins/paddings and other whitespace throughout your app.
*/
export const spacing = {
xxxs: s(2),
xxs: s(4),
xs: s(8),
sm: s(12),
md: s(16),
lg: s(24),
xl: s(32),
xxl: s(48),
xxxl: s(64),
}

View File

@@ -0,0 +1,16 @@
import { s } from "@/utils/responsive"
const SPACING_MULTIPLIER = 1.0
// This is an example of how you can have different spacing values for different themes.
export const spacing = {
xxxs: s(2 * SPACING_MULTIPLIER),
xxs: s(4 * SPACING_MULTIPLIER),
xs: s(8 * SPACING_MULTIPLIER),
sm: s(12 * SPACING_MULTIPLIER),
md: s(16 * SPACING_MULTIPLIER),
lg: s(24 * SPACING_MULTIPLIER),
xl: s(32 * SPACING_MULTIPLIER),
xxl: s(48 * SPACING_MULTIPLIER),
xxxl: s(64 * SPACING_MULTIPLIER),
}

View File

@@ -0,0 +1,23 @@
import { ViewStyle } from "react-native"
import { spacing } from "./spacing"
/* Use this file to define styles that are used in multiple places in your app. */
export const $styles = {
row: { flexDirection: "row" } as ViewStyle,
flex1: { flex: 1 } as ViewStyle,
flexWrap: { flexWrap: "wrap" } as ViewStyle,
container: {
paddingTop: spacing.lg + spacing.xl,
paddingHorizontal: spacing.lg,
} as ViewStyle,
toggleInner: {
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
} as ViewStyle,
}

View File

@@ -0,0 +1,23 @@
import { colors as colorsLight } from "./colors"
import { colors as colorsDark } from "./colorsDark"
import { spacing as spacingLight } from "./spacing"
import { spacing as spacingDark } from "./spacingDark"
import { timing } from "./timing"
import type { Theme } from "./types"
import { typography } from "./typography"
// Here we define our themes.
export const lightTheme: Theme = {
colors: colorsLight,
spacing: spacingLight,
typography,
timing,
isDark: false,
}
export const darkTheme: Theme = {
colors: colorsDark,
spacing: spacingDark,
typography,
timing,
isDark: true,
}

View File

@@ -0,0 +1,6 @@
export const timing = {
/**
* The duration (ms) for quick animations.
*/
quick: 300,
}

View File

@@ -0,0 +1,64 @@
import type { StyleProp } from "react-native"
import { colors as colorsLight } from "./colors"
import { colors as colorsDark } from "./colorsDark"
import { spacing as spacingLight } from "./spacing"
import { spacing as spacingDark } from "./spacingDark"
import { timing } from "./timing"
import { typography } from "./typography"
// This supports "light" and "dark" themes by default. If undefined, it'll use the system theme
export type ImmutableThemeContextModeT = "light" | "dark"
export type ThemeContextModeT = ImmutableThemeContextModeT | undefined
// Because we have two themes, we need to define the types for each of them.
// colorsLight and colorsDark should have the same keys, but different values.
export type Colors = typeof colorsLight | typeof colorsDark
// The spacing type needs to take into account the different spacing values for light and dark themes.
export type Spacing = typeof spacingLight | typeof spacingDark
// These two are consistent across themes.
export type Timing = typeof timing
export type Typography = typeof typography
// The overall Theme object should contain all of the data you need to style your app.
export interface Theme {
colors: Colors
spacing: Spacing
typography: Typography
timing: Timing
isDark: boolean
}
/**
* Represents a function that returns a styled component based on the provided theme.
* @template T The type of the style.
* @param theme The theme object.
* @returns The styled component.
*
* @example
* const $container: ThemedStyle<ViewStyle> = (theme) => ({
* flex: 1,
* backgroundColor: theme.colors.background,
* justifyContent: "center",
* alignItems: "center",
* })
* // Then use in a component like so:
* const Component = () => {
* const { themed } = useAppTheme()
* return <View style={themed($container)} />
* }
*/
export type ThemedStyle<T> = (theme: Theme) => T
export type ThemedStyleArray<T> = (
| ThemedStyle<T>
| StyleProp<T>
| (StyleProp<T> | ThemedStyle<T>)[]
)[]
/**
*/
export type AllowedStylesT<T> = ThemedStyle<T> | StyleProp<T> | ThemedStyleArray<T>
/**
*/
export type ThemedFnT = <T>(styleOrStyleFn: AllowedStylesT<T>) => T

View File

@@ -0,0 +1,71 @@
// TODO: write documentation about fonts and typography along with guides on how to add custom fonts in own
// markdown file and add links from here
import { Platform } from "react-native"
import {
SpaceGrotesk_300Light as spaceGroteskLight,
SpaceGrotesk_400Regular as spaceGroteskRegular,
SpaceGrotesk_500Medium as spaceGroteskMedium,
SpaceGrotesk_600SemiBold as spaceGroteskSemiBold,
SpaceGrotesk_700Bold as spaceGroteskBold,
} from "@expo-google-fonts/space-grotesk"
export const customFontsToLoad = {
spaceGroteskLight,
spaceGroteskRegular,
spaceGroteskMedium,
spaceGroteskSemiBold,
spaceGroteskBold,
}
const fonts = {
spaceGrotesk: {
// Cross-platform Google font.
light: "spaceGroteskLight",
normal: "spaceGroteskRegular",
medium: "spaceGroteskMedium",
semiBold: "spaceGroteskSemiBold",
bold: "spaceGroteskBold",
},
helveticaNeue: {
// iOS only font.
thin: "HelveticaNeue-Thin",
light: "HelveticaNeue-Light",
normal: "Helvetica Neue",
medium: "HelveticaNeue-Medium",
},
courier: {
// iOS only font.
normal: "Courier",
},
sansSerif: {
// Android only font.
thin: "sans-serif-thin",
light: "sans-serif-light",
normal: "sans-serif",
medium: "sans-serif-medium",
},
monospace: {
// Android only font.
normal: "monospace",
},
}
export const typography = {
/**
* The fonts are available to use, but prefer using the semantic name.
*/
fonts,
/**
* The primary font. Used in most places.
*/
primary: fonts.spaceGrotesk,
/**
* An alternate font used for perhaps titles and stuff.
*/
secondary: Platform.select({ ios: fonts.helveticaNeue, android: fonts.sansSerif }),
/**
* Lets get fancy with a monospace font!
*/
code: Platform.select({ ios: fonts.courier, android: fonts.monospace }),
}