template_0205
This commit is contained in:
85
RN_TEMPLATE/app/theme/colors.ts
Normal file
85
RN_TEMPLATE/app/theme/colors.ts
Normal 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
|
||||
50
RN_TEMPLATE/app/theme/colorsDark.ts
Normal file
50
RN_TEMPLATE/app/theme/colorsDark.ts
Normal 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
|
||||
145
RN_TEMPLATE/app/theme/context.tsx
Normal file
145
RN_TEMPLATE/app/theme/context.tsx
Normal 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
|
||||
}
|
||||
25
RN_TEMPLATE/app/theme/context.utils.ts
Normal file
25
RN_TEMPLATE/app/theme/context.utils.ts
Normal 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)
|
||||
}
|
||||
16
RN_TEMPLATE/app/theme/spacing.ts
Normal file
16
RN_TEMPLATE/app/theme/spacing.ts
Normal 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),
|
||||
}
|
||||
16
RN_TEMPLATE/app/theme/spacingDark.ts
Normal file
16
RN_TEMPLATE/app/theme/spacingDark.ts
Normal 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),
|
||||
}
|
||||
23
RN_TEMPLATE/app/theme/styles.ts
Normal file
23
RN_TEMPLATE/app/theme/styles.ts
Normal 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,
|
||||
}
|
||||
23
RN_TEMPLATE/app/theme/theme.ts
Normal file
23
RN_TEMPLATE/app/theme/theme.ts
Normal 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,
|
||||
}
|
||||
6
RN_TEMPLATE/app/theme/timing.ts
Normal file
6
RN_TEMPLATE/app/theme/timing.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const timing = {
|
||||
/**
|
||||
* The duration (ms) for quick animations.
|
||||
*/
|
||||
quick: 300,
|
||||
}
|
||||
64
RN_TEMPLATE/app/theme/types.ts
Normal file
64
RN_TEMPLATE/app/theme/types.ts
Normal 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
|
||||
71
RN_TEMPLATE/app/theme/typography.ts
Normal file
71
RN_TEMPLATE/app/theme/typography.ts
Normal 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 }),
|
||||
}
|
||||
Reference in New Issue
Block a user