-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
231 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { createContext, ReactNode, useContext } from 'react' | ||
import { | ||
BACKGROUND_COLORS, | ||
BackgroundColors, | ||
defaultThemes, | ||
themeWithBrand, | ||
UiTheme, | ||
UiThemeSelectProvider, | ||
} from '@pubkey-ui/core' | ||
import { ThemeLink } from './app-routes' | ||
import { atomWithStorage } from 'jotai/utils' | ||
import { atom, useAtomValue, useSetAtom } from 'jotai/index' | ||
import { Button, MantineColor, Menu } from '@mantine/core' | ||
|
||
export interface AppTheme extends UiTheme { | ||
active?: boolean | ||
} | ||
|
||
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'] } }) }, | ||
] | ||
|
||
const initialThemes = appThemes | ||
const initialTheme = appThemes[0] | ||
|
||
const themeAtom = atomWithStorage<AppTheme>('pubkey-ui-app-theme', initialTheme, undefined, { getOnInit: true }) | ||
const themesAtom = atomWithStorage<AppTheme[]>('pubkey-ui-app-themes', initialThemes, undefined, { getOnInit: true }) | ||
|
||
const activeThemesAtom = atom<AppTheme[]>((get) => { | ||
const themes = get(themesAtom) | ||
const theme = get(themeAtom) | ||
return themes.map((item) => ({ | ||
...item, | ||
active: item.id === theme.id, | ||
})) | ||
}) | ||
|
||
const activeThemeAtom = atom<AppTheme>((get) => { | ||
const themes = get(activeThemesAtom) | ||
|
||
return themes.find((item) => item.active) || themes[0] | ||
}) | ||
|
||
export interface AppThemeProviderContext { | ||
theme: AppTheme | ||
themes: AppTheme[] | ||
addTheme: (color: MantineColor, dark?: BackgroundColors) => void | ||
setTheme: (theme: AppTheme) => void | ||
resetThemes: () => void | ||
} | ||
|
||
const Context = createContext<AppThemeProviderContext>({} as AppThemeProviderContext) | ||
|
||
export function AppThemeProvider({ children }: { children: ReactNode }) { | ||
const theme = useAtomValue(activeThemeAtom) | ||
const themes = useAtomValue(activeThemesAtom) | ||
const setTheme = useSetAtom(themeAtom) | ||
const setThemes = useSetAtom(themesAtom) | ||
|
||
const value: AppThemeProviderContext = { | ||
theme, | ||
themes, | ||
addTheme: (color: MantineColor, dark?: BackgroundColors) => { | ||
const id = `${color}-${dark ?? 'default'}` | ||
// Make sure we don't add a theme with the same id | ||
if (themes.find((item) => item.id === 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) | ||
}, | ||
setTheme, | ||
} | ||
|
||
return ( | ||
<Context.Provider value={value}> | ||
<UiThemeSelectProvider link={ThemeLink} theme={value.theme} themes={value.themes}> | ||
{children} | ||
</UiThemeSelectProvider> | ||
</Context.Provider> | ||
) | ||
} | ||
|
||
export function useAppTheme() { | ||
return useContext(Context) | ||
} | ||
|
||
export function AppThemeSelect() { | ||
const { themes, theme, setTheme } = useAppTheme() | ||
return ( | ||
<Menu shadow="md" width={200}> | ||
<Menu.Target> | ||
<Button>{theme.id}</Button> | ||
</Menu.Target> | ||
|
||
<Menu.Dropdown> | ||
{themes.map((item) => ( | ||
<Menu.Item key={item.id} onClick={() => setTheme(item)}> | ||
{item.id} | ||
</Menu.Item> | ||
))} | ||
</Menu.Dropdown> | ||
</Menu> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,24 @@ | ||
import { UiThemeProvider } from '@pubkey-ui/core' | ||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' | ||
import { AppLayout } from './app-layout' | ||
import { AppRoutes, ThemeLink } from './app-routes' | ||
import { AppRoutes } from './app-routes' | ||
import { ClusterProvider } from './features/cluster/cluster-data-access' | ||
import { SolanaProvider } from './features/solana/solana-provider' | ||
import { AppThemeProvider } from './app-theme.provider' | ||
|
||
const client = new QueryClient() | ||
|
||
export function App() { | ||
return ( | ||
<QueryClientProvider client={client}> | ||
<UiThemeProvider link={ThemeLink}> | ||
<AppThemeProvider> | ||
<ClusterProvider> | ||
<SolanaProvider> | ||
<AppLayout> | ||
<AppRoutes /> | ||
</AppLayout> | ||
</SolanaProvider> | ||
</ClusterProvider> | ||
</UiThemeProvider> | ||
</AppThemeProvider> | ||
</QueryClientProvider> | ||
) | ||
} |
23 changes: 23 additions & 0 deletions
23
apps/web/src/app/features/demo/demo-feature-theme-select.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Button, Group } from '@mantine/core' | ||
import { UiCard, UiDebugModal, UiStack, UiThemeSelect, useUiThemeSelect } from '@pubkey-ui/core' | ||
|
||
export function DemoFeatureThemeSelect() { | ||
const { themes, selected, selectTheme } = useUiThemeSelect() | ||
return ( | ||
<UiStack> | ||
<UiCard title="ThemeSelect"> | ||
<UiThemeSelect /> | ||
</UiCard> | ||
<UiCard title="ThemeSelect"> | ||
<Group> | ||
{themes.map((item) => ( | ||
<Button disabled={selected.id === item.id} key={item.id} onClick={() => selectTheme(item.id)}> | ||
{item.id} | ||
</Button> | ||
))} | ||
</Group> | ||
</UiCard> | ||
<UiDebugModal data={{ themes, selected }} /> | ||
</UiStack> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { | ||
backgroundColorIds, | ||
BackgroundColors, | ||
mantineColorIds, | ||
UiCard, | ||
UiContainer, | ||
UiDebug, | ||
UiInfo, | ||
UiStack, | ||
useUiThemeSelect, | ||
} from '@pubkey-ui/core' | ||
import { useAppTheme } from '../../app-theme.provider' | ||
import { Button, Group, MantineColor, Select } from '@mantine/core' | ||
import { useState } from 'react' | ||
export function ThemesFeature() { | ||
const { themes, addTheme, setTheme, theme } = useAppTheme() | ||
const { selected } = useUiThemeSelect() | ||
return ( | ||
<UiContainer> | ||
<UiStack> | ||
<UiCard title="Add App Theme"> | ||
<UiStack> | ||
<UiInfo variant="outline" message="These are some local themes that are stored in your browser." /> | ||
<ThemeForm add={addTheme} /> | ||
</UiStack> | ||
</UiCard> | ||
<UiCard title="ThemeSelect"> | ||
<Group> | ||
{themes.map((item) => ( | ||
<Button disabled={theme.id === item.id} key={item.id} onClick={() => setTheme(item)}> | ||
{item.id} | ||
</Button> | ||
))} | ||
</Group> | ||
</UiCard> | ||
<UiDebug data={{ selected: selected.id, theme: theme.id, themes }} open /> | ||
</UiStack> | ||
</UiContainer> | ||
) | ||
} | ||
|
||
export function ThemeForm({ add }: { add: (color: MantineColor, dark?: BackgroundColors) => void }) { | ||
const [color, setColor] = useState<MantineColor>('blue') | ||
const [dark, setDark] = useState<BackgroundColors | undefined>(undefined) | ||
|
||
return ( | ||
<Group align="end"> | ||
<Select | ||
label="Color" | ||
description="Select the primary color" | ||
required | ||
data={mantineColorIds.map((id) => ({ label: id, value: id }))} | ||
value={color} | ||
onChange={(value) => (value ? setColor(value as MantineColor) : undefined)} | ||
/> | ||
|
||
<Select | ||
label="Dark" | ||
description="Select the dark color" | ||
clearable | ||
data={backgroundColorIds.map((id) => ({ label: id, value: id }))} | ||
value={dark} | ||
onChange={(value) => (value ? setDark(value as BackgroundColors) : undefined)} | ||
/> | ||
<Button | ||
onClick={() => { | ||
add(color, dark) | ||
}} | ||
> | ||
Add | ||
</Button> | ||
</Group> | ||
) | ||
} |