From bd63fc51debb34a76f23bf90059762704b6034a5 Mon Sep 17 00:00:00 2001 From: Stephen Arg Date: Sun, 1 Feb 2026 19:15:49 +0100 Subject: [PATCH] Add color presets with light/dark/OLED per preset (#956) * These changes include adding an Unplayed filter and also fixing shuffle all to shuffle based on the filtered selection * Added genre filter and selection page * Forgot to hit save on this file * Fixed bug affecting genre filtering * Shuffle all query now includes the genres * Fixed trigger to triggerHaptic * Fixed trigger to triggerHaptic * Added optional color themes * Slight adjustment to some dark color presets --------- Co-authored-by: StephenArg Co-authored-by: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> --- App.tsx | 18 +- src/components/Global/components/icon.tsx | 18 +- .../Global/helpers/switch-with-label.tsx | 7 +- .../Player/components/blurred-background.tsx | 14 +- src/components/Player/mini-player.tsx | 13 +- src/components/Settings/component.tsx | 11 +- .../Settings/components/preferences-tab.tsx | 110 +++++- src/components/jellify.tsx | 15 +- src/components/theme.ts | 64 ++-- src/screens/Tabs/index.tsx | 1 + src/screens/Tabs/tab-bar.tsx | 46 ++- src/stores/settings/app.ts | 19 + tamagui.config.ts | 353 ++++++++++++++---- 13 files changed, 532 insertions(+), 157 deletions(-) diff --git a/App.tsx b/App.tsx index 728de832..ca0c4e66 100644 --- a/App.tsx +++ b/App.tsx @@ -17,14 +17,14 @@ import TrackPlayer, { import { CAPABILITIES } from './src/constants/player' import { SafeAreaProvider } from 'react-native-safe-area-context' import { NavigationContainer } from '@react-navigation/native' -import { JellifyDarkTheme, JellifyLightTheme, JellifyOLEDTheme } from './src/components/theme' +import { getJellifyNavTheme } from './src/components/theme' import { requestStoragePermission } from './src/utils/permisson-helpers' import ErrorBoundary from './src/components/ErrorBoundary' import OTAUpdateScreen from './src/components/OtaUpdates' import { usePerformanceMonitor } from './src/hooks/use-performance-monitor' import navigationRef from './navigation' import { BUFFERS, PROGRESS_UPDATE_EVENT_INTERVAL } from './src/configs/player.config' -import { useThemeSetting } from './src/stores/settings/app' +import { useColorPresetSetting, useThemeSetting } from './src/stores/settings/app' import { getApi } from './src/stores' import CarPlayNavigation from './src/components/CarPlay/Navigation' import { CarPlay } from 'react-native-carplay' @@ -124,23 +124,15 @@ export default function App(): React.JSX.Element { function Container({ playerIsReady }: { playerIsReady: boolean }): React.JSX.Element { const [theme] = useThemeSetting() + const [colorPreset] = useColorPresetSetting() const isDarkMode = useColorScheme() === 'dark' + const resolvedMode = theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme return ( diff --git a/src/components/Global/components/icon.tsx b/src/components/Global/components/icon.tsx index 938ee0b7..4acd71e5 100644 --- a/src/components/Global/components/icon.tsx +++ b/src/components/Global/components/icon.tsx @@ -64,6 +64,16 @@ export default function Icon({ const pressStyle = animation ? { opacity: 0.6 } : undefined + // Tamagui theme keys are unprefixed (e.g. "primary" not "$primary"); resolve for token strings + const themeColorKey = + color && typeof color === 'string' && color.startsWith('$') ? color.slice(1) : color + const resolvedColor = + color && !disabled + ? (theme[themeColorKey as keyof typeof theme]?.val ?? theme.color.val) + : disabled + ? theme.neutral.val + : theme.color.val + return ( >() @@ -90,7 +91,7 @@ export default function Miniplayer(): React.JSX.Element { pressStyle={pressStyle} animation={'quick'} onPress={openPlayer} - backgroundColor='$background' + backgroundColor={theme.background.val} > @@ -144,15 +145,19 @@ export default function Miniplayer(): React.JSX.Element { function MiniPlayerProgress(): React.JSX.Element { const progress = useProgress(UPDATE_INTERVAL) + const theme = useTheme() return ( - + ) } diff --git a/src/components/Settings/component.tsx b/src/components/Settings/component.tsx index 3770ab37..f03535c9 100644 --- a/src/components/Settings/component.tsx +++ b/src/components/Settings/component.tsx @@ -1,6 +1,8 @@ import React, { Suspense, lazy } from 'react' +import { useColorScheme } from 'react-native' import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs' -import { getToken, getTokenValue, useTheme, Spinner, YStack } from 'tamagui' +import { getTokenValue, useTheme, Spinner, YStack } from 'tamagui' +import { useColorPresetSetting, useThemeSetting } from '../../stores/settings/app' import SettingsTabBar from './tab-bar' // Lazy load tab components to improve initial render @@ -63,9 +65,16 @@ function LazyInfoTab() { export default function Settings(): React.JSX.Element { const theme = useTheme() + const [themeSetting] = useThemeSetting() + const [colorPreset] = useColorPresetSetting() + const isDarkMode = useColorScheme() === 'dark' + const resolvedMode = themeSetting === 'system' ? (isDarkMode ? 'dark' : 'light') : themeSetting + // Key forces navigator to remount when preset/mode changes so tab bar colors update + const themeKey = `${colorPreset}_${resolvedMode}` return ( void +}) { + return ( + + + + + {option.label} + + {isSelected && } + + + ) +} + function getThemeSubtitle(themeSetting: ThemeSetting): string { switch (themeSetting) { case 'light': @@ -126,10 +198,28 @@ function getThemeSubtitle(themeSetting: ThemeSetting): string { } } +function getColorPresetSubtitle(colorPreset: ColorPreset): string { + switch (colorPreset) { + case 'purple': + return 'Purple vibes' + case 'ocean': + return 'Oceanic vibes' + case 'forest': + return 'Foresty vibes' + case 'sunset': + return 'Sunset vibes' + case 'peanut': + return 'Sandbox vibes' + default: + return 'Default vibes' + } +} + export default function PreferencesTab(): React.JSX.Element { const [sendMetrics, setSendMetrics] = useSendMetricsSetting() const [reducedHaptics, setReducedHaptics] = useReducedHapticsSetting() const [themeSetting, setThemeSetting] = useThemeSetting() + const [colorPreset, setColorPreset] = useColorPresetSetting() const [hideRunTimes, setHideRunTimes] = useHideRunTimesSetting() const left = useSwipeSettingsStore((s) => s.left) @@ -138,7 +228,7 @@ export default function PreferencesTab(): React.JSX.Element { const toggleRight = useSwipeSettingsStore((s) => s.toggleRight) const themeSubtitle = getThemeSubtitle(themeSetting) - + const colorPresetSubtitle = getColorPresetSubtitle(colorPreset) return ( ), }, + { + title: 'Color Preset', + subTitle: colorPresetSubtitle && `${colorPresetSubtitle}`, + iconName: 'palette', + iconColor: '$primary', + children: ( + + {COLOR_PRESET_OPTIONS.map((option) => ( + setColorPreset(option.value)} + /> + ))} + + ), + }, { title: 'Track Swipe Actions', subTitle: 'Choose actions for left/right swipes', diff --git a/src/components/jellify.tsx b/src/components/jellify.tsx index cfb36152..45065787 100644 --- a/src/components/jellify.tsx +++ b/src/components/jellify.tsx @@ -10,13 +10,17 @@ import { } from '@typedigital/telemetrydeck-react' import telemetryDeckConfig from '../../telemetrydeck.json' import * as Sentry from '@sentry/react-native' -import { getToken, Theme, useTheme } from 'tamagui' +import { getToken, Theme, ThemeName, useTheme } from 'tamagui' import Toast from 'react-native-toast-message' import JellifyToastConfig from '../configs/toast.config' import { useColorScheme } from 'react-native' import { StorageProvider } from '../providers/Storage' import { useSelectPlayerEngine } from '../stores/player/engine' -import { useSendMetricsSetting, useThemeSetting } from '../stores/settings/app' +import { + useColorPresetSetting, + useSendMetricsSetting, + useThemeSetting, +} from '../stores/settings/app' import { GLITCHTIP_DSN } from '../configs/config' import useDownloadProcessor from '../hooks/use-download-processor' /** @@ -25,12 +29,17 @@ import useDownloadProcessor from '../hooks/use-download-processor' */ export default function Jellify(): React.JSX.Element { const [theme] = useThemeSetting() + const [colorPreset] = useColorPresetSetting() const isDarkMode = useColorScheme() === 'dark' + + const resolvedMode = theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme + const themeName = `${colorPreset}_${resolvedMode}` // e.g. 'purple_dark' + useSelectPlayerEngine() return ( - + diff --git a/src/components/theme.ts b/src/components/theme.ts index 271b3be2..96d17e55 100644 --- a/src/components/theme.ts +++ b/src/components/theme.ts @@ -1,5 +1,7 @@ import { DarkTheme, DefaultTheme } from '@react-navigation/native' -import { getToken, getTokens } from 'tamagui' +import type { Theme } from '@react-navigation/native' +import { PRESET_PALETTES } from '../../tamagui.config' +import type { ColorPreset } from '../stores/settings/app' interface Fonts { regular: FontStyle @@ -43,38 +45,36 @@ const JellifyFonts: Fonts = { }, } -export const JellifyDarkTheme: ReactNavigation.Theme = { - dark: true, - colors: { - ...DarkTheme.colors, - card: getTokens().color.$darkBackground.val, - border: getTokens().color.$neutral.val, - background: getTokens().color.$darkBackground.val, - primary: getTokens().color.$primaryDark.val, - }, - fonts: JellifyFonts, +function paletteToNavTheme( + palette: (typeof PRESET_PALETTES)['purple']['dark'], + dark: boolean, +): Theme { + const base = dark ? DarkTheme : DefaultTheme + return { + ...base, + dark, + colors: { + ...base.colors, + background: palette.background, + card: palette.background, + border: palette.borderColor, + primary: palette.primary, + }, + fonts: JellifyFonts, + } } -export const JellifyLightTheme = { - ...DefaultTheme, - colors: { - ...DefaultTheme.colors, - primary: getTokens().color.$primaryLight.val, - border: getTokens().color.$neutral.val, - background: getTokens().color.$white.val, - card: getTokens().color.$white.val, - }, - fonts: JellifyFonts, +/** React Navigation theme for a given color preset and mode (purple_dark, ocean_light, etc.) */ +export function getJellifyNavTheme(preset: ColorPreset, mode: 'light' | 'dark' | 'oled'): Theme { + const palette = PRESET_PALETTES[preset][mode] + return paletteToNavTheme(palette, mode !== 'light') } -export const JellifyOLEDTheme: ReactNavigation.Theme = { - dark: true, - colors: { - ...DarkTheme.colors, - card: getTokens().color.$black.val, - border: getTokens().color.$neutral.val, - background: getTokens().color.$black.val, - primary: getTokens().color.$primaryDark.val, - }, - fonts: JellifyFonts, -} +/** Purple dark — matches Tamagui purple_dark (current JellifyDarkTheme) */ +export const JellifyDarkTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.dark, true) + +/** Purple light — matches Tamagui purple_light (current JellifyLightTheme) */ +export const JellifyLightTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.light, false) + +/** Purple oled — matches Tamagui purple_oled (current JellifyOLEDTheme) */ +export const JellifyOLEDTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.oled, true) diff --git a/src/screens/Tabs/index.tsx b/src/screens/Tabs/index.tsx index 1e3bf08a..bfd0fe59 100644 --- a/src/screens/Tabs/index.tsx +++ b/src/screens/Tabs/index.tsx @@ -28,6 +28,7 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element animation: 'shift', tabBarActiveTintColor: theme.primary.val, tabBarInactiveTintColor: theme.borderColor.val, + tabBarStyle: { backgroundColor: theme.background.val }, lazy: true, }} tabBar={(props) => } diff --git a/src/screens/Tabs/tab-bar.tsx b/src/screens/Tabs/tab-bar.tsx index 45d06d1a..d79130a3 100644 --- a/src/screens/Tabs/tab-bar.tsx +++ b/src/screens/Tabs/tab-bar.tsx @@ -1,17 +1,57 @@ +import React, { useMemo } from 'react' import Miniplayer from '../../components/Player/mini-player' import InternetConnectionWatcher from '../../components/Network/internetConnectionWatcher' import { BottomTabBar, BottomTabBarProps } from '@react-navigation/bottom-tabs' import useIsMiniPlayerActive from '../../hooks/use-mini-player' +import { useTheme } from 'tamagui' -export default function TabBar({ ...props }: BottomTabBarProps): React.JSX.Element { +/** + * Merge theme-driven tab bar options into the focused route's descriptor + * so the bar updates immediately when color preset changes (the navigator + * often does not re-pass updated screenOptions to the tab bar). + */ +export default function TabBar(props: BottomTabBarProps): React.JSX.Element { const isMiniPlayerActive = useIsMiniPlayerActive() + const theme = useTheme() + + const descriptorsWithTheme = useMemo(() => { + const focusedRoute = props.state.routes[props.state.index] + const focusedDescriptor = props.descriptors[focusedRoute.key] + if (!focusedDescriptor) return props.descriptors + return { + ...props.descriptors, + [focusedRoute.key]: { + ...focusedDescriptor, + options: { + ...focusedDescriptor.options, + tabBarStyle: { + ...focusedDescriptor.options.tabBarStyle, + backgroundColor: theme.background.val, + }, + tabBarActiveTintColor: theme.primary.val, + tabBarInactiveTintColor: theme.borderColor.val, + }, + }, + } + }, [ + props.descriptors, + props.state.routes, + props.state.index, + theme.background.val, + theme.primary.val, + theme.borderColor.val, + ]) + + // Key forces mini-player to remount when theme changes so colors update + // (avoids stale styles from Reanimated/Progress when preset changes without interaction) + const themeKey = `${theme.background.val}-${theme.primary.val}` return ( <> - {isMiniPlayerActive && } + {isMiniPlayerActive && } - + ) } diff --git a/src/stores/settings/app.ts b/src/stores/settings/app.ts index c125a08c..c556d9a6 100644 --- a/src/stores/settings/app.ts +++ b/src/stores/settings/app.ts @@ -4,6 +4,7 @@ import { createJSONStorage, devtools, persist } from 'zustand/middleware' import { useShallow } from 'zustand/react/shallow' export type ThemeSetting = 'system' | 'light' | 'dark' | 'oled' +export type ColorPreset = 'purple' | 'ocean' | 'forest' | 'sunset' | 'peanut' type AppSettingsStore = { sendMetrics: boolean @@ -17,6 +18,9 @@ type AppSettingsStore = { theme: ThemeSetting setTheme: (theme: ThemeSetting) => void + + colorPreset: ColorPreset + setColorPreset: (colorPreset: ColorPreset) => void } export const useAppSettingsStore = create()( @@ -34,6 +38,9 @@ export const useAppSettingsStore = create()( theme: 'system', setTheme: (theme: ThemeSetting) => set({ theme }), + + colorPreset: 'purple', + setColorPreset: (colorPreset: ColorPreset) => set({ colorPreset }), }), { name: 'app-settings-storage', @@ -50,6 +57,18 @@ export const useThemeSetting: () => [ThemeSetting, (theme: ThemeSetting) => void return [theme, setTheme] } + +export const useColorPresetSetting: () => [ + ColorPreset, + (colorPreset: ColorPreset) => void, +] = () => { + const colorPreset = useAppSettingsStore((state) => state.colorPreset) + + const setColorPreset = useAppSettingsStore((state) => state.setColorPreset) + + return [colorPreset, setColorPreset] +} + export const useReducedHapticsSetting: () => [boolean, (reducedHaptics: boolean) => void] = () => { const reducedHaptics = useAppSettingsStore((state) => state.reducedHaptics) diff --git a/tamagui.config.ts b/tamagui.config.ts index 36354d65..a039d10e 100644 --- a/tamagui.config.ts +++ b/tamagui.config.ts @@ -51,6 +51,275 @@ const tokens = createTokens({ }, }) +/** Theme mode palette: semantic keys used by Tamagui and React Navigation */ +type PresetModePalette = { + background: string + background75: string + background50: string + background25: string + borderColor: string + color: string + success: string + secondary: string + primary: string + danger: string + warning: string + neutral: string + translucent: string +} + +/** Palettes per preset (purple = current Jellify themes), for Tamagui + nav */ +export const PRESET_PALETTES: Record< + 'purple' | 'ocean' | 'forest' | 'sunset' | 'peanut', + { light: PresetModePalette; dark: PresetModePalette; oled: PresetModePalette } +> = { + purple: { + // Matches current JellifyDarkTheme / JellifyLightTheme / JellifyOLEDTheme + dark: { + background: 'rgba(25, 24, 28, 1)', + background75: 'rgba(25, 24, 28, 0.75)', + background50: 'rgba(25, 24, 28, 0.5)', + background25: 'rgba(25, 24, 28, 0.25)', + borderColor: '#77748E', + color: '#ffffff', + success: 'rgba(87, 233, 201, 1)', + secondary: 'rgba(75, 125, 215, 1)', + primary: '#887BFF', + danger: '#FF066F', + warning: '#FF6625', + neutral: '#77748E', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + light: { + background: '#ffffff', + background75: 'rgba(235, 221, 255, 0.75)', + background50: 'rgba(235, 221, 255, 0.5)', + background25: 'rgba(235, 221, 255, 0.25)', + borderColor: '#77748E', + color: '#0C0622', + success: 'rgba(16, 175, 141, 1)', + secondary: 'rgba(0, 58, 159, 1)', + primary: '#4b0fd6ff', + danger: '#B30077', + warning: '#a93300ff', + neutral: '#77748E', + translucent: 'rgba(255, 255, 255, 0.75)', + }, + oled: { + background: '#000000', + background75: 'rgba(0, 0, 0, 0.75)', + background50: 'rgba(0, 0, 0, 0.5)', + background25: 'rgba(0, 0, 0, 0.25)', + borderColor: '#77748E', + color: '#ffffff', + success: 'rgba(87, 233, 201, 1)', + secondary: 'rgba(75, 125, 215, 1)', + primary: '#887BFF', + danger: '#FF066F', + warning: '#FF6625', + neutral: '#77748E', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + }, + ocean: { + dark: { + background: 'rgba(25, 24, 28, 1)', + background75: 'rgba(25, 24, 28, 0.75)', + background50: 'rgba(25, 24, 28, 0.5)', + background25: 'rgba(25, 24, 28, 0.25)', + borderColor: '#78909C', + color: '#ffffff', + success: '#4DD0E1', + secondary: '#81D4FA', + primary: '#4FC3F7', + danger: '#FF7043', + warning: '#FFB74D', + neutral: '#78909C', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + light: { + background: '#E1F5FE', + background75: 'rgba(225, 245, 254, 0.75)', + background50: 'rgba(225, 245, 254, 0.5)', + background25: 'rgba(225, 245, 254, 0.25)', + borderColor: '#546E7A', + color: '#01579B', + success: '#00838F', + secondary: '#0277BD', + primary: '#0288D1', + danger: '#D84315', + warning: '#EF6C00', + neutral: '#546E7A', + translucent: 'rgba(255, 255, 255, 0.75)', + }, + oled: { + background: '#000000', + background75: 'rgba(0, 0, 0, 0.75)', + background50: 'rgba(0, 0, 0, 0.5)', + background25: 'rgba(0, 0, 0, 0.25)', + borderColor: '#78909C', + color: '#ffffff', + success: '#4DD0E1', + secondary: '#81D4FA', + primary: '#4FC3F7', + danger: '#FF7043', + warning: '#FFB74D', + neutral: '#78909C', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + }, + forest: { + dark: { + background: 'rgb(35, 47, 35)', + background75: 'rgba(35, 47, 35, 0.75)', + background50: 'rgba(35, 47, 35, 0.5)', + background25: 'rgba(35, 47, 35, 0.25)', + borderColor: '#8D9E8C', + color: '#ffffff', + success: '#66BB6A', + secondary: '#9CCC65', + primary: 'rgb(56, 105, 56)', + danger: '#E57373', + warning: '#FFB74D', + neutral: '#8D9E8C', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + light: { + background: '#E8F5E9', + background75: 'rgba(232, 245, 233, 0.75)', + background50: 'rgba(232, 245, 233, 0.5)', + background25: 'rgba(232, 245, 233, 0.25)', + borderColor: '#558B2F', + color: '#1B5E20', + success: '#2E7D32', + secondary: '#43A047', + primary: 'rgb(14, 143, 21)', + danger: '#C62828', + warning: '#E65100', + neutral: '#558B2F', + translucent: 'rgba(255, 255, 255, 0.75)', + }, + oled: { + background: '#000000', + background75: 'rgba(0, 0, 0, 0.75)', + background50: 'rgba(0, 0, 0, 0.5)', + background25: 'rgba(0, 0, 0, 0.25)', + borderColor: '#8D9E8C', + color: '#ffffff', + success: '#66BB6A', + secondary: '#9CCC65', + primary: 'rgb(11, 128, 17)', + danger: '#E57373', + warning: '#FFB74D', + neutral: '#8D9E8C', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + }, + sunset: { + dark: { + background: 'rgb(52, 34, 28)', + background75: 'rgba(52, 34, 28, 0.75)', + background50: 'rgba(52, 34, 28, 0.5)', + background25: 'rgba(52, 34, 28, 0.25)', + borderColor: '#A1887F', + color: '#ffffff', + success: '#FFAB91', + secondary: '#FF8A65', + primary: '#FF7043', + danger: '#EF5350', + warning: '#FFCA28', + neutral: '#A1887F', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + light: { + background: '#FFF3E0', + background75: 'rgba(255, 243, 224, 0.75)', + background50: 'rgba(255, 243, 224, 0.5)', + background25: 'rgba(255, 243, 224, 0.25)', + borderColor: '#BF360C', + color: '#3E2723', + success: '#E64A19', + secondary: '#FF5722', + primary: '#FF5722', + danger: '#B71C1C', + warning: '#F57C00', + neutral: '#BF360C', + translucent: 'rgba(255, 255, 255, 0.75)', + }, + oled: { + background: '#000000', + background75: 'rgba(0, 0, 0, 0.75)', + background50: 'rgba(0, 0, 0, 0.5)', + background25: 'rgba(0, 0, 0, 0.25)', + borderColor: '#A1887F', + color: '#ffffff', + success: '#FFAB91', + secondary: '#FF8A65', + primary: '#FF7043', + danger: '#EF5350', + warning: '#FFCA28', + neutral: '#A1887F', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + }, + peanut: { + dark: { + background: 'rgba(62, 39, 22, 1)', + background75: 'rgba(62, 39, 22, 0.75)', + background50: 'rgba(62, 39, 22, 0.5)', + background25: 'rgba(62, 39, 22, 0.25)', + borderColor: '#BCAAA4', + color: '#ffffff', + success: '#D7CCC8', + secondary: '#A1887F', + primary: '#D7CCC8', + danger: '#8D6E63', + warning: '#FFAB91', + neutral: '#BCAAA4', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + light: { + background: '#EFEBE9', + background75: 'rgba(239, 235, 233, 0.75)', + background50: 'rgba(239, 235, 233, 0.5)', + background25: 'rgba(239, 235, 233, 0.25)', + borderColor: '#6D4C41', + color: '#3E2723', + success: '#5D4037', + secondary: '#795548', + primary: '#8D6E63', + danger: '#4E342E', + warning: '#BF360C', + neutral: '#6D4C41', + translucent: 'rgba(255, 255, 255, 0.75)', + }, + oled: { + background: '#000000', + background75: 'rgba(0, 0, 0, 0.75)', + background50: 'rgba(0, 0, 0, 0.5)', + background25: 'rgba(0, 0, 0, 0.25)', + borderColor: '#BCAAA4', + color: '#ffffff', + success: '#D7CCC8', + secondary: '#A1887F', + primary: '#D7CCC8', + danger: '#8D6E63', + warning: '#FFAB91', + neutral: '#BCAAA4', + translucent: 'rgba(0, 0, 0, 0.5)', + }, + }, +} + +const presetNames = ['purple', 'ocean', 'forest', 'sunset', 'peanut'] as const + +const themes: Record = {} +for (const preset of presetNames) { + for (const mode of ['light', 'dark', 'oled'] as const) { + themes[`${preset}_${mode}`] = PRESET_PALETTES[preset][mode] + } +} + const jellifyConfig = createTamagui({ animations, fonts: { @@ -60,89 +329,7 @@ const jellifyConfig = createTamagui({ media, shorthands, tokens, - themes: { - dark: { - background: tokens.color.darkBackground, - background75: tokens.color.darkBackground75, - background50: tokens.color.darkBackground50, - background25: tokens.color.darkBackground25, - borderColor: tokens.color.neutral, - color: tokens.color.white, - success: tokens.color.tealDark, - secondary: tokens.color.secondaryDark, - primary: tokens.color.primaryDark, - danger: tokens.color.dangerDark, - warning: tokens.color.warningDark, - neutral: tokens.color.neutral, - - translucent: tokens.color.darkTranslucent, - }, - oled: { - // True black OLED theme - background: tokens.color.black, - background75: tokens.color.black75, - background50: tokens.color.black50, - background25: tokens.color.black25, - borderColor: tokens.color.neutral, - color: tokens.color.white, - success: tokens.color.tealDark, - secondary: tokens.color.secondaryDark, - primary: tokens.color.primaryDark, - danger: tokens.color.dangerDark, - warning: tokens.color.warningDark, - neutral: tokens.color.neutral, - - translucent: tokens.color.darkTranslucent, - }, - dark_inverted_purple: { - color: tokens.color.purpleDark, - borderColor: tokens.color.amethyst, - background: tokens.color.amethyst, - background25: tokens.color.amethyst25, - background50: tokens.color.amethyst50, - background75: tokens.color.amethyst75, - success: tokens.color.tealDark, - secondary: tokens.color.secondaryDark, - primary: tokens.color.primaryDark, - danger: tokens.color.dangerDark, - warning: tokens.color.warningDark, - neutral: tokens.color.neutral, - - translucent: tokens.color.darkTranslucent, - }, - light: { - background: tokens.color.white, - background75: tokens.color.lightBackground75, - background50: tokens.color.lightBackground50, - background25: tokens.color.lightBackground25, - borderColor: tokens.color.neutral, - color: tokens.color.purpleDark, - success: tokens.color.tealLight, - secondary: tokens.color.secondaryLight, - primary: tokens.color.primaryLight, - danger: tokens.color.dangerLight, - warning: tokens.color.warningLight, - neutral: tokens.color.neutral, - - translucent: tokens.color.lightTranslucent, - }, - light_inverted_purple: { - color: tokens.color.purpleDark, - borderColor: tokens.color.neutral, - background: tokens.color.amethyst, - background25: tokens.color.amethyst25, - background50: tokens.color.amethyst50, - background75: tokens.color.amethyst75, - success: tokens.color.tealLight, - secondary: tokens.color.secondaryLight, - primary: tokens.color.primaryLight, - danger: tokens.color.dangerLight, - warning: tokens.color.warningLight, - neutral: tokens.color.neutral, - - translucent: tokens.color.lightTranslucent, - }, - }, + themes, }) export type JellifyConfig = typeof jellifyConfig