From 2ed40b315860a8856b7036f2ed6234b51e6222fa Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Sat, 9 Mar 2024 07:33:14 +0000 Subject: [PATCH] feat: add app themes --- apps/web/src/app/app-routes.tsx | 2 +- apps/web/src/app/app-theme.provider.tsx | 80 ++++++++++++----- .../app/features/themes/themes-feature.tsx | 86 ++++++++++++++++--- 3 files changed, 133 insertions(+), 35 deletions(-) diff --git a/apps/web/src/app/app-routes.tsx b/apps/web/src/app/app-routes.tsx index 9f8729d..379a2ea 100644 --- a/apps/web/src/app/app-routes.tsx +++ b/apps/web/src/app/app-routes.tsx @@ -18,7 +18,7 @@ const routes: RouteObject[] = [ { path: '/dashboard', element: }, { path: '/demo/*', element: }, { path: '/dev', element: }, - { path: '/themes', element: }, + { path: '/themes/*', element: }, { path: '*', element: }, ] diff --git a/apps/web/src/app/app-theme.provider.tsx b/apps/web/src/app/app-theme.provider.tsx index b961b6d..e89133b 100644 --- a/apps/web/src/app/app-theme.provider.tsx +++ b/apps/web/src/app/app-theme.provider.tsx @@ -3,6 +3,7 @@ import { BACKGROUND_COLORS, BackgroundColors, defaultThemes, + mantineColorIds, themeWithBrand, UiTheme, UiThemeSelectProvider, @@ -10,24 +11,55 @@ import { import { ThemeLink } from './app-routes' import { atomWithStorage } from 'jotai/utils' import { atom, useAtomValue, useSetAtom } from 'jotai/index' -import { Button, MantineColor, Menu } from '@mantine/core' +import { Button, Divider, MantineColor, Menu } from '@mantine/core' +import { Link } from 'react-router-dom' export interface AppTheme extends UiTheme { active?: boolean } +function createAppTheme(color: MantineColor, dark?: BackgroundColors) { + const id = `${color}-${dark ?? 'default'}` + // Make sure `color` and `dark` are valid values + if (dark && !Object.keys(BACKGROUND_COLORS).includes(dark ?? 'default')) { + throw new Error(`Invalid value for dark: ${dark}`) + } + if (!mantineColorIds.includes(color)) { + console.log(`Invalid color: ${color}`) + throw new Error(`Invalid value for color: ${color}`) + } + + return { + id, + theme: themeWithBrand(color, { + components: { + Input: { + styles: { + root: { + // backgroundColor: 'transparent', + }, + }, + }, + }, + colors: { dark: dark ? BACKGROUND_COLORS[dark] : undefined }, + }), + } +} + const appThemes: AppTheme[] = [ ...defaultThemes, - { id: 'gray-pink', theme: themeWithBrand('pink', { colors: { dark: BACKGROUND_COLORS['gray'] } }) }, - { id: 'zinc-pink', theme: themeWithBrand('pink', { colors: { dark: BACKGROUND_COLORS['zinc'] } }) }, - { id: 'neutral-pink', theme: themeWithBrand('pink', { colors: { dark: BACKGROUND_COLORS['neutral'] } }) }, - { id: 'slate-pink', theme: themeWithBrand('pink', { colors: { dark: BACKGROUND_COLORS['slate'] } }) }, - { id: 'stone-pink', theme: themeWithBrand('pink', { colors: { dark: BACKGROUND_COLORS['stone'] } }) }, - { id: 'gray-blue', theme: themeWithBrand('blue', { colors: { dark: BACKGROUND_COLORS['gray'] } }) }, - { id: 'zinc-blue', theme: themeWithBrand('blue', { colors: { dark: BACKGROUND_COLORS['zinc'] } }) }, - { id: 'neutral-blue', theme: themeWithBrand('blue', { colors: { dark: BACKGROUND_COLORS['neutral'] } }) }, - { id: 'slate-blue', theme: themeWithBrand('blue', { colors: { dark: BACKGROUND_COLORS['slate'] } }) }, - { id: 'stone-blue', theme: themeWithBrand('blue', { colors: { dark: BACKGROUND_COLORS['stone'] } }) }, + createAppTheme('blue'), + createAppTheme('red'), + createAppTheme('pink'), + createAppTheme('grape'), + createAppTheme('violet'), + createAppTheme('indigo'), + createAppTheme('cyan'), + createAppTheme('green'), + createAppTheme('lime'), + createAppTheme('yellow'), + createAppTheme('orange'), + createAppTheme('teal'), ] const initialThemes = appThemes @@ -39,7 +71,7 @@ const themesAtom = atomWithStorage('pubkey-ui-app-themes', initialTh const activeThemesAtom = atom((get) => { const themes = get(themesAtom) const theme = get(themeAtom) - return themes.map((item) => ({ + return themes?.map((item) => ({ ...item, active: item.id === theme.id, })) @@ -48,7 +80,7 @@ const activeThemesAtom = atom((get) => { const activeThemeAtom = atom((get) => { const themes = get(activeThemesAtom) - return themes.find((item) => item.active) || themes[0] + return themes?.find((item) => item.active) || themes[0] }) export interface AppThemeProviderContext { @@ -56,7 +88,7 @@ export interface AppThemeProviderContext { themes: AppTheme[] addTheme: (color: MantineColor, dark?: BackgroundColors) => void setTheme: (theme: AppTheme) => void - resetThemes: () => void + resetThemes: () => Promise } const Context = createContext({} as AppThemeProviderContext) @@ -71,27 +103,25 @@ export function AppThemeProvider({ children }: { children: ReactNode }) { theme, themes, addTheme: (color: MantineColor, dark?: BackgroundColors) => { - const id = `${color}-${dark ?? 'default'}` + const theme = createAppTheme(color, dark) // Make sure we don't add a theme with the same id - if (themes.find((item) => item.id === id)) { + if (themes.find((item) => item.id === theme.id)) { return } - const theme: AppTheme = { - id, - theme: themeWithBrand(color, { colors: { dark: dark ? BACKGROUND_COLORS[dark] : undefined } }), - } + setThemes((prev) => [...prev, theme]) setTheme(theme) }, - resetThemes: () => { - setThemes(initialThemes) + resetThemes: async () => { + setTheme({ ...initialTheme }) + setThemes(() => [...initialThemes]) }, setTheme, } return ( - + {children} @@ -116,6 +146,10 @@ export function AppThemeSelect() { {item.id} ))} + + + App Themes + ) diff --git a/apps/web/src/app/features/themes/themes-feature.tsx b/apps/web/src/app/features/themes/themes-feature.tsx index 77591ce..15189f8 100644 --- a/apps/web/src/app/features/themes/themes-feature.tsx +++ b/apps/web/src/app/features/themes/themes-feature.tsx @@ -5,35 +5,65 @@ import { UiCard, UiContainer, UiDebug, + UiDebugModal, UiInfo, UiStack, useUiThemeSelect, } from '@pubkey-ui/core' import { useAppTheme } from '../../app-theme.provider' -import { Button, Group, MantineColor, Select } from '@mantine/core' +import { Button, Grid, Group, MantineColor, Select, Text } from '@mantine/core' import { useState } from 'react' +import { DemoFeatureTabRoutes } from '../demo/demo-feature-tab-routes' +import { DemoFeatureLoader } from '../demo/demo-feature-loader' +import { DemoFeatureAlerts } from '../demo/demo-feature-alerts' +import { DemoFeatureAnchor } from '../demo/demo-feature-anchor' +import { DemoFeatureBack } from '../demo/demo-feature-back' +import { DemoFeatureCopy } from '../demo/demo-feature-copy' +import { DemoFeatureForm } from '../demo/demo-feature-form' +import { DemoFeatureGridRoutes } from '../demo/demo-feature-grid-routes' +import { DemoFeatureHeader } from '../demo/demo-feature-header' +import { DemoFeatureMenu } from '../demo/demo-feature-menu' +import { DemoFeatureNotFound } from '../demo/demo-feature-not-found' +import { DemoFeaturePage } from '../demo/demo-feature-page' +import { DemoFeatureSearchInput } from '../demo/demo-feature-search-input' export function ThemesFeature() { - const { themes, addTheme, setTheme, theme } = useAppTheme() + const { themes, addTheme, resetThemes, setTheme, theme } = useAppTheme() const { selected } = useUiThemeSelect() return ( + + These are some local themes that are stored in your browser. + + + + + + } + /> - - {themes.map((item) => ( - - ))} + {themes + .sort((a, b) => a.id.localeCompare(b.id)) + .map((item) => ( + + ))} - + + ) @@ -49,7 +79,7 @@ export function ThemeForm({ add }: { add: (color: MantineColor, dark?: Backgroun label="Color" description="Select the primary color" required - data={mantineColorIds.map((id) => ({ label: id, value: id }))} + data={mantineColorIds.sort((a, b) => a.localeCompare(b)).map((id) => ({ label: id, value: id }))} value={color} onChange={(value) => (value ? setColor(value as MantineColor) : undefined)} /> @@ -58,7 +88,7 @@ export function ThemeForm({ add }: { add: (color: MantineColor, dark?: Backgroun label="Dark" description="Select the dark color" clearable - data={backgroundColorIds.map((id) => ({ label: id, value: id }))} + data={backgroundColorIds.sort((a, b) => a.localeCompare(b)).map((id) => ({ label: id, value: id }))} value={dark} onChange={(value) => (value ? setDark(value as BackgroundColors) : undefined)} /> @@ -72,3 +102,37 @@ export function ThemeForm({ add }: { add: (color: MantineColor, dark?: Backgroun ) } + +export function AppThemeUiDemo() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +}