42 files changed
+501
-643
No files matched your search
+17
-6
@@ -2,11 +2,11 @@ import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { tail } from 'lodash'
|
||||
import { Page } from 'src/components/Page'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
import { IBreadcumbsPart, IMeta, IPage } from 'src/types'
|
||||
|
||||
function array<T>(value: T | T[]) {
|
||||
return Array.isArray(value)
|
||||
@@ -14,13 +14,21 @@ function array<T>(value: T | T[]) {
|
||||
: [value]
|
||||
}
|
||||
|
||||
const Index = props => (
|
||||
type Props = {
|
||||
page: any
|
||||
title: string
|
||||
content: string
|
||||
breadcrumb: IBreadcumbsPart[]
|
||||
meta: IMeta
|
||||
}
|
||||
|
||||
const Index: NextPage<Props> = props => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
contentStyle={{
|
||||
marginTop: 'var(--size-l)'
|
||||
}}
|
||||
breadcrumbs={props.breadcrumb}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
@@ -42,16 +50,18 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
} else {
|
||||
slug = '/' + array(ctx.params.slug).join('/')
|
||||
}
|
||||
const page = await requestGet(createApiUrl(`/api/page?page=${slug}`), null)
|
||||
const page = await requestGet<IPage>(createApiUrl(`/api/page?page=${slug}`), null)
|
||||
if (!page) {
|
||||
throw new Error(`Not found: ${slug}`)
|
||||
}
|
||||
const image = page.preview ? page.preview.artifacts.fb : {}
|
||||
const breadcrumbSize = page.breadcrumb?.length ?? 0
|
||||
const breadcrumb = breadcrumbSize < 2 ? null : page.breadcrumb
|
||||
|
||||
return {
|
||||
props: {
|
||||
page,
|
||||
content: page.data,
|
||||
pageUrl: slug,
|
||||
title: page.title,
|
||||
meta: meta({
|
||||
title: page.title,
|
||||
@@ -59,6 +69,7 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
imageWidth: image.width,
|
||||
imageHeight: image.height,
|
||||
}),
|
||||
breadcrumb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Article } from 'src/components/Article'
|
||||
import { Html } from 'src/components/Html'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
data: any //(album)
|
||||
}
|
||||
|
||||
@@ -21,7 +18,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
@@ -43,7 +39,6 @@ const Page: NextPage<Props> = props => {
|
||||
}
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
const pageUrl = '/gallery'
|
||||
// const id = ctx.query.slug
|
||||
const id = ctx.params.slug
|
||||
const data = await requestGet(createApiUrl(`/api/albums/${id}`), null)
|
||||
@@ -55,7 +50,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
pageUrl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { get } from 'lodash'
|
||||
import { App } from 'src/components/App'
|
||||
import { Article } from 'src/components/Article'
|
||||
import { Html } from 'src/components/Html'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
@@ -12,7 +10,6 @@ import { NextPage } from 'next'
|
||||
import { IArticle } from 'src/types'
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
meta: any
|
||||
data: IArticle
|
||||
}
|
||||
@@ -25,7 +22,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
@@ -59,7 +55,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
pageUrl,
|
||||
meta: meta({
|
||||
title: data.title,
|
||||
image: image.src,
|
||||
|
||||
@@ -2,8 +2,6 @@ import Head from 'next/head'
|
||||
import { range } from 'lodash'
|
||||
import { App } from 'src/components/App'
|
||||
import { ArticleGrid } from 'src/components/ArticleGrid'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
@@ -12,7 +10,6 @@ import { NextPage } from 'next'
|
||||
const pageSize = Number(process.env.APP_ARTICLES_PAGE_SIZE)
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
meta: any
|
||||
title: string
|
||||
data: any //(list of article)
|
||||
@@ -28,7 +25,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
@@ -46,7 +42,6 @@ const Page: NextPage<Props> = props => {
|
||||
}
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
const pageUrl = '/'
|
||||
// const page = parseInt(ctx.query.page)
|
||||
const page = parseInt(ctx.params.slug)
|
||||
const res = await requestGet<IResponseItems<any[]>>(
|
||||
@@ -67,7 +62,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
data,
|
||||
nextPage,
|
||||
prevPage,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
|
||||
@@ -3,8 +3,6 @@ import { get } from 'lodash'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { CollectiveImage } from 'src/components/CollectiveImage'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { IMeta, IImage } from 'src/types'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
@@ -20,7 +18,6 @@ interface IPerson {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
title: string
|
||||
image: string
|
||||
meta: IMeta
|
||||
@@ -35,7 +32,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
@@ -83,7 +79,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: NextPageContext) => {
|
||||
// const pageUrl = ctx.req.url
|
||||
const pageUrl = '/collective'
|
||||
|
||||
const res = await requestGet<IResponseItems<IPerson>>(createApiUrl('/api/persons'), null)
|
||||
const data = res?.items || []
|
||||
@@ -96,11 +91,9 @@ export const unstable_getStaticProps = async (ctx: NextPageContext) => {
|
||||
props: {
|
||||
data,
|
||||
image,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
url: pageUrl,
|
||||
description: 'Преподаватели Шлиссельбургской ДХШ',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import Document from 'src/components/Document'
|
||||
import { Share } from 'src/components/Share'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
@@ -12,7 +10,6 @@ import { NextPage } from 'next'
|
||||
import { IMeta } from 'src/types'
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
data: any //(file)
|
||||
meta: IMeta
|
||||
}
|
||||
@@ -26,7 +23,6 @@ const Page: NextPage<Props> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
@@ -40,14 +36,12 @@ const Page: NextPage<Props> = props => {
|
||||
}
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
const pageUrl = '/documents'
|
||||
const id = ctx.params.slug
|
||||
const data = await requestGet(createApiUrl(`/api/files/${id}`), {})
|
||||
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
pageUrl,
|
||||
meta: meta({
|
||||
// title: file.meta,
|
||||
})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { IDocument } from 'src/types'
|
||||
import { DocumentList } from 'src/components/DocumentList'
|
||||
@@ -94,14 +92,12 @@ function getSorted(documents: IDocument[]): IDocumentCategory[] {
|
||||
|
||||
interface IProps {
|
||||
collections: IDocumentCategory[]
|
||||
pageUrl: string
|
||||
title: string
|
||||
meta: any
|
||||
}
|
||||
|
||||
const Page: NextPage<IProps> = props => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
@@ -122,7 +118,6 @@ const Page: NextPage<IProps> = props => (
|
||||
)
|
||||
|
||||
export const unstable_getStaticProps = async (ctx) => {
|
||||
const pageUrl = '/documents'
|
||||
const res = await requestGet<IResponseItems<IDocument>>(createApiUrl('/api/files'), null)
|
||||
const files = res.items
|
||||
const title = 'Документы'
|
||||
@@ -131,7 +126,6 @@ export const unstable_getStaticProps = async (ctx) => {
|
||||
return {
|
||||
props: {
|
||||
collections,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { splitBy } from 'src/lib/array'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { createApiUrl, requestGet } from 'src/next-lib'
|
||||
@@ -64,14 +62,12 @@ const ACollection = ({ title, albums }) => (
|
||||
|
||||
interface IProps {
|
||||
meta: any
|
||||
pageUrl: string
|
||||
title: string
|
||||
collections: any[]
|
||||
}
|
||||
|
||||
const Page: NextPage<IProps> = props => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
@@ -96,7 +92,6 @@ const Page: NextPage<IProps> = props => (
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
// const pageUrl = ctx.req.url
|
||||
const pageUrl = '/gallery'
|
||||
const res = await requestGet<any>(createApiUrl(`/api/albums`), null)
|
||||
if (!res) {
|
||||
return null
|
||||
@@ -108,7 +103,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
return {
|
||||
props: {
|
||||
collections: [...albumCollections.entries()],
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
|
||||
+1
-7
@@ -3,15 +3,12 @@ import { App } from 'src/components/App'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import { ArticleGrid } from 'src/components/ArticleGrid'
|
||||
import { HudozkaTitle } from 'src/components/HudozkaTitle'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
import { IArticle } from 'src/types'
|
||||
|
||||
interface IProps {
|
||||
pageUrl: string
|
||||
title: string
|
||||
meta: any
|
||||
prevPage: number
|
||||
@@ -21,7 +18,6 @@ interface IProps {
|
||||
|
||||
const Page: NextPage<IProps> = props => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
@@ -45,7 +41,6 @@ const Page: NextPage<IProps> = props => (
|
||||
)
|
||||
|
||||
export const unstable_getStaticProps = async () => {
|
||||
const pageUrl = '/'
|
||||
const page = 1
|
||||
const pageSize = process.env.APP_ARTICLES_PAGE_SIZE
|
||||
const res = await requestGet<IResponseItems<IArticle>>(createApiUrl(`/api/articles?page=${page}&pageSize=${pageSize}`), null)
|
||||
@@ -63,11 +58,10 @@ export const unstable_getStaticProps = async () => {
|
||||
articles,
|
||||
nextPage,
|
||||
prevPage,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
url: pageUrl,
|
||||
url: '/',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,16 @@ import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Schedule } from 'src/components/Schedule'
|
||||
import { Share } from 'src/components/Share'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { createApiUrl, requestGet } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
|
||||
type Props = {
|
||||
pageUrl: string
|
||||
title: string
|
||||
data: any //(schedule)
|
||||
}
|
||||
|
||||
const Page: NextPage<Props> = props => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
wide={true}
|
||||
>
|
||||
@@ -30,8 +26,6 @@ const Page: NextPage<Props> = props => (
|
||||
)
|
||||
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
// const pageUrl = ctx.req.url
|
||||
const pageUrl = '/schedule'
|
||||
const period = '2016-2017'
|
||||
const semester = 'spring'
|
||||
const data = await requestGet(createApiUrl(`/api/schedule?period=${period}&semester=${semester}`), null)
|
||||
@@ -42,7 +36,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
return {
|
||||
props: {
|
||||
data,
|
||||
pageUrl,
|
||||
title: 'Расписание',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import Head from 'next/head'
|
||||
import { App } from 'src/components/App'
|
||||
import { Article } from 'src/components/Article'
|
||||
import { Meta } from 'src/components/Meta'
|
||||
import menuModel from 'src/models/menu'
|
||||
import { buildMenu } from 'src/lib/menu'
|
||||
import { meta } from 'src/lib/meta'
|
||||
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
|
||||
import { NextPage } from 'next'
|
||||
@@ -11,7 +9,6 @@ import { IMeta } from 'src/types'
|
||||
import { Html } from 'src/components/Html'
|
||||
|
||||
interface IProps {
|
||||
pageUrl: string
|
||||
title: string
|
||||
meta: IMeta
|
||||
person: any
|
||||
@@ -26,7 +23,6 @@ const Index: NextPage<IProps> = props => {
|
||||
|
||||
return (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
>
|
||||
<Head>
|
||||
@@ -53,7 +49,6 @@ const Index: NextPage<IProps> = props => {
|
||||
export const unstable_getStaticProps = async (ctx: any) => {
|
||||
console.log('call unstable_getStaticProps', ctx)
|
||||
|
||||
const pageUrl = '/collective'
|
||||
// const id = ctx.query.person
|
||||
// const id = ctx.params.person ?? 'va-sarzhin'
|
||||
const id = ctx.params.person //?? 'mg-timasheva'
|
||||
@@ -73,7 +68,6 @@ export const unstable_getStaticProps = async (ctx: any) => {
|
||||
|
||||
return {
|
||||
props: {
|
||||
pageUrl,
|
||||
person,
|
||||
title,
|
||||
meta: meta({
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 643 B |
Binary file not shown.
|
Before Width: | Height: | Size: 147 B |
@@ -1,22 +0,0 @@
|
||||
export const Header: React.FC = props => (
|
||||
<header>
|
||||
<style jsx>{`
|
||||
.navigation {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.navigation__body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="navigation">
|
||||
<div className="navigation__body">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
@@ -1,36 +1,54 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
import cx from 'classnames'
|
||||
import Comments from '../Comments'
|
||||
import { useReducedMotion } from 'src/hooks/useReducedMotion'
|
||||
import { Wrapper } from '../Wrapper'
|
||||
import { Header } from './Header'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import config from 'src/config'
|
||||
import { Footer } from '../Footer'
|
||||
import { Content } from '../Content'
|
||||
import { Block } from '../Block'
|
||||
import { Breadcrumbs } from '../Breadcrumbs'
|
||||
import { IBreadcumbsPart } from 'src/types'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMobile } from 'src/hooks/useMobile'
|
||||
|
||||
const Navigation = dynamic(() => import('../Navigation').then(mod => mod.Navigation), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
export interface IAppProps {
|
||||
menu: any
|
||||
showAuthor: boolean
|
||||
wide?: boolean
|
||||
contentStyle?: React.CSSProperties
|
||||
breadcrumbs?: IBreadcumbsPart[]
|
||||
}
|
||||
|
||||
export const App: React.FC<IAppProps> = ({ wide = false, ...props }) => {
|
||||
const motionDisabled = useReducedMotion()
|
||||
export const App: React.FC<IAppProps> = props => {
|
||||
const hideBreadcrumbs = useMobile()
|
||||
const router = useRouter()
|
||||
const wide = props.wide ?? false
|
||||
const blockStyle = {
|
||||
justifyContent: 'center'
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={config}>
|
||||
<Wrapper
|
||||
header={(
|
||||
<Header>
|
||||
<Navigation
|
||||
items={props.menu.items}
|
||||
<header>
|
||||
<Navigation style={{
|
||||
margin: '0 var(--size-m) var(--size-m)',
|
||||
}} />
|
||||
{hideBreadcrumbs || !props.breadcrumbs ? null : (
|
||||
<Block direction={'horizontal'} style={blockStyle}>
|
||||
<Content wide={wide}>
|
||||
<Breadcrumbs
|
||||
items={props.breadcrumbs}
|
||||
path={router.asPath}
|
||||
/>
|
||||
</Header>
|
||||
</Content>
|
||||
</Block>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
footer={(
|
||||
<Footer
|
||||
@@ -38,15 +56,11 @@ export const App: React.FC<IAppProps> = ({ wide = false, ...props }) => {
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx('content', {
|
||||
'reduced-motion': motionDisabled,
|
||||
'content_wide': wide,
|
||||
})}
|
||||
style={props.contentStyle}
|
||||
>
|
||||
<Block direction={'horizontal'} style={blockStyle}>
|
||||
<Content wide={wide} style={props.contentStyle}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Content>
|
||||
</Block>
|
||||
|
||||
{/* <Comments /> */}
|
||||
</Wrapper>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import './styles.css'
|
||||
|
||||
import { IBreadcumbsPart } from 'src/types'
|
||||
import { Button } from '../Button'
|
||||
import { insertBetween } from 'src/lib/array'
|
||||
|
||||
export type BreadcrumbsProps = {
|
||||
style?: React.CSSProperties
|
||||
items: IBreadcumbsPart[]
|
||||
path: string
|
||||
}
|
||||
|
||||
export const Breadcrumbs: React.FC<BreadcrumbsProps> = props => {
|
||||
const items = insertBetween(props.items, () => '/')
|
||||
|
||||
return (
|
||||
<nav className={'breadcrumbs'} style={props.style}>
|
||||
{items.map((x, i) => {
|
||||
if (typeof x === 'string') {
|
||||
return (
|
||||
<li key={i}>
|
||||
<span style={{ margin: '0 var(--size-xs)' }}>{x}</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={x.href}>
|
||||
<Button
|
||||
href={x.href}
|
||||
theme={'ghost'}
|
||||
size={'small'}
|
||||
disabled={x.href === props.path}
|
||||
>
|
||||
{x.name}
|
||||
</Button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</nav >
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
|
||||
position: relative;
|
||||
right: var(--size-s);
|
||||
}
|
||||
|
||||
.breadcrumbs li {
|
||||
margin-bottom: var(--size-xs);
|
||||
}
|
||||
|
||||
.breadcrumbCurrent {
|
||||
color: var(--color-text-second);
|
||||
font-size: var(--font-size-second);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -1,16 +1,33 @@
|
||||
import './styles.css'
|
||||
|
||||
import Link from 'next/link'
|
||||
import cx from 'classnames'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export type ButtonProps = {
|
||||
value?: any
|
||||
style?: React.CSSProperties
|
||||
href?: string
|
||||
theme?: 'default' | 'ghost' | 'icon'
|
||||
size?: 'default' | 'small'
|
||||
disabled?: boolean
|
||||
onClick?: (value: any, event: MouseEvent) => void
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = props => {
|
||||
export const Button: React.FC<ButtonProps> = ({ size = 'default', theme = 'default', disabled = false, ...props }) => {
|
||||
const className = cx('button', `size_${size}`, `theme_${theme}`, {
|
||||
disabled
|
||||
})
|
||||
const onClick = useCallback(event => {
|
||||
if (props.onClick) {
|
||||
props.onClick(props.value, event)
|
||||
}
|
||||
}, [props.value, props.onClick])
|
||||
|
||||
if (props.href) {
|
||||
return (
|
||||
<Link href={props.href}>
|
||||
<a className={'button invisible'} style={props.style}>
|
||||
<a className={className} style={props.style}>
|
||||
{props.children}
|
||||
</a>
|
||||
</Link>
|
||||
@@ -18,7 +35,11 @@ export const Button: React.FC<ButtonProps> = props => {
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={'button'} style={props.style}>
|
||||
<button
|
||||
className={className}
|
||||
style={props.style}
|
||||
onClick={onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
.button {
|
||||
border: none;
|
||||
display: block;
|
||||
font-size: var(--font-size-default);
|
||||
|
||||
text-align: center;
|
||||
margin-top: var(--size-m);
|
||||
margin-bottom: var(--size-m);
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
|
||||
padding: var(--size-s) var(--size-m);
|
||||
border-radius: 100px;
|
||||
border: 1px solid var(--color-shadow);
|
||||
|
||||
background-color: var(--color-back);
|
||||
box-shadow: 0 0 0 1px var(--color-shadow);
|
||||
|
||||
transition: box-shadow 150ms;
|
||||
|
||||
@@ -21,5 +16,38 @@
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
margin: 1px;
|
||||
/* margin-left: 1px;
|
||||
margin-right: 1px; */
|
||||
|
||||
border: none;
|
||||
box-shadow: var(--shadow-active);
|
||||
}
|
||||
|
||||
/* MODIFICATORS */
|
||||
|
||||
.button.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--color-text-second);
|
||||
}
|
||||
|
||||
/* SIZE */
|
||||
|
||||
.button.size_default {
|
||||
padding: var(--size-s) var(--size-m);
|
||||
}
|
||||
|
||||
.button.size_small {
|
||||
padding: var(--size-xs) var(--size-s);
|
||||
font-size: var(--font-size-second);
|
||||
}
|
||||
|
||||
/* THEME */
|
||||
|
||||
.button.theme_ghost {
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.button.theme_icon {
|
||||
padding: var(--size-xs) var(--size-xs);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import cx from 'classnames'
|
||||
import { useReducedMotion } from 'src/hooks/useReducedMotion'
|
||||
|
||||
export type ContentProps = {
|
||||
style?: React.CSSProperties
|
||||
wide: boolean
|
||||
}
|
||||
|
||||
export const Content: React.FC<ContentProps> = props => {
|
||||
const motionDisabled = useReducedMotion()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('content', {
|
||||
'reduced-motion': motionDisabled,
|
||||
'content_wide': props.wide,
|
||||
})}
|
||||
style={props.style}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface ActiveLinkProps {
|
||||
href?: string
|
||||
activeStyle?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const ActiveLink: React.FC<ActiveLinkProps> = props => props.href
|
||||
? (
|
||||
<Link href={props.href}>
|
||||
<a>{props.children}</a>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={props.activeStyle}>{props.children}</span>
|
||||
)
|
||||
@@ -1,33 +1,34 @@
|
||||
import * as React from 'react'
|
||||
import cx from 'classnames'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ActiveLink } from './ActiveLink'
|
||||
import { isPartOfPath } from 'src/lib/url'
|
||||
|
||||
export interface IMenuItemProps {
|
||||
active: boolean
|
||||
text: string
|
||||
url: string
|
||||
export type MenuItemProps = {
|
||||
href: string
|
||||
layout: 'desktop' | 'mobile'
|
||||
}
|
||||
|
||||
export const MenuItem: React.FC<IMenuItemProps> = ({ active, text, url }) => (
|
||||
<>
|
||||
<style jsx>{`
|
||||
a {
|
||||
color: var(--dark-color);
|
||||
}
|
||||
export const MenuItem: React.FC<MenuItemProps> = 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 ? null : props.href
|
||||
|
||||
a:visited {
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-color: var(--dark-color);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{active
|
||||
? (
|
||||
<span>{text}</span>
|
||||
) : (
|
||||
<a href={url}>{text}</a>
|
||||
return (
|
||||
<li
|
||||
className={cx('menuItem', props.layout, {
|
||||
selected,
|
||||
})}
|
||||
>
|
||||
<ActiveLink
|
||||
href={href}
|
||||
activeStyle={{
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ActiveLink>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
+13
-115
@@ -1,125 +1,23 @@
|
||||
import * as React from 'react'
|
||||
import './styles.css'
|
||||
|
||||
import cx from 'classnames'
|
||||
import { getPathWithNoTrailingSlash } from '../../lib/url'
|
||||
import { MenuItem } from './MenuItem'
|
||||
import { IMenu } from 'src/types'
|
||||
|
||||
const itemUrl = (url: string) => getPathWithNoTrailingSlash(url)
|
||||
|
||||
export interface IMenuProps {
|
||||
export type MenuProps = {
|
||||
layout: 'desktop' | 'mobile'
|
||||
items: any[]
|
||||
items: IMenu[]
|
||||
}
|
||||
|
||||
export const Menu: React.FC<IMenuProps> = props => (
|
||||
<div className={cx('menu', props.layout)}>
|
||||
<style jsx>{`
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.desktop {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div.mobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
li{
|
||||
position: relative;
|
||||
border-top: var(--menu-colored-mark-thickness) solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
li.desktop {
|
||||
padding: 0.3em 0.2em 0;
|
||||
margin-right: 1em;
|
||||
|
||||
font-size: var(--normal-font-size);
|
||||
}
|
||||
|
||||
li.mobile {
|
||||
padding: 0.3em 0.5em;
|
||||
margin-bottom: var(--half-margin);
|
||||
|
||||
font-size: 1.25em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
li.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
li.desktop.selected.blue {
|
||||
border-top-color: var(--blue-color);
|
||||
}
|
||||
|
||||
li.desktop.selected.orange {
|
||||
border-top-color: var(--orange-color);
|
||||
}
|
||||
|
||||
li.desktop.selected.green {
|
||||
border-top-color: var(--green-color);
|
||||
}
|
||||
|
||||
li.desktop.selected.yellow {
|
||||
border-top-color: var(--yellow-color);
|
||||
}
|
||||
|
||||
li.desktop.selected.pink {
|
||||
border-top-color: var(--pink-color);
|
||||
}
|
||||
|
||||
li.desktop.selected.red {
|
||||
border-top-color: var(--red-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.blue {
|
||||
background-color: var(--blue-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.orange {
|
||||
background-color: var(--orange-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.green {
|
||||
background-color: var(--green-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.yellow {
|
||||
background-color: var(--yellow-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.pink {
|
||||
background-color: var(--pink-color);
|
||||
}
|
||||
|
||||
li.mobile.selected.red {
|
||||
background-color: var(--red-color);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
export const Menu: React.FC<MenuProps> = props => (
|
||||
<menu className={cx('menu', props.layout)}>
|
||||
{props.items.map((item, index) => (
|
||||
<li key={index}
|
||||
className={cx(
|
||||
props.layout,
|
||||
item.color, {
|
||||
selected: item.highlighted,
|
||||
})}
|
||||
<MenuItem key={index}
|
||||
href={item.href}
|
||||
layout={props.layout}
|
||||
>
|
||||
<MenuItem
|
||||
url={itemUrl(item.url)}
|
||||
text={item.text}
|
||||
active={item.active}
|
||||
/>
|
||||
</li>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
</menu>
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
font-size: var(--font-size-second);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.menu.desktop {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.menu.mobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
position: relative;
|
||||
border-top: var(--menu-mark-thickness) solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/* MOBILE */
|
||||
|
||||
.menuItem.mobile {
|
||||
font-size: var(--font-size-default);
|
||||
|
||||
padding: var(--size-xs) var(--size-s);
|
||||
}
|
||||
|
||||
.menuItem.mobile.selected {
|
||||
background-color: var(--color-back-opposite);
|
||||
color: var(--color-text-opposite)
|
||||
}
|
||||
|
||||
.menuItem.mobile.selected a {
|
||||
color: var(--color-text-opposite)
|
||||
}
|
||||
|
||||
/* DESKTOP */
|
||||
|
||||
.menuItem.desktop {
|
||||
padding: var(--size-xs) var(--size-s);
|
||||
}
|
||||
|
||||
.menuItem.desktop.selected {
|
||||
border-top-color: var(--color-back-opposite);
|
||||
}
|
||||
@@ -1,79 +1,27 @@
|
||||
import * as React from 'react'
|
||||
import './styles.css'
|
||||
|
||||
import cx from 'classnames'
|
||||
import { Button } from '../Button'
|
||||
|
||||
export interface IMenuToggleProps {
|
||||
style?: React.CSSProperties
|
||||
open: boolean
|
||||
position: 'left' | 'right'
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const MenuToggle: React.FC<IMenuToggleProps> = props => {
|
||||
return (
|
||||
<Button
|
||||
onClick={props.onClick}
|
||||
style={props.style}
|
||||
theme={'icon'}
|
||||
>
|
||||
<div
|
||||
className={cx(props.position, {
|
||||
className={cx('toggle', {
|
||||
open: props.open,
|
||||
close: !props.open,
|
||||
})}
|
||||
style={props.style}
|
||||
>
|
||||
<style jsx>{`
|
||||
div {
|
||||
--hmenu-margin: 6px;
|
||||
--hmenu-icon-width: 32px;
|
||||
--hmenu-icon-height: 32px;
|
||||
|
||||
position: absolute;
|
||||
height: var(--hmenu-icon-height);
|
||||
margin: var(--hmenu-margin);
|
||||
}
|
||||
|
||||
div.right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
div.left {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
|
||||
width: var(--hmenu-icon-width);
|
||||
height: var(--hmenu-icon-height);
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.open a {
|
||||
background-image: url(/static/graphics/hmenu-close-b.png);
|
||||
}
|
||||
|
||||
div.open a:hover {
|
||||
background-image: url(/static/graphics/hmenu-close-hover-b.png);
|
||||
}
|
||||
|
||||
div.close a {
|
||||
background-image: url(/static/graphics/hmenu-b.png);
|
||||
}
|
||||
|
||||
div.close a:hover {
|
||||
background-image: url(/static/graphics/hmenu-hover-b.png);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
onClick={props.onClick}
|
||||
></a>
|
||||
</div>
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.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);
|
||||
}
|
||||
@@ -1,67 +1,76 @@
|
||||
import * as React from 'react'
|
||||
import { MenuToggle } from '../MenuToggle'
|
||||
import { useScreenType } from '../../hooks/useScreenType'
|
||||
import { useMobile } from 'src/hooks/useMobile'
|
||||
import { Menu } from '../Menu'
|
||||
import { Overlay } from '../Overlay'
|
||||
import { useState, useCallback, useContext } from 'react'
|
||||
import { ConfigContext } from 'src/context/ConfigContext'
|
||||
import { Block } from '../Block'
|
||||
import { Spacer } from '../Spacer'
|
||||
|
||||
export interface INavigationProps {
|
||||
items: any[]
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const Navigation: React.FC<INavigationProps> = props => {
|
||||
const isMobile = useScreenType(['phone', 'tablet'])
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const onClick = React.useCallback(
|
||||
const collapseMenu = useMobile()
|
||||
const [open, setOpen] = useState(false)
|
||||
const onClick = useCallback(
|
||||
() => {
|
||||
setOpen(!open)
|
||||
},
|
||||
[open]
|
||||
)
|
||||
const { menu } = useContext(ConfigContext)
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<style jsx>{`
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
|
||||
font-size: var(--normal-font-size);
|
||||
list-style: none;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{isMobile ? (
|
||||
<MenuToggle
|
||||
open={open}
|
||||
position={'right'}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
zIndex: 1000001,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Menu
|
||||
layout={'desktop'}
|
||||
items={props.items}
|
||||
/>
|
||||
)}
|
||||
|
||||
<>
|
||||
<Overlay
|
||||
show={open}
|
||||
onClickOverlay={onClick}
|
||||
>
|
||||
<div style={{
|
||||
width: '50%',
|
||||
margin: '50px auto',
|
||||
<Block direction={'horizontal'}>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
margin: 'var(--size-xs)'
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
<Block direction={'horizontal'} style={{
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<nav style={{
|
||||
width: '75%',
|
||||
}}>
|
||||
<Menu
|
||||
layout={'mobile'}
|
||||
items={props.items}
|
||||
items={menu}
|
||||
/>
|
||||
</div>
|
||||
</Overlay>
|
||||
</nav>
|
||||
</Block>
|
||||
</Overlay>
|
||||
|
||||
{collapseMenu ? (
|
||||
<Block direction={'horizontal'}>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
margin: 'var(--size-xs)'
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
) : (
|
||||
<nav style={props.style}>
|
||||
<Menu
|
||||
layout={'desktop'}
|
||||
items={menu}
|
||||
/>
|
||||
</nav>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +1,42 @@
|
||||
import * as React from 'react'
|
||||
import './styles.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<IOverlayProps> = props => {
|
||||
const motionDisabled = useReducedMotion()
|
||||
const duration = motionDisabled ? 0 : 250
|
||||
|
||||
const onClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onClick = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
||||
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 (
|
||||
<Transition
|
||||
duration={duration}
|
||||
show={props.show}
|
||||
extraStyle={{
|
||||
zIndex: 1000000,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={'overlay'}
|
||||
style={props.style}
|
||||
>
|
||||
<style jsx>{`
|
||||
div {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
z-index: 1000000;
|
||||
|
||||
background-color: var(--color-back-main);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as React from 'react'
|
||||
import { Share } from '../Share'
|
||||
import { Html } from '../Html'
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ import cx from 'classnames'
|
||||
export type WrapperProps = {
|
||||
header: React.ReactNode
|
||||
footer: React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
mainStyle?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const Wrapper: React.FC<WrapperProps> = props => (
|
||||
<div className={cx('wrapper', 'theme-default')}>
|
||||
<div className={cx('wrapper', 'theme-default')} style={props.style}>
|
||||
<style jsx>{`
|
||||
.wrapper {
|
||||
display: flex;
|
||||
@@ -17,20 +19,11 @@ export const Wrapper: React.FC<WrapperProps> = props => (
|
||||
font-family: var(--text-font);
|
||||
font-size: var(--font-size-default);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{props.header}
|
||||
|
||||
<main>
|
||||
<main style={props.mainStyle}>
|
||||
{props.children}
|
||||
</main>
|
||||
|
||||
|
||||
@@ -29,5 +29,43 @@ export default {
|
||||
value: 'https://www.instagram.com/hudozka',
|
||||
title: 'Инстаграм'
|
||||
}
|
||||
],
|
||||
menu: [
|
||||
{
|
||||
name: 'Школа',
|
||||
href: '/',
|
||||
},
|
||||
{
|
||||
name: 'Расписание',
|
||||
href: '/schedule',
|
||||
},
|
||||
{
|
||||
name: 'Поступление',
|
||||
href: '/join',
|
||||
},
|
||||
{
|
||||
name: 'Предметы',
|
||||
href: '/courses',
|
||||
},
|
||||
{
|
||||
name: 'Коллектив',
|
||||
href: '/collective',
|
||||
},
|
||||
{
|
||||
name: 'Галерея',
|
||||
href: '/gallery',
|
||||
},
|
||||
{
|
||||
name: '«Мы соседи»',
|
||||
href: '/neighbors',
|
||||
},
|
||||
{
|
||||
name: 'Документы',
|
||||
href: '/documents',
|
||||
},
|
||||
{
|
||||
name: 'Сведения об образовательной организации',
|
||||
href: '/info/',
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import useMedia from 'use-media'
|
||||
|
||||
export function useMobile(): boolean {
|
||||
return useMedia('(max-width: 31.25em)', false)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import useMedia from 'use-media'
|
||||
|
||||
export type Type = 'phone' | 'tablet' | 'laptop' | 'desktop'
|
||||
|
||||
export function useScreenType(types: Type[]): boolean {
|
||||
const isPhone = useMedia('(max-width: 31.25em)')
|
||||
const [match, setMatch] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMatch(isPhone && types.includes('phone'))
|
||||
}, [isPhone])
|
||||
|
||||
return match
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
export function splitBy(fn) {
|
||||
return items => {
|
||||
return items.reduce((acc, item) => {
|
||||
const key = fn(item)
|
||||
const items = acc.get(key) || []
|
||||
|
||||
return acc.set(key, [...items, item])
|
||||
}, new Map())
|
||||
}
|
||||
}
|
||||
|
||||
export function head(list) {
|
||||
return Array.isArray(list) && list.length
|
||||
? list[0]
|
||||
: null
|
||||
}
|
||||
|
||||
export function last(list) {
|
||||
return Array.isArray(list) && list.length
|
||||
? list[list.length - 1]
|
||||
: null
|
||||
}
|
||||
|
||||
// exports.splitBy = splitBy
|
||||
// exports.head = head
|
||||
// exports.last = last
|
||||
@@ -0,0 +1,22 @@
|
||||
export function splitBy(fn) {
|
||||
return items => {
|
||||
return items.reduce((acc, item) => {
|
||||
const key = fn(item)
|
||||
const items = acc.get(key) || []
|
||||
|
||||
return acc.set(key, [...items, item])
|
||||
}, new Map())
|
||||
}
|
||||
}
|
||||
|
||||
export function insertBetween<T, I = T>(items: T[], fn: (i: number) => I): Array<T | I> {
|
||||
return items.reduce((acc, x, i) => {
|
||||
acc.push(x)
|
||||
|
||||
if (i < items.length - 1) {
|
||||
acc.push(fn(i))
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import url from 'url'
|
||||
import { getPathWithNoTrailingSlash } from '../lib/url'
|
||||
import { urlToPattern } from '../lib/url'
|
||||
import { isMatchPathPattern } from '../lib/url'
|
||||
import { isEqualPath } from '../lib/url'
|
||||
import { compose, any } from '../lib/common'
|
||||
|
||||
function isActive(path, menuItem) {
|
||||
return isEqualPath(path, menuItem.url, true)
|
||||
}
|
||||
|
||||
function isHighlighted(path, menuItem) {
|
||||
return any(flatMenuItem(menuItem)
|
||||
.map(i => isMatchPathPattern(
|
||||
compose(urlToPattern, getPathWithNoTrailingSlash)(i.url),
|
||||
path,
|
||||
true
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
function flatMenuItem(item) {
|
||||
const rootUrl = item.url
|
||||
const flatUrls = items => {
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
url: url.resolve(rootUrl, item.url)
|
||||
}))
|
||||
}
|
||||
|
||||
return !item.items
|
||||
? [item]
|
||||
: item.items.reduce((acc, i) => [...acc, ...compose(flatUrls, flatMenuItem)(i)], [item])
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes plain menu model and sample path and defines is an item of menu should be active
|
||||
*
|
||||
* @param path
|
||||
* @param menu
|
||||
* @return {{items}}
|
||||
*/
|
||||
export function buildMenu(path, menu) {
|
||||
const items = menu.map(item => ({
|
||||
...item,
|
||||
active: isActive(path, item),
|
||||
highlighted: isHighlighted(path, item),
|
||||
}))
|
||||
return { items }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
function getPathWithNoTrailingSlash(path='') {
|
||||
path = path.charAt(path.length - 1) === '/'
|
||||
? path.substr(0, path.length - 1)
|
||||
: path
|
||||
return path.length === 0
|
||||
? '/'
|
||||
: path
|
||||
}
|
||||
|
||||
function isEqualPath(a, b, ignoreTrailingSlash = false) {
|
||||
return ignoreTrailingSlash
|
||||
? getPathWithNoTrailingSlash(a) === getPathWithNoTrailingSlash(b)
|
||||
: a === b
|
||||
}
|
||||
|
||||
function isMatchPathPattern(pattern, path, ignoreTrailingSlash = false) {
|
||||
const test = ignoreTrailingSlash
|
||||
? getPathWithNoTrailingSlash(path)
|
||||
: path
|
||||
return pattern.test(test)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* /article/* -> ^\/article\/[/\w\d-_]+\/?$
|
||||
*
|
||||
* @param url
|
||||
* @return {RegExp}
|
||||
*/
|
||||
function urlToPattern(url) {
|
||||
const u = url.replace('/*', '[/\\w\\d-_]+')
|
||||
const pattern = `^${u}/?$`
|
||||
|
||||
return new RegExp(pattern)
|
||||
}
|
||||
|
||||
exports.getPathWithNoTrailingSlash = getPathWithNoTrailingSlash
|
||||
exports.isMatchPathPattern = isMatchPathPattern
|
||||
exports.urlToPattern = urlToPattern
|
||||
exports.isEqualPath = isEqualPath
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isPartOfPath(part: string, path: string): boolean {
|
||||
return path.startsWith(part)
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
url: '/',
|
||||
text: 'Школа',
|
||||
color: 'blue',
|
||||
items: [
|
||||
{url: '/summer-school/2017', text: 'Летние каникулы 2017'},
|
||||
{url: '/articles/*', text: 'Статьи'},
|
||||
{url: '/article/*', text: ''},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Расписание',
|
||||
url: '/schedule/',
|
||||
color: 'green',
|
||||
items: [
|
||||
{url: '*', text: ''},
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
'text': 'Поступление',
|
||||
'url': '/join',
|
||||
'color': 'yellow'
|
||||
},
|
||||
{
|
||||
url: '/courses/',
|
||||
text: 'Предметы',
|
||||
color: 'magenta',
|
||||
items: [
|
||||
{url: 'ceramics', text: 'Керамика'},
|
||||
{url: 'batik', text: 'Живопись'},
|
||||
{url: 'easel-composition', text: 'Живопись'},
|
||||
{url: 'cg', text: 'Живопись'},
|
||||
{url: 'scale-modeling', text: 'Живопись'},
|
||||
{url: 'drawing', text: 'Живопись'},
|
||||
{url: 'costume-composition', text: 'Живопись'},
|
||||
{url: 'felting', text: 'Живопись'},
|
||||
{url: 'art-history', text: 'Живопись'},
|
||||
|
||||
{url: 'perishki', text: 'Живопись'},
|
||||
{url: 'matreshki', text: 'Живопись'},
|
||||
{url: 'listochki', text: 'Живопись'},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Коллектив',
|
||||
url: '/collective',
|
||||
color: 'pink',
|
||||
items: [
|
||||
{url: '/teacher/*', text: ''},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Галерея',
|
||||
url: '/gallery',
|
||||
color: 'red',
|
||||
items: [
|
||||
{url: '/album/*', text: ''},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '«Мы соседи»',
|
||||
url: '/neighbors',
|
||||
color: 'yellow',
|
||||
},
|
||||
{
|
||||
text: 'Документы',
|
||||
url: '/documents',
|
||||
color: 'gray',
|
||||
items: [
|
||||
{url: '/document/*', text: ''},
|
||||
]
|
||||
},
|
||||
{
|
||||
url: '/info/',
|
||||
text: 'Сведения об образовательной организации',
|
||||
color: 'orange',
|
||||
items: [
|
||||
{url: 'main', text: 'Основные сведения'},
|
||||
{url: 'structure', text: 'Структура и органы управления образовательной организацией'},
|
||||
{url: 'education', text: 'Образование'},
|
||||
{url: 'standards', text: 'Образовательные стандарты'},
|
||||
//{url: '/collective', text: 'Руководство. Педагогический (научно-педагогический) состав'},
|
||||
{url: 'support', text: 'Материально-техническое обеспечение образовательного процесса'},
|
||||
{url: 'grants', text: 'Стипендии и иные виды материальной поддержки'},
|
||||
{url: 'paid-services', text: 'Платные образовательные услуги'},
|
||||
{url: 'finances', text: 'Финансово-хозяйственная деятельность'},
|
||||
{url: 'vacancy', text: 'Вакантные места для приема'},
|
||||
|
||||
{url: 'tools', text: 'Список рекомендованных материалов'},
|
||||
{url: 'history', text: 'История школы'},
|
||||
|
||||
{url: '*', text: ''},
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Текущий контроль',
|
||||
'url': '/info/audit',
|
||||
'color': 'gray'
|
||||
}
|
||||
]
|
||||
+9
-13
@@ -12,7 +12,7 @@
|
||||
--biggest-font-size: 2.25rem;
|
||||
|
||||
/* MENU */
|
||||
--menu-colored-mark-thickness: 4px;
|
||||
--menu-mark-thickness: 4px;
|
||||
|
||||
/* SIZE */
|
||||
--mobile-width: 31.25em;
|
||||
@@ -59,16 +59,16 @@
|
||||
--schedule-row-color: hsl(210, 55%, 90%);
|
||||
|
||||
/* TEXT COLOR */
|
||||
--color-text: #303030;
|
||||
--color-text: #202020;
|
||||
--color-text-second: #9090a0;
|
||||
--color-text-opposite: var(--light-color);
|
||||
--color-text-opposite: #f0f0f0;
|
||||
|
||||
--color-back-main: white;
|
||||
--color-back-opposite: var(--darker-color);
|
||||
|
||||
/* SIZE */
|
||||
--size-xs: 0.25em;
|
||||
--size-s: 0.5em;
|
||||
--size-xs: 0.35em;
|
||||
--size-s: 0.75em;
|
||||
--size-m: 1em;
|
||||
--size-l: 2em;
|
||||
--size-xl: 3em;
|
||||
@@ -175,12 +175,8 @@ a.invisible {
|
||||
border-bottom-color: var(--color-text-opposite);
|
||||
}
|
||||
|
||||
.actor {
|
||||
border-bottom: 1px dashed black;
|
||||
}
|
||||
|
||||
.actor:hover {
|
||||
border-bottom: 2px solid black;
|
||||
.noscroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 31.25em) {
|
||||
@@ -196,8 +192,8 @@ a.invisible {
|
||||
--font-size-h3: 1.25rem;
|
||||
|
||||
--content-width: 100%;
|
||||
--content-margin: var(--size-l);
|
||||
--content-wide-margin: var(--size-l) var(--size-l);
|
||||
--content-margin: var(--size-m);
|
||||
--content-wide-margin: var(--size-m) var(--size-m);
|
||||
|
||||
--kazimir-image-increase: 0px;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
export interface IMenu {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export interface IBreadcumbsPart {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export interface IPage {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
data: string
|
||||
preview: any //IImage
|
||||
tokens: any[]
|
||||
breadcrumb?: IBreadcumbsPart[]
|
||||
}
|
||||
|
||||
export interface IDocument {
|
||||
category: string
|
||||
fileName: string
|
||||
|
||||
Reference in new issue
Block a user