test refactor
This commit is contained in:
Generated
+10
@@ -19,6 +19,7 @@
|
|||||||
"axios": "^1.15.2",
|
"axios": "^1.15.2",
|
||||||
"effector": "^23.4.4",
|
"effector": "^23.4.4",
|
||||||
"effector-react": "^23.3.0",
|
"effector-react": "^23.3.0",
|
||||||
|
"lucide-react": "^1.14.0",
|
||||||
"maplibre-gl": "^5.24.0",
|
"maplibre-gl": "^5.24.0",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
@@ -7415,6 +7416,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"axios": "^1.15.2",
|
"axios": "^1.15.2",
|
||||||
"effector": "^23.4.4",
|
"effector": "^23.4.4",
|
||||||
"effector-react": "^23.3.0",
|
"effector-react": "^23.3.0",
|
||||||
|
"lucide-react": "^1.14.0",
|
||||||
"maplibre-gl": "^5.24.0",
|
"maplibre-gl": "^5.24.0",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined'
|
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'
|
||||||
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'
|
import MenuRoundedIcon from '@mui/icons-material/MenuRounded'
|
||||||
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'
|
|
||||||
import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined'
|
|
||||||
import AppBar from '@mui/material/AppBar'
|
import AppBar from '@mui/material/AppBar'
|
||||||
import Badge from '@mui/material/Badge'
|
import Badge from '@mui/material/Badge'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import FormControl from '@mui/material/FormControl'
|
|
||||||
import IconButton from '@mui/material/IconButton'
|
import IconButton from '@mui/material/IconButton'
|
||||||
import InputLabel from '@mui/material/InputLabel'
|
import { alpha, useTheme } from '@mui/material/styles'
|
||||||
import MenuItem from '@mui/material/MenuItem'
|
|
||||||
import Select from '@mui/material/Select'
|
|
||||||
import type { SelectChangeEvent } from '@mui/material/Select'
|
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
import Toolbar from '@mui/material/Toolbar'
|
import Toolbar from '@mui/material/Toolbar'
|
||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
@@ -21,7 +14,6 @@ import useMediaQuery from '@mui/material/useMediaQuery'
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useUnit } from 'effector-react'
|
import { useUnit } from 'effector-react'
|
||||||
import { Link as RouterLink, useNavigate } from 'react-router-dom'
|
import { Link as RouterLink, useNavigate } from 'react-router-dom'
|
||||||
import type { ColorScheme } from '@/app/providers/theme-controller'
|
|
||||||
import { useThemeController } from '@/app/providers/theme-controller'
|
import { useThemeController } from '@/app/providers/theme-controller'
|
||||||
import { fetchMyCart } from '@/entities/cart/api/cart-api'
|
import { fetchMyCart } from '@/entities/cart/api/cart-api'
|
||||||
import { fetchMyOrders } from '@/entities/order/api/order-api'
|
import { fetchMyOrders } from '@/entities/order/api/order-api'
|
||||||
@@ -29,143 +21,24 @@ import { CartBadge } from '@/features/cart/cart-badge'
|
|||||||
import { UserMenu } from '@/features/user/user-menu'
|
import { UserMenu } from '@/features/user/user-menu'
|
||||||
import { STORE_NAME } from '@/shared/config'
|
import { STORE_NAME } from '@/shared/config'
|
||||||
import { $user, logout, tokenSet } from '@/shared/model/auth'
|
import { $user, logout, tokenSet } from '@/shared/model/auth'
|
||||||
|
import type { ColorScheme } from '@/shared/model/theme'
|
||||||
import { BearLogo } from '@/shared/ui/BearLogo'
|
import { BearLogo } from '@/shared/ui/BearLogo'
|
||||||
|
import { ModeSwitcher } from '@/shared/ui/ModeSwitcher'
|
||||||
|
import { SchemeSwitcher } from '@/shared/ui/SchemeSwitcher'
|
||||||
import { NavigationDrawer } from '@/widgets/navigation-drawer'
|
import { NavigationDrawer } from '@/widgets/navigation-drawer'
|
||||||
|
|
||||||
type NavItem = { label: string; to: string }
|
type NavItem = { label: string; to: string }
|
||||||
|
|
||||||
const navItems: NavItem[] = [{ label: 'Каталог', to: '/' }]
|
const navItems: NavItem[] = [{ label: 'Каталог', to: '/' }]
|
||||||
|
|
||||||
function ThemeControlsDesktop(props: {
|
|
||||||
scheme: string
|
|
||||||
mode: string
|
|
||||||
resolvedMode: 'light' | 'dark'
|
|
||||||
onSchemeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onModeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onCycleMode: () => void
|
|
||||||
}) {
|
|
||||||
const { scheme, mode, resolvedMode, onSchemeChange, onModeChange, onCycleMode } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FormControl
|
|
||||||
size="small"
|
|
||||||
sx={{
|
|
||||||
ml: 2,
|
|
||||||
minWidth: 160,
|
|
||||||
'& .MuiInputLabel-root': { color: 'rgba(255,255,255,0.85)' },
|
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#fff' },
|
|
||||||
'& .MuiInputLabel-root.MuiInputLabel-shrink': { color: 'rgba(255,255,255,0.92)' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InputLabel id="scheme-label">Схема</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="scheme-label"
|
|
||||||
value={scheme}
|
|
||||||
label="Схема"
|
|
||||||
onChange={onSchemeChange}
|
|
||||||
sx={{
|
|
||||||
color: 'inherit',
|
|
||||||
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.5)' },
|
|
||||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.9)' },
|
|
||||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.95)' },
|
|
||||||
'.MuiSvgIcon-root': { color: 'inherit' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem value="craft">Крафт</MenuItem>
|
|
||||||
<MenuItem value="forest">Лес</MenuItem>
|
|
||||||
<MenuItem value="ocean">Океан</MenuItem>
|
|
||||||
<MenuItem value="berry">Ягоды</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl
|
|
||||||
size="small"
|
|
||||||
sx={{
|
|
||||||
ml: 2,
|
|
||||||
minWidth: 150,
|
|
||||||
'& .MuiInputLabel-root': { color: 'rgba(255,255,255,0.85)' },
|
|
||||||
'& .MuiInputLabel-root.Mui-focused': { color: '#fff' },
|
|
||||||
'& .MuiInputLabel-root.MuiInputLabel-shrink': { color: 'rgba(255,255,255,0.92)' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InputLabel id="mode-label">Тема</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="mode-label"
|
|
||||||
value={mode}
|
|
||||||
label="Тема"
|
|
||||||
onChange={onModeChange}
|
|
||||||
sx={{
|
|
||||||
color: 'inherit',
|
|
||||||
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.5)' },
|
|
||||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.9)' },
|
|
||||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.95)' },
|
|
||||||
'.MuiSvgIcon-root': { color: 'inherit' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem value="system">Авто (система)</MenuItem>
|
|
||||||
<MenuItem value="light">Светлая</MenuItem>
|
|
||||||
<MenuItem value="dark">Тёмная</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
onClick={onCycleMode}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
aria-label="Переключить режим темы (авто/светлая/тёмная)"
|
|
||||||
title={`Сейчас: ${mode === 'system' ? `авто (${resolvedMode})` : mode}`}
|
|
||||||
>
|
|
||||||
{resolvedMode === 'dark' ? <LightModeOutlinedIcon /> : <DarkModeOutlinedIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ThemeControlsMobile(props: {
|
|
||||||
scheme: string
|
|
||||||
mode: string
|
|
||||||
resolvedMode: 'light' | 'dark'
|
|
||||||
onSchemeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onModeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onCycleMode: () => void
|
|
||||||
}) {
|
|
||||||
const { scheme, mode, resolvedMode, onSchemeChange, onModeChange, onCycleMode } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
||||||
<FormControl size="small" fullWidth>
|
|
||||||
<InputLabel id="scheme-label-mobile">Схема</InputLabel>
|
|
||||||
<Select labelId="scheme-label-mobile" value={scheme} label="Схема" onChange={onSchemeChange}>
|
|
||||||
<MenuItem value="craft">Крафт</MenuItem>
|
|
||||||
<MenuItem value="forest">Лес</MenuItem>
|
|
||||||
<MenuItem value="ocean">Океан</MenuItem>
|
|
||||||
<MenuItem value="berry">Ягоды</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl size="small" fullWidth>
|
|
||||||
<InputLabel id="mode-label-mobile">Тема</InputLabel>
|
|
||||||
<Select labelId="mode-label-mobile" value={mode} label="Тема" onChange={onModeChange}>
|
|
||||||
<MenuItem value="system">Авто (система)</MenuItem>
|
|
||||||
<MenuItem value="light">Светлая</MenuItem>
|
|
||||||
<MenuItem value="dark">Тёмная</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Button variant="outlined" onClick={onCycleMode}>
|
|
||||||
Быстро переключить: {mode === 'system' ? `авто (${resolvedMode})` : mode}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppHeader() {
|
export function AppHeader() {
|
||||||
const { mode, resolvedMode, scheme, setMode, setScheme, cycleMode } = useThemeController()
|
const { mode, resolvedMode, scheme, setScheme, cycleMode } = useThemeController()
|
||||||
const user = useUnit($user)
|
const user = useUnit($user)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const isAdmin = Boolean(user?.isAdmin)
|
const isAdmin = Boolean(user?.isAdmin)
|
||||||
const headerNavItems = isAdmin ? [...navItems, { label: 'Админка', to: '/admin' }] : navItems
|
const headerNavItems = isAdmin ? [...navItems, { label: 'Админка', to: '/admin' }] : navItems
|
||||||
|
const theme = useTheme()
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
|
||||||
|
|
||||||
const cartQuery = useQuery({
|
const cartQuery = useQuery({
|
||||||
queryKey: ['me', 'cart'],
|
queryKey: ['me', 'cart'],
|
||||||
@@ -184,14 +57,14 @@ export function AppHeader() {
|
|||||||
).length
|
).length
|
||||||
|
|
||||||
const [mobileOpen, setMobileOpen] = useState(false)
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
const theme = useTheme()
|
const [scrolled, setScrolled] = useState(false)
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
|
|
||||||
|
|
||||||
const onSchemeChange = (e: SelectChangeEvent<string>) => setScheme(e.target.value as ColorScheme)
|
useEffect(() => {
|
||||||
const onModeChange = (e: SelectChangeEvent<string>) => {
|
const handler = () => setScrolled(window.scrollY > 0)
|
||||||
const v = e.target.value
|
handler()
|
||||||
if (v === 'system' || v === 'light' || v === 'dark') setMode(v)
|
window.addEventListener('scroll', handler, { passive: true })
|
||||||
}
|
return () => window.removeEventListener('scroll', handler)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const go = (to: string) => {
|
const go = (to: string) => {
|
||||||
setMobileOpen(false)
|
setMobileOpen(false)
|
||||||
@@ -205,11 +78,20 @@ export function AppHeader() {
|
|||||||
navigate('/')
|
navigate('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeControls = { scheme, mode, resolvedMode, onSchemeChange, onModeChange, onCycleMode: cycleMode }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppBar position="sticky" color="primary" elevation={0} sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<AppBar
|
||||||
|
position="sticky"
|
||||||
|
color="primary"
|
||||||
|
elevation={scrolled ? 2 : 0}
|
||||||
|
sx={{
|
||||||
|
borderBottom: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
bgcolor: alpha(theme.palette.primary.main, 0.88),
|
||||||
|
backdropFilter: 'blur(8px)',
|
||||||
|
transition: 'box-shadow 0.2s ease, background-color 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -219,7 +101,7 @@ export function AppHeader() {
|
|||||||
edge="start"
|
edge="start"
|
||||||
sx={{ mr: 1 }}
|
sx={{ mr: 1 }}
|
||||||
>
|
>
|
||||||
<MenuOutlinedIcon />
|
<MenuRoundedIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -255,7 +137,7 @@ export function AppHeader() {
|
|||||||
<Tooltip title="Заказы">
|
<Tooltip title="Заказы">
|
||||||
<IconButton color="inherit" sx={{ ml: 1 }} onClick={() => navigate('/me/orders')} aria-label="Заказы">
|
<IconButton color="inherit" sx={{ ml: 1 }} onClick={() => navigate('/me/orders')} aria-label="Заказы">
|
||||||
<Badge color="secondary" badgeContent={activeOrdersCount} invisible={activeOrdersCount === 0}>
|
<Badge color="secondary" badgeContent={activeOrdersCount} invisible={activeOrdersCount === 0}>
|
||||||
<LocalShippingOutlinedIcon />
|
<Inventory2OutlinedIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -273,7 +155,12 @@ export function AppHeader() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isMobile && <ThemeControlsDesktop {...themeControls} />}
|
{!isMobile && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1.5 }}>
|
||||||
|
<SchemeSwitcher value={scheme} onChange={(s: ColorScheme) => setScheme(s)} />
|
||||||
|
<ModeSwitcher mode={mode} resolvedMode={resolvedMode} onCycleMode={cycleMode} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
@@ -283,10 +170,13 @@ export function AppHeader() {
|
|||||||
user={user}
|
user={user}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
navItems={headerNavItems}
|
navItems={headerNavItems}
|
||||||
themeControls={themeControls}
|
scheme={scheme}
|
||||||
|
mode={mode}
|
||||||
|
resolvedMode={resolvedMode}
|
||||||
|
onSchemeChange={(s: ColorScheme) => setScheme(s)}
|
||||||
|
onCycleMode={cycleMode}
|
||||||
onNavigate={go}
|
onNavigate={go}
|
||||||
onLogout={onLogout}
|
onLogout={onLogout}
|
||||||
ThemeControlsMobile={ThemeControlsMobile}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { createContext, type PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'
|
import { createContext, type PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import type { PaletteMode } from '@mui/material'
|
import type { PaletteMode } from '@mui/material'
|
||||||
|
import type { ColorScheme, ThemeModePreference } from '@/shared/model/theme'
|
||||||
export type ColorScheme = 'craft' | 'forest' | 'ocean' | 'berry'
|
|
||||||
|
|
||||||
export type ThemeModePreference = 'system' | PaletteMode
|
|
||||||
|
|
||||||
export type ThemeSettings = {
|
export type ThemeSettings = {
|
||||||
mode: ThemeModePreference
|
mode: ThemeModePreference
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ShoppingCartOutlinedIcon from '@mui/icons-material/ShoppingCartOutlined'
|
import ShoppingCartRoundedIcon from '@mui/icons-material/ShoppingCartRounded'
|
||||||
import Badge from '@mui/material/Badge'
|
import Badge from '@mui/material/Badge'
|
||||||
import IconButton from '@mui/material/IconButton'
|
import IconButton from '@mui/material/IconButton'
|
||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
@@ -23,7 +23,7 @@ export function CartBadge({ user, cartCount, onNavigate }: Props) {
|
|||||||
aria-label="Корзина"
|
aria-label="Корзина"
|
||||||
>
|
>
|
||||||
<Badge color="secondary" badgeContent={user ? cartCount : 0} invisible={!user || cartCount === 0}>
|
<Badge color="secondary" badgeContent={user ? cartCount : 0} invisible={!user || cartCount === 0}>
|
||||||
<ShoppingCartOutlinedIcon />
|
<ShoppingCartRoundedIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'
|
import PersonOutlineRoundedIcon from '@mui/icons-material/PersonOutlineRounded'
|
||||||
import Badge from '@mui/material/Badge'
|
import Badge from '@mui/material/Badge'
|
||||||
import IconButton from '@mui/material/IconButton'
|
import IconButton from '@mui/material/IconButton'
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
import ListItemText from '@mui/material/ListItemText'
|
||||||
@@ -40,7 +40,7 @@ export function UserMenu({ user, onNavigate, onLogout }: Props) {
|
|||||||
invisible={!user}
|
invisible={!user}
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
>
|
>
|
||||||
<AccountCircleOutlinedIcon />
|
<PersonOutlineRoundedIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { PaletteMode } from '@mui/material'
|
||||||
|
|
||||||
|
export type ColorScheme = 'craft' | 'forest' | 'ocean' | 'berry'
|
||||||
|
|
||||||
|
export type ThemeModePreference = 'system' | PaletteMode
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined'
|
||||||
|
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'
|
||||||
|
import IconButton from '@mui/material/IconButton'
|
||||||
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
import type { ThemeModePreference } from '@/shared/model/theme'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
mode: ThemeModePreference
|
||||||
|
resolvedMode: 'light' | 'dark'
|
||||||
|
onCycleMode: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModeLabel(mode: ThemeModePreference, resolvedMode: 'light' | 'dark'): string {
|
||||||
|
switch (mode) {
|
||||||
|
case 'system':
|
||||||
|
return `Авто (${resolvedMode === 'dark' ? 'тёмная' : 'светлая'})`
|
||||||
|
case 'light':
|
||||||
|
return 'Светлая'
|
||||||
|
case 'dark':
|
||||||
|
return 'Тёмная'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModeSwitcher({ mode, resolvedMode, onCycleMode }: Props) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={`Тема: ${getModeLabel(mode, resolvedMode)}`}>
|
||||||
|
<IconButton color="inherit" onClick={onCycleMode} aria-label="Переключить тему">
|
||||||
|
{resolvedMode === 'dark' ? <LightModeOutlinedIcon /> : <DarkModeOutlinedIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ModeSwitcher } from './ModeSwitcher'
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import IconButton from '@mui/material/IconButton'
|
||||||
|
import { Cherry, Hammer, Trees, WavesHorizontal } from 'lucide-react'
|
||||||
|
import type { ColorScheme } from '@/shared/model/theme'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: ColorScheme
|
||||||
|
onChange: (scheme: ColorScheme) => void
|
||||||
|
orientation?: 'horizontal' | 'vertical'
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCHEMES: { key: ColorScheme; color: string; label: string; icon: React.ReactNode }[] = [
|
||||||
|
{ key: 'craft', color: '#6D4C41', label: 'Крафт', icon: <Hammer size={14} /> },
|
||||||
|
{ key: 'forest', color: '#2E7D32', label: 'Лес', icon: <Trees size={14} /> },
|
||||||
|
{ key: 'ocean', color: '#1565C0', label: 'Океан', icon: <WavesHorizontal size={14} /> },
|
||||||
|
{ key: 'berry', color: '#7B1FA2', label: 'Ягоды', icon: <Cherry size={14} /> },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function SchemeSwitcher({ value, onChange, orientation = 'horizontal' }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: orientation === 'vertical' ? 'column' : 'row',
|
||||||
|
gap: 0.5,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{SCHEMES.map((s) => {
|
||||||
|
const active = value === s.key
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
key={s.key}
|
||||||
|
onClick={() => onChange(s.key)}
|
||||||
|
size="small"
|
||||||
|
title={s.label}
|
||||||
|
sx={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
minWidth: 30,
|
||||||
|
bgcolor: s.color,
|
||||||
|
border: 2,
|
||||||
|
borderColor: active ? 'common.white' : 'rgba(255,255,255,0.4)',
|
||||||
|
boxShadow: active ? `0 0 0 1.5px ${s.color}, 0 0 8px ${s.color}99` : 'none',
|
||||||
|
transform: active ? 'scale(1.1)' : 'scale(1)',
|
||||||
|
color: 'common.white',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.2)',
|
||||||
|
bgcolor: s.color,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{active ? <CheckCircleRoundedIcon sx={{ fontSize: 14 }} /> : s.icon}
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { SchemeSwitcher } from './SchemeSwitcher'
|
||||||
@@ -2,20 +2,13 @@ import Box from '@mui/material/Box'
|
|||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import Divider from '@mui/material/Divider'
|
import Divider from '@mui/material/Divider'
|
||||||
import Drawer from '@mui/material/Drawer'
|
import Drawer from '@mui/material/Drawer'
|
||||||
import type { SelectChangeEvent } from '@mui/material/Select'
|
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { STORE_NAME } from '@/shared/config'
|
import { STORE_NAME } from '@/shared/config'
|
||||||
import type { AuthUser } from '@/shared/model/auth'
|
import type { AuthUser } from '@/shared/model/auth'
|
||||||
|
import type { ColorScheme, ThemeModePreference } from '@/shared/model/theme'
|
||||||
import { BearLogo } from '@/shared/ui/BearLogo'
|
import { BearLogo } from '@/shared/ui/BearLogo'
|
||||||
|
import { ModeSwitcher } from '@/shared/ui/ModeSwitcher'
|
||||||
type ThemeControls = {
|
import { SchemeSwitcher } from '@/shared/ui/SchemeSwitcher'
|
||||||
scheme: string
|
|
||||||
mode: string
|
|
||||||
resolvedMode: 'light' | 'dark'
|
|
||||||
onSchemeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onModeChange: (e: SelectChangeEvent<string>) => void
|
|
||||||
onCycleMode: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -23,10 +16,13 @@ type Props = {
|
|||||||
user: AuthUser | null
|
user: AuthUser | null
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
navItems: { label: string; to: string }[]
|
navItems: { label: string; to: string }[]
|
||||||
themeControls: ThemeControls
|
scheme: ColorScheme
|
||||||
|
mode: ThemeModePreference
|
||||||
|
resolvedMode: 'light' | 'dark'
|
||||||
|
onSchemeChange: (scheme: ColorScheme) => void
|
||||||
|
onCycleMode: () => void
|
||||||
onNavigate: (to: string) => void
|
onNavigate: (to: string) => void
|
||||||
onLogout: () => void
|
onLogout: () => void
|
||||||
ThemeControlsMobile: React.ComponentType<ThemeControls>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NavigationDrawer({
|
export function NavigationDrawer({
|
||||||
@@ -35,10 +31,13 @@ export function NavigationDrawer({
|
|||||||
user,
|
user,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
navItems,
|
navItems,
|
||||||
themeControls,
|
scheme,
|
||||||
|
mode,
|
||||||
|
resolvedMode,
|
||||||
|
onSchemeChange,
|
||||||
|
onCycleMode,
|
||||||
onNavigate,
|
onNavigate,
|
||||||
onLogout,
|
onLogout,
|
||||||
ThemeControlsMobile,
|
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const go = (to: string) => {
|
const go = (to: string) => {
|
||||||
onClose()
|
onClose()
|
||||||
@@ -93,7 +92,10 @@ export function NavigationDrawer({
|
|||||||
|
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 2 }} />
|
||||||
|
|
||||||
<ThemeControlsMobile {...themeControls} />
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'flex-start' }}>
|
||||||
|
<SchemeSwitcher value={scheme} onChange={onSchemeChange} orientation="vertical" />
|
||||||
|
<ModeSwitcher mode={mode} resolvedMode={resolvedMode} onCycleMode={onCycleMode} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user