20 files changed
+252
-56
No files matched your search
+4
-1
@@ -8,6 +8,7 @@ import App from "next/app"
|
||||
import Head from "next/head"
|
||||
import { YMetrika } from "src/components/YMetrika"
|
||||
import { GAnalytics } from "src/components/GAnalytics"
|
||||
import { Theme } from "@/ui/theme/Theme"
|
||||
|
||||
export default class MyApp extends App {
|
||||
render() {
|
||||
@@ -41,7 +42,9 @@ export default class MyApp extends App {
|
||||
<YMetrika account={24027460} />
|
||||
<GAnalytics account={"UA-60209057-1"} />
|
||||
</Head>
|
||||
<Component {...pageProps} />
|
||||
<Theme>
|
||||
<Component {...pageProps} />
|
||||
</Theme>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ export default function Document() {
|
||||
{/* Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;900&display=swap"
|
||||
rel="preload"
|
||||
as="style"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
</Head>
|
||||
|
||||
@@ -7,9 +7,22 @@ import { Breadcrumbs } from "../Breadcrumbs"
|
||||
import { IBreadcumbsPart, IMenu } from "src/types"
|
||||
import { useRouter } from "next/router"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
import { Gosuslugi } from "../Gosuslugi"
|
||||
import { Navigation } from "@/components/Navigation"
|
||||
import { Box } from "@/ui/Box"
|
||||
import { Button } from "@/ui/Button"
|
||||
import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons"
|
||||
import { useTheme } from "@/ui/theme/useTheme"
|
||||
import { Copyright } from "../Copyright"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { Author } from "../Author"
|
||||
import { Title } from "@/ui/Title"
|
||||
import { Panel } from "@/ui/Panel"
|
||||
import { Contacts } from "../Contacts"
|
||||
import dynamic from "next/dynamic"
|
||||
|
||||
const Gosuslugi = dynamic(import("../Gosuslugi").then(module => module.Gosuslugi), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export type AppProps = {
|
||||
showAuthor?: boolean
|
||||
@@ -25,6 +38,7 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
|
||||
const blockStyle = {
|
||||
justifyContent: "center",
|
||||
}
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{
|
||||
@@ -50,9 +64,30 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
|
||||
</header>
|
||||
)}
|
||||
footer={(
|
||||
<Footer
|
||||
showAuthor={showAuthor}
|
||||
>
|
||||
<Footer>
|
||||
<Box gap={40} align={false} style={{
|
||||
marginBottom: "var(--size-l)",
|
||||
}}>
|
||||
<Panel ghost>
|
||||
<Contacts />
|
||||
</Panel>
|
||||
|
||||
<Panel ghost>
|
||||
<Title level={3}>
|
||||
Версия для слабовидящих
|
||||
</Title>
|
||||
<Button theme="icon" size="default" onClick={() => {
|
||||
setTheme(theme === "default" ? "contrast" : "default")
|
||||
}}>
|
||||
{theme === "default" ? (
|
||||
<EyeOpenIcon width={24} height={24} />
|
||||
) : (
|
||||
<EyeClosedIcon width={24} height={24} />
|
||||
)}
|
||||
</Button>
|
||||
</Panel>
|
||||
</Box>
|
||||
|
||||
<div style={{
|
||||
backgroundColor: "white",
|
||||
padding: 10,
|
||||
@@ -61,6 +96,18 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
|
||||
}}>
|
||||
<Gosuslugi />
|
||||
</div>
|
||||
|
||||
|
||||
<Box>
|
||||
<Copyright />
|
||||
|
||||
{!showAuthor ? null : (
|
||||
<>
|
||||
<Spacer as={"span"} />
|
||||
<Author />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Footer>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const Author: React.FC = () => (
|
||||
<span>
|
||||
Разработка и поддержка сайта —
|
||||
Разработка и поддержка — {" "}
|
||||
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
|
||||
www.tmshv.ru
|
||||
</a>
|
||||
@@ -5,6 +5,8 @@
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
transition: box-shadow 150ms;
|
||||
|
||||
border: var(--border);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
@@ -52,8 +54,8 @@
|
||||
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
width: 100%;
|
||||
height: var(--radius);
|
||||
top: calc(var(--radius) * -1);
|
||||
height: var(--size-m);
|
||||
top: calc(var(--size-m) * -1);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@@ -68,10 +70,9 @@
|
||||
position: absolute;
|
||||
|
||||
--offset: var(--size-m);
|
||||
--r: var(--size-s);
|
||||
left: var(--offset);
|
||||
right: var(--offset);
|
||||
bottom: var(--offset);
|
||||
|
||||
border-radius: var(--radius) var(--radius) var(--r) var(--r);
|
||||
border-radius: var(--radius-small);
|
||||
}
|
||||
@@ -3,13 +3,14 @@ import s from "./contacts.module.css"
|
||||
import { useContext } from "react"
|
||||
import { ConfigContext } from "src/context/ConfigContext"
|
||||
import Link from "next/link"
|
||||
import { Title } from "@/ui/Title"
|
||||
|
||||
export const Contacts: React.FC = props => {
|
||||
const { contacts } = useContext(ConfigContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<strong className={s.strong}>Контактная информация</strong>
|
||||
<Title level={3}>Контактная информация</Title>
|
||||
|
||||
{contacts.map((x, i) => (
|
||||
<p key={i} className={s.link}>
|
||||
|
||||
@@ -7,4 +7,3 @@
|
||||
border-top-left-radius: var(--radius);
|
||||
border-top-right-radius: var(--radius);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
import s from "./footer.module.css"
|
||||
|
||||
import cx from "classnames"
|
||||
import { Copyright } from "../Copyright"
|
||||
import { Author } from "./Author"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { Contacts } from "../Contacts"
|
||||
import { Box } from "@/ui/Box"
|
||||
|
||||
export type FooterProps = {
|
||||
showAuthor: boolean
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({ showAuthor, children }) => (
|
||||
<footer className={cx(s.footer, "opposite")}>
|
||||
<Box vertical align={false} style={{
|
||||
marginBottom: "var(--size-l)",
|
||||
}}>
|
||||
<Contacts />
|
||||
</Box>
|
||||
|
||||
export const Footer: React.FC<FooterProps> = ({ children }) => (
|
||||
<footer className={`${s.footer} opposite`}>
|
||||
{children}
|
||||
|
||||
<Box>
|
||||
<Copyright />
|
||||
|
||||
{!showAuthor ? null : (
|
||||
<>
|
||||
<Spacer as={"span"} />
|
||||
<Author />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</footer >
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.menuItem {
|
||||
user-select: none;
|
||||
background-color: var(--color-back-main);
|
||||
border-radius: var(--size-s);
|
||||
border-radius: var(--radius-small);
|
||||
transition: box-shadow 200ms ease-in, background-color 200ms ease-in;
|
||||
padding: var(--size-s) var(--size-s);
|
||||
}
|
||||
@@ -32,6 +32,12 @@
|
||||
|
||||
.menuItem.active:hover {
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--color-hover);
|
||||
background-color: var(--color-hover-back);
|
||||
}
|
||||
|
||||
.menuItem.active:hover a {
|
||||
color: var(--color-hover);
|
||||
}
|
||||
|
||||
.menuItem.current {
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ConfigContext } from "src/context/ConfigContext"
|
||||
import { Box } from "@/ui/Box"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { useToggle } from "react-use"
|
||||
import { Button } from "@/ui/Button"
|
||||
import { EyeOpenIcon } from "@radix-ui/react-icons"
|
||||
|
||||
export type NavigationProps = {
|
||||
style?: React.CSSProperties
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import s from "./wrapper.module.css"
|
||||
|
||||
import cx from "classnames"
|
||||
import { useTheme } from "@/ui/theme/useTheme"
|
||||
|
||||
export type WrapperProps = {
|
||||
header: React.ReactNode
|
||||
@@ -10,14 +11,18 @@ export type WrapperProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const Wrapper: React.FC<WrapperProps> = props => (
|
||||
<div className={cx(s.container, "theme-default")} style={props.style}>
|
||||
{props.header}
|
||||
export const Wrapper: React.FC<WrapperProps> = props => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
<main className={s.main} style={props.mainStyle}>
|
||||
{props.children}
|
||||
</main>
|
||||
return (
|
||||
<div className={cx(s.container, `theme-${theme}`)} style={props.style}>
|
||||
{props.header}
|
||||
|
||||
{props.footer}
|
||||
</div>
|
||||
)
|
||||
<main className={s.main} style={props.mainStyle}>
|
||||
{props.children}
|
||||
</main>
|
||||
|
||||
{props.footer}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+68
-2
@@ -34,6 +34,7 @@
|
||||
--color-back-opposite: hsl(0, 0%, 15%);
|
||||
|
||||
--color-shadow: rgba(27, 51, 88, 0.25);
|
||||
--color-hover: none;
|
||||
|
||||
/* SIZE */
|
||||
--size-xs: 0.35rem;
|
||||
@@ -44,6 +45,7 @@
|
||||
|
||||
/* EFFECTS */
|
||||
--radius: 16px;
|
||||
--radius-small: 12px;
|
||||
--shadow:
|
||||
0 2px 4px rgba(27, 51, 88, 0.2),
|
||||
0 4px 8px -8px rgba(0, 0, 0, 0.2),
|
||||
@@ -51,6 +53,7 @@
|
||||
--shadow-active:
|
||||
0 12px 24px -8px rgba(27, 51, 88, 0.25),
|
||||
0 8px 16px -8px rgba(0, 0, 0, 0.3);
|
||||
--border: none;
|
||||
|
||||
/* SPECIAL */
|
||||
--article-line-height: 1.5em;
|
||||
@@ -86,6 +89,7 @@ body {
|
||||
font-family: var(--text-font);
|
||||
}
|
||||
|
||||
/* TITLE */
|
||||
h1 {
|
||||
font-family: var(--title-font);
|
||||
text-align: center;
|
||||
@@ -103,8 +107,7 @@ h2 {
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: var(--size-m) 0;
|
||||
|
||||
margin-bottom: var(--size-s);
|
||||
font-size: var(--font-size-h3);
|
||||
|
||||
hyphens: none;
|
||||
@@ -191,3 +194,66 @@ h3 {
|
||||
--shadow-active: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* CONTRAST THEME */
|
||||
.theme-contrast {
|
||||
/* FONTS */
|
||||
--title-font: 'Roboto', Helvetica, sans-serif;
|
||||
--text-font: 'Roboto', Helvetica, sans-serif;
|
||||
|
||||
/* TEXT COLOR */
|
||||
--color-text: black;
|
||||
--color-text-second: black;
|
||||
--color-text-opposite: white;
|
||||
|
||||
--color-back-main: white;
|
||||
--color-back: white;
|
||||
--color-back-active: hsl(240, 10%, 20%);
|
||||
/* --color-back-opposite: hsl(240, 10%, 80%); */
|
||||
--color-back-opposite: black;
|
||||
--color-link: hsl(240, 10%, 40%);
|
||||
--color-link-opposite: hsl(240, 10%, 60%);
|
||||
|
||||
--color-hover: white;
|
||||
--color-hover-back: black;
|
||||
|
||||
/* EFFECTS */
|
||||
/* --radius: 0px; */
|
||||
--shadow: none;
|
||||
--shadow-active: none;
|
||||
|
||||
/* FONTS */
|
||||
--font-size-default: 16pt;
|
||||
--font-size-accent: 24pt;
|
||||
--font-size-second: 14pt;
|
||||
|
||||
--font-size-h1: 5rem;
|
||||
--font-size-h2: 3rem;
|
||||
--font-size-h3: 1.5rem;
|
||||
|
||||
/* OTHER */
|
||||
--radius: 0;
|
||||
--radius-small: 0;
|
||||
--border: 5px solid var(--color-back-opposite);
|
||||
}
|
||||
|
||||
/* CONTRAST + DARK THEME */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.theme-contrast {
|
||||
/* TEXT COLOR */
|
||||
--color-text: white;
|
||||
--color-text-second: white;
|
||||
--color-text-opposite: black;
|
||||
|
||||
--color-back-main: black;
|
||||
--color-back: black;
|
||||
--color-back-opposite: white;
|
||||
|
||||
/* --color-back-active: hsl(240, 10%, 20%); */
|
||||
/* --color-link: hsl(240, 10%, 40%); */
|
||||
/* --color-link-opposite: hsl(240, 10%, 60%); */
|
||||
|
||||
--color-hover: white;
|
||||
--color-hover-back: white;
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,13 @@ const cx = classnames.bind(s)
|
||||
export type BoxProps = {
|
||||
as?: keyof HTMLElementTagNameMap
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
wrap?: boolean
|
||||
align?: boolean
|
||||
vertical?: boolean
|
||||
center?: boolean
|
||||
gap?: number
|
||||
}
|
||||
|
||||
export const Box: React.FC<BoxProps> = ({
|
||||
@@ -22,11 +24,16 @@ export const Box: React.FC<BoxProps> = ({
|
||||
center = false,
|
||||
align = true,
|
||||
style,
|
||||
gap,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const newProps = {
|
||||
className: cx(s.box, { vertical, align, center, wrap }),
|
||||
style,
|
||||
className: cx(s.box, className, { vertical, align, center, wrap }),
|
||||
style: {
|
||||
...style,
|
||||
gap,
|
||||
},
|
||||
}
|
||||
|
||||
return createElement(as, newProps, props.children)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import s from "./panel.module.css"
|
||||
|
||||
import { Box } from "../Box"
|
||||
|
||||
export type PanelProps = {
|
||||
children?: React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
ghost?: boolean
|
||||
}
|
||||
|
||||
export const Panel: React.FC<PanelProps> = ({
|
||||
style,
|
||||
children,
|
||||
ghost = false,
|
||||
}) => {
|
||||
const variant = ghost ? s.ghost : s.default
|
||||
|
||||
return (
|
||||
<Box vertical align={false} className={`${s.panel} ${variant}`} style={style}>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.panel {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.default {
|
||||
padding: var(--size-m);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
background-color: none;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createElement } from "react"
|
||||
|
||||
export type TitleProps = {
|
||||
level?: 1 | 2 | 3 | 4 | 5 | 6
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const Title: React.FC<TitleProps> = ({ level = 1, children, ...props }) => {
|
||||
return createElement(`h${level}`, props, children)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useLocalStorage } from "react-use"
|
||||
import { ThemeContext } from "./ThemeContext"
|
||||
import { ThemeType } from "./types"
|
||||
|
||||
export type ThemeProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const Theme: React.FC<ThemeProps> = ({ children }) => {
|
||||
const [theme, setTheme] = useLocalStorage<ThemeType>("hudozka-theme", "default")
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={[theme ?? "default", setTheme]}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createContext } from "react"
|
||||
import { SetTheme, ThemeType } from "./types"
|
||||
|
||||
export const ThemeContext = createContext<[ThemeType, SetTheme]>(["default", () => {}])
|
||||
@@ -0,0 +1,2 @@
|
||||
export type ThemeType = "default" | "contrast"
|
||||
export type SetTheme = (theme: ThemeType) => void
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useContext } from "react"
|
||||
import { ThemeContext } from "./ThemeContext"
|
||||
import { SetTheme, ThemeType } from "./types"
|
||||
|
||||
export type UseThemeResult = {
|
||||
theme: ThemeType
|
||||
setTheme: SetTheme
|
||||
// toggleTheme:
|
||||
}
|
||||
|
||||
export function useTheme(): UseThemeResult {
|
||||
const [theme, setTheme] = useContext(ThemeContext)
|
||||
|
||||
return { theme, setTheme }
|
||||
}
|
||||
Reference in new issue
Block a user