diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
index 28c4b86f..8a15b505 100644
--- a/src/components/Footer/index.tsx
+++ b/src/components/Footer/index.tsx
@@ -3,34 +3,35 @@ import s from "./footer.module.css"
import cx from "classnames"
import { Copyright } from "../Copyright"
import { Author } from "./Author"
-import { Block } from "../Block"
import { Spacer } from "../Spacer"
import { Contacts } from "../Contacts"
+import { Box } from "@/ui/Box"
-export interface IFooterProps {
+export type FooterProps = {
showAuthor: boolean
+ children?: React.ReactNode
}
-export const Footer: React.FC
= props => (
+export const Footer: React.FC = ({ showAuthor, children }) => (
)
diff --git a/src/components/Menu/ActiveLink.tsx b/src/components/Menu/ActiveLink.tsx
index 0d97daad..e4638073 100644
--- a/src/components/Menu/ActiveLink.tsx
+++ b/src/components/Menu/ActiveLink.tsx
@@ -1,8 +1,9 @@
import Link from "next/link"
-export interface ActiveLinkProps {
+export type ActiveLinkProps = {
href?: string
activeStyle?: React.CSSProperties
+ children?: React.ReactNode
}
export const ActiveLink: React.FC = props => props.href
diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx
deleted file mode 100644
index 8fb57050..00000000
--- a/src/components/Menu/MenuItem.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import s from "./styles.module.css"
-import cx from "classnames"
-import { useRouter } from "next/router"
-import { ActiveLink } from "./ActiveLink"
-import { isPartOfPath } from "src/lib/url"
-
-export type MenuItemProps = {
- href: string
-}
-
-export const MenuItem: React.FC = props => {
- const ignore = ["/"]
- const router = useRouter()
- const current = router.asPath === props.href
- const selected = current || (!ignore.includes(props.href) && isPartOfPath(props.href, router.asPath))
- const href = current ? undefined : props.href
-
- return (
-
-
- {props.children}
-
-
- )
-}
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
index be6ac673..83cbea2e 100644
--- a/src/components/Menu/index.tsx
+++ b/src/components/Menu/index.tsx
@@ -1,27 +1,43 @@
import s from "./styles.module.css"
-import cx from "classnames"
-import { MenuItem } from "./MenuItem"
-import { IMenu } from "src/types"
+import { IMenu } from "@/types"
+import * as NavigationMenu from "@radix-ui/react-navigation-menu"
+import classnames from "classnames/bind"
+import { useRouter } from "next/router"
+import { ActiveLink } from "./ActiveLink"
-const layoutClass = {
- desktop: s.desktop,
- mobile: s.mobile,
-}
+let cx = classnames.bind(s)
export type MenuProps = {
- layout: "desktop" | "mobile"
+ vertical?: boolean
items: IMenu[]
}
-export const Menu: React.FC = props => (
-
-)
+export const Menu: React.FC = ({ vertical = false, items }) => {
+ const router = useRouter()
+ return (
+
+
+ {
+ items.map(item => {
+ const current = router.asPath === item.href
+ const href = current ? undefined : item.href
+
+ if (!href) {
+ return (
+
+ {item.name}
+
+ )
+ }
+ return (
+
+ {item.name}
+
+ )
+ })
+ }
+
+
+ )
+}
diff --git a/src/components/Menu/styles.module.css b/src/components/Menu/styles.module.css
index aa6c1653..e60ccf20 100644
--- a/src/components/Menu/styles.module.css
+++ b/src/components/Menu/styles.module.css
@@ -3,50 +3,51 @@
align-items: flex-start;
flex-wrap: wrap;
+ gap: var(--size-m);
+
font-size: var(--font-size-second);
list-style: none;
-}
-/* MENU LAYOUT */
-
-.desktop {
+ /* NOT VERTICAL */
flex-direction: row;
}
-.mobile {
+.vertical {
flex-direction: column;
}
/* MENU ITEM */
-.menu li {
- position: relative;
- border-top: var(--menu-mark-thickness) solid rgba(0, 0, 0, 0);
+.menuItem {
+ user-select: none;
+ background-color: var(--color-back-main);
+ border-radius: var(--size-s);
+ transition: box-shadow 200ms ease-in, background-color 200ms ease-in;
+ padding: var(--size-s) var(--size-s);
}
-/* MENU ITEM MOBILE */
-
-.mobile li {
- font-size: var(--font-size-default);
-
- padding: var(--size-xs) var(--size-s);
+.menuItem.active {
+ cursor: pointer;
}
-.mobile li.selected {
+.menuItem.active:hover {
+ box-shadow: var(--shadow);
+}
+
+.menuItem.current {
+ color: var(--color-text-opposite);
background-color: var(--color-back-opposite);
- color: var(--color-text-opposite)
}
-.mobile li.selected a {
- color: var(--color-text-opposite)
-}
-
-/* MENU ITEM DESKTOP */
-
-.desktop li {
- padding: var(--size-xs) var(--size-s);
+.menuItem a {
+ text-decoration: none;
+ color: var(--color-text);
}
-.desktop li.selected {
- border-top-color: var(--color-back-opposite);
+/* DARK THEME */
+@media (prefers-color-scheme: dark) {
+ .menuItem.active:hover {
+ box-shadow: none;
+ background-color: var(--color-back);
+ }
}
diff --git a/src/components/MenuToggle/index.tsx b/src/components/MenuToggle/index.tsx
index 79b7ad0c..84868393 100644
--- a/src/components/MenuToggle/index.tsx
+++ b/src/components/MenuToggle/index.tsx
@@ -1,27 +1,22 @@
-import s from "./styles.module.css"
+import { Button, ButtonProps } from "@/ui/Button"
+import { HamburgerMenuIcon, Cross1Icon } from "@radix-ui/react-icons"
-import cx from "classnames"
-import { Button } from "../Button"
-
-export interface IMenuToggleProps {
- style?: React.CSSProperties
+export type MenuToggleProps = Omit & {
open: boolean
- onClick: () => void
}
-export const MenuToggle: React.FC = props => {
+export const MenuToggle: React.FC = ({ open, ...props }) => {
+ const icon = open ? (
+
+ ) : (
+
+ )
return (
)
}
diff --git a/src/components/MenuToggle/styles.module.css b/src/components/MenuToggle/styles.module.css
deleted file mode 100644
index 1f984b06..00000000
--- a/src/components/MenuToggle/styles.module.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.toggle {
- --s: 32px;
- width: var(--s);
- height: var(--s);
- background-size: var(--s);
-}
-
-.toggle.open {
- background-image: url(/static/graphics/hmenu-close-b.png);
-}
-
-.toggle.close {
- background-image: url(/static/graphics/hmenu-b.png);
-}
diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx
index 77ca7fc2..90c3b8aa 100644
--- a/src/components/Navigation/index.tsx
+++ b/src/components/Navigation/index.tsx
@@ -1,76 +1,65 @@
import { MenuToggle } from "../MenuToggle"
import { useMobile } from "src/hooks/useMobile"
import { Menu } from "../Menu"
-import { Overlay } from "../Overlay"
-import { useState, useCallback, useContext } from "react"
+import { Overlay } from "@/ui/Overlay"
+import { useContext } from "react"
import { ConfigContext } from "src/context/ConfigContext"
-import { Block } from "../Block"
+import { Box } from "@/ui/Box"
import { Spacer } from "../Spacer"
+import { useToggle } from "react-use"
-export interface INavigationProps {
+export type NavigationProps = {
style?: React.CSSProperties
}
-export const Navigation: React.FC = props => {
+export const Navigation: React.FC = props => {
const collapseMenu = useMobile()
- const [open, setOpen] = useState(false)
- const onClick = useCallback(
- () => {
- setOpen(!open)
- },
- [open]
- )
+ const [open, toggleOpen] = useToggle(false)
const { menu } = useContext(ConfigContext)
return (
<>
-
+
-
-
+
-
-
-
-
- {collapseMenu ? (
-
-
-
-
- ) : (
-
- )}
+
+
+
+
+ {collapseMenu ? null : (
+
+ )}
+
+
+
+ {!collapseMenu ? null : (
+
+ )}
+
>
)
}
diff --git a/src/components/Overlay/index.tsx b/src/components/Overlay/index.tsx
deleted file mode 100644
index 6149251d..00000000
--- a/src/components/Overlay/index.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import s from "./styles.module.css"
-
-import { Transition } from "./Transition"
-import { useReducedMotion } from "../../hooks/useReducedMotion"
-import { useCallback, useEffect } from "react"
-
-export interface IOverlayProps {
- show: boolean
- onClickOverlay: () => void
- style?: React.CSSProperties
-}
-
-export const Overlay: React.FC = props => {
- const motionDisabled = useReducedMotion()
- const onClick = useCallback((event: React.MouseEvent) => {
- if (event.target === event.currentTarget) {
- props.onClickOverlay()
- }
- }, [props.onClickOverlay])
- const duration = motionDisabled ? 0 : 250
-
- useEffect(() => {
- if (props.show) {
- document.body.classList.add("noscroll")
- } else {
- document.body.classList.remove("noscroll")
- }
- }, [props.show])
-
- return (
-
-
- {props.children}
-
-
- )
-}
diff --git a/src/components/Page/index.tsx b/src/components/Page/index.tsx
index 2ba3c620..38257e6d 100644
--- a/src/components/Page/index.tsx
+++ b/src/components/Page/index.tsx
@@ -4,6 +4,7 @@ import { PageMeta } from "../PageMeta"
import { ITag } from "src/types"
export type PageProps = {
+ children?: React.ReactNode
style?: React.CSSProperties
date?: Date
tags: ITag[]
diff --git a/src/components/PageGrid/index.tsx b/src/components/PageGrid/index.tsx
index 289d1d1a..7c518751 100644
--- a/src/components/PageGrid/index.tsx
+++ b/src/components/PageGrid/index.tsx
@@ -3,8 +3,8 @@ import { PageCardDto } from "@/types"
import { CardGrid } from "../CardGrid"
import { Card, CardLayout } from "../Card"
import { Date } from "./Date"
-import { Block } from "../Block"
import { Spacer } from "../Spacer"
+import { Box } from "@/ui/Box"
export type PageGridProps = {
items: PageCardDto[]
@@ -23,9 +23,7 @@ export const PageGrid: React.FC = props => {
const content = !item.featured
? (
-
+
{item.title}
{!item.date ? null : (
@@ -33,7 +31,7 @@ export const PageGrid: React.FC = props => {
marginTop: "var(--size-m)",
}}>{item.date}
)}
-
+
) : (
item.title
)
diff --git a/src/components/Pager/index.tsx b/src/components/Pager/index.tsx
deleted file mode 100644
index b7dd8b92..00000000
--- a/src/components/Pager/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Block, BlockProps } from "../Block"
-import { Button } from "../Button"
-import { Spacer } from "../Spacer"
-
-export type PagerProps = Omit & {
- nextHref: string | null
- prevHref: string | null
-}
-
-export const Pager: React.FC = ({ prevHref, nextHref, ...props }) => (
-
- {!prevHref ? null : (
-
- )}
-
- {!nextHref ? null : (
-
- )}
-
-)
diff --git a/src/components/Picture/index.tsx b/src/components/Picture/index.tsx
index 3f4668be..0032eb3d 100644
--- a/src/components/Picture/index.tsx
+++ b/src/components/Picture/index.tsx
@@ -43,7 +43,7 @@ export const Picture: React.FC = memo(({ wide = false, ...props })
height={props.height}
layout={"responsive"}
blurDataURL={props.blur}
- placeholder={"blur"}
+ placeholder={props.blur ? "blur": "empty"}
/>
{!props.caption ? null : (
{props.caption}
diff --git a/src/components/TagList/index.tsx b/src/components/TagList/index.tsx
index ced51795..c4c0e57e 100644
--- a/src/components/TagList/index.tsx
+++ b/src/components/TagList/index.tsx
@@ -1,17 +1,19 @@
-import s from './styles.module.css'
+import s from "./styles.module.css"
-import cx from 'classnames'
-import { ITag } from 'src/types'
-import { Button } from '../Button'
+import cx from "classnames"
+import { ITag } from "src/types"
+import { Button } from "@/ui/Button"
+import Image from "next/image"
const directionClass = {
- horizontal: '',
+ horizontal: "",
vertical: s.vertical,
}
-export type Direction = 'horizontal' | 'vertical'
+export type Direction = "horizontal" | "vertical"
export type TagProps = {
+ children?: React.ReactNode
href: string
direction: Direction
}
diff --git a/src/components/Wrapper/index.tsx b/src/components/Wrapper/index.tsx
index 0725f431..cc5c99bb 100644
--- a/src/components/Wrapper/index.tsx
+++ b/src/components/Wrapper/index.tsx
@@ -7,6 +7,7 @@ export type WrapperProps = {
footer: React.ReactNode
style?: React.CSSProperties
mainStyle?: React.CSSProperties
+ children?: React.ReactNode
}
export const Wrapper: React.FC = props => (
diff --git a/src/components/Wrapper/wrapper.module.css b/src/components/Wrapper/wrapper.module.css
index 012781a9..6b8d26b9 100644
--- a/src/components/Wrapper/wrapper.module.css
+++ b/src/components/Wrapper/wrapper.module.css
@@ -1,7 +1,6 @@
.container {
display: flex;
flex-direction: column;
- min-height: 100vh;
background-color: var(--color-back-main);
font-family: var(--text-font);
@@ -9,5 +8,6 @@
}
.main {
+ min-height: 100vh;
flex: 1;
}
diff --git a/src/config.ts b/src/config.ts
index 49d38097..14ff5217 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -40,34 +40,6 @@ const config = {
name: "Школа",
href: "/",
},
- {
- name: "Поступление",
- href: "/join",
- },
- {
- name: "Предметы",
- href: "/courses",
- },
- {
- name: "Коллектив",
- href: "/collective",
- },
- {
- name: "«Мы соседи»",
- href: "/neighbors",
- },
- {
- name: "Документы",
- href: "/documents",
- },
- {
- name: "Сведения об образовательной организации",
- href: "/info",
- },
- {
- name: "Дистанционное образование",
- href: "/info/online-education",
- },
],
}
diff --git a/src/lib/url.ts b/src/lib/url.ts
index 2ae5acbe..bdc785a5 100644
--- a/src/lib/url.ts
+++ b/src/lib/url.ts
@@ -1,7 +1,3 @@
-export function isPartOfPath(part: string, path: string): boolean {
- return path.startsWith(part)
-}
-
export function queryList(key: string, values: string[]): string {
if (values.length === 0) {
return ""
diff --git a/src/style/article.css b/src/style/article.css
index 5b8f9b23..2f5d52ed 100644
--- a/src/style/article.css
+++ b/src/style/article.css
@@ -2,6 +2,21 @@
hyphens: auto;
}
+.article a {
+ color: var(--color-text);
+
+ text-decoration: none;
+ border-bottom: 1px solid var(--color-link);
+}
+
+.article a:hover {
+ border-bottom: 2px solid var(--color-text);
+}
+
+.article a:active {
+ border-width: 2px;
+}
+
.article h1 {
margin-bottom: var(--size-l);
}
diff --git a/src/style/style.css b/src/style/style.css
index e9d22303..12a2a369 100644
--- a/src/style/style.css
+++ b/src/style/style.css
@@ -44,14 +44,16 @@
/* EFFECTS */
--radius: 16px;
- --shadow: 0 0 8px var(--color-shadow);
+ --shadow:
+ 0 2px 4px rgba(27, 51, 88, 0.2),
+ 0 4px 8px -8px rgba(0, 0, 0, 0.2),
+ 0 8px 16px -8px rgba(0, 0, 0, 0.3);
--shadow-active:
0 12px 24px -8px rgba(27, 51, 88, 0.25),
0 8px 16px -8px rgba(0, 0, 0, 0.3);
/* SPECIAL */
--article-line-height: 1.5em;
- --menu-mark-thickness: 4px;
--embed-video-ratio: 56.25%;
/* CONTENT */
@@ -108,24 +110,9 @@ h3 {
hyphens: none;
}
-a {
- color: var(--color-text);
-
- text-decoration: none;
- border-bottom: 1px solid var(--color-link);
-}
-
-a:hover {
- border-bottom: 2px solid var(--color-text);
-}
-
-a:active {
- border-width: 2px;
-}
-
-a.invisible {
+/* a.invisible {
border-bottom: none;
-}
+} */
.opposite a {
color: var(--color-text-opposite);
@@ -136,10 +123,6 @@ a.invisible {
border-bottom-color: var(--color-text-opposite);
}
-.noscroll {
- overflow: hidden;
-}
-
/* RC TOOLTIP OVERRIDE */
.hudozka.rc-tooltip {
@@ -199,6 +182,7 @@ a.invisible {
--color-back-main: hsl(240, 10%, 10%);
--color-back: hsl(240, 10%, 15%);
+ --color-back-active: hsl(240, 10%, 20%);
--color-back-opposite: hsl(240, 10%, 80%);
--color-link: hsl(240, 10%, 40%);
--color-link-opposite: hsl(240, 10%, 60%);
diff --git a/src/ui/Box/box.module.css b/src/ui/Box/box.module.css
new file mode 100644
index 00000000..3eb304c2
--- /dev/null
+++ b/src/ui/Box/box.module.css
@@ -0,0 +1,25 @@
+.box {
+ display: flex;
+ align-items: baseline;
+
+ justify-content: flex-start;
+ flex-wrap: nowrap;
+ flex-direction: row;
+}
+
+.box.align {
+ align-items: center;
+}
+
+.box.wrap {
+ flex-wrap: wrap;
+}
+
+.box.center {
+ align-items: center;
+ justify-content: center;
+}
+
+.box.vertical {
+ flex-direction: column;
+}
diff --git a/src/ui/Box/index.tsx b/src/ui/Box/index.tsx
new file mode 100644
index 00000000..2243fdb5
--- /dev/null
+++ b/src/ui/Box/index.tsx
@@ -0,0 +1,33 @@
+import s from "./box.module.css"
+
+import classnames from "classnames/bind"
+import { createElement } from "react"
+
+const cx = classnames.bind(s)
+
+export type BoxProps = {
+ as?: keyof HTMLElementTagNameMap
+ children?: React.ReactNode
+ style?: React.CSSProperties
+ wrap?: boolean
+ align?: boolean
+ vertical?: boolean
+ center?: boolean
+}
+
+export const Box: React.FC = ({
+ as = "div",
+ wrap = false,
+ vertical = false,
+ center = false,
+ align = true,
+ style,
+ ...props
+}) => {
+ const newProps = {
+ className: cx(s.box, { vertical, align, center, wrap }),
+ style,
+ }
+
+ return createElement(as, newProps, props.children)
+}
diff --git a/src/components/Button/styles.module.css b/src/ui/Button/button.module.css
similarity index 60%
rename from src/components/Button/styles.module.css
rename to src/ui/Button/button.module.css
index 96b64d31..4649d061 100644
--- a/src/components/Button/styles.module.css
+++ b/src/ui/Button/button.module.css
@@ -1,28 +1,26 @@
.button {
- display: block;
+ display: inline-flex;
+ align-items: center;
+
font-size: var(--font-size-default);
text-align: center;
- border-radius: 100px;
+ border-radius: var(--size-s);
border: 1px solid var(--color-shadow);
color: var(--color-text);
background-color: var(--color-back);
-
- transition: box-shadow 150ms;
+ box-shadow: none;
+ transition: box-shadow 200ms ease-in, background-color 200ms ease-in;
outline: none;
cursor: pointer;
}
.button:hover {
- margin: 1px;
- /* margin-left: 1px;
- margin-right: 1px; */
-
- border: none;
- box-shadow: var(--shadow-active);
+ border-color: rgba(0, 0, 0, 0);
+ box-shadow: var(--shadow);
}
/* MODIFICATORS */
@@ -50,5 +48,13 @@
}
.themeIcon {
+ border-color: rgba(0, 0, 0, 0);
padding: var(--size-xs) var(--size-xs);
}
+
+/* DARK THEME */
+@media (prefers-color-scheme: dark) {
+ .button:hover {
+ background-color: var(--color-back-active);
+ }
+}
diff --git a/src/components/Button/index.tsx b/src/ui/Button/index.tsx
similarity index 81%
rename from src/components/Button/index.tsx
rename to src/ui/Button/index.tsx
index bbd34f05..6ac4463f 100644
--- a/src/components/Button/index.tsx
+++ b/src/ui/Button/index.tsx
@@ -1,8 +1,9 @@
-import s from "./styles.module.css"
+import s from "./button.module.css"
import Link from "next/link"
import cx from "classnames"
import { useCallback } from "react"
+import type { MouseEvent, MouseEventHandler } from "react"
const sizeClass = {
default: s.sizeDefault,
@@ -16,6 +17,7 @@ const themeClass = {
}
export type ButtonProps = {
+ children?: React.ReactNode
value?: any
style?: React.CSSProperties
href?: string
@@ -29,11 +31,11 @@ export const Button: React.FC = ({ size = "default", theme = "defau
const className = cx(s.button, sizeClass[size], themeClass[theme], {
[s.disabled]: disabled,
})
- const onClick = useCallback(event => {
- if (props.onClick) {
+ const onClick = useCallback>(event => {
+ if (typeof props.onClick === "function") {
props.onClick(props.value, event)
}
- }, [props.value, props.onClick])
+ }, [props])
if (props.href) {
return (
diff --git a/src/components/Overlay/Transition.tsx b/src/ui/Overlay/Transition.tsx
similarity index 67%
rename from src/components/Overlay/Transition.tsx
rename to src/ui/Overlay/Transition.tsx
index 48be786b..885b1d9e 100644
--- a/src/components/Overlay/Transition.tsx
+++ b/src/ui/Overlay/Transition.tsx
@@ -1,25 +1,26 @@
-import * as React from "react"
+import { useCallback, useEffect, useMemo, useState } from "react"
-export interface ITransitionProps {
+export type TransitionProps = {
+ children?: React.ReactNode
duration: number
extraStyle?: React.CSSProperties
show: boolean
}
-export const Transition: React.FC = props => {
- const [mounted, setMounted] = React.useState(props.show)
- const [opacity, setOpacity] = React.useState(0)
- const onTransitionEnd = React.useCallback(() => {
+export const Transition: React.FC = props => {
+ const [mounted, setMounted] = useState(props.show)
+ const [opacity, setOpacity] = useState(0)
+ const onTransitionEnd = useCallback(() => {
if (opacity === 0) {
setMounted(false)
}
}, [opacity])
- const transition = React.useMemo(
+ const transition = useMemo(
() => `all ${props.duration}ms`,
[props.duration]
)
- React.useEffect(() => {
+ useEffect(() => {
if (!props.show) {
setOpacity(0)
return
diff --git a/src/ui/Overlay/index.tsx b/src/ui/Overlay/index.tsx
new file mode 100644
index 00000000..d380ad04
--- /dev/null
+++ b/src/ui/Overlay/index.tsx
@@ -0,0 +1,39 @@
+import s from "./styles.module.css"
+
+import { Transition } from "./Transition"
+import { useReducedMotion } from "../../hooks/useReducedMotion"
+import { useLockBodyScroll } from "react-use"
+
+export type OverlayProps = {
+ children?: React.ReactNode
+ show: boolean
+ onClickOverlay: () => void
+ style?: React.CSSProperties
+ duration: number
+}
+
+export const Overlay: React.FC = props => {
+ const motionDisabled = useReducedMotion()
+ const duration = motionDisabled ? 0 : props.duration
+
+ useLockBodyScroll(props.show)
+
+ return (
+
+ {
+ if (event.target === event.currentTarget) {
+ props.onClickOverlay()
+ }
+ }}
+ className={s.overlay}
+ style={props.style}
+ >
+ {props.children}
+
+
+ )
+}
diff --git a/src/components/Overlay/styles.module.css b/src/ui/Overlay/styles.module.css
similarity index 100%
rename from src/components/Overlay/styles.module.css
rename to src/ui/Overlay/styles.module.css