Skip to content

Commit

Permalink
feat(blog): add blog pages
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackySoul committed Dec 28, 2024
1 parent 2cc6f7e commit 2611650
Show file tree
Hide file tree
Showing 39 changed files with 777 additions and 87 deletions.
2 changes: 2 additions & 0 deletions packages/vkui-docs-theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export { useFetch } from './src/hooks/useFetch';
export { StorybookIcon, GithubIcon, FigmaIcon } from './src/icons';
export { type DocsThemeConfig };
export default Layout;

export { TagTitle, TagName, getStaticPathsTags } from './src/blog/tags';
45 changes: 45 additions & 0 deletions packages/vkui-docs-theme/src/blog/PostsLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { SimpleGrid } from '@vkontakte/vkui';
import { useRouter } from 'next/router';
import { useConfig } from '../contexts';
import { Post } from './components/Post';
import { findPosts } from './helpers';
import { getTags } from './tags';

export function PostsLayout() {
const config = useConfig();
const { pageMap, frontMatter } = config;
const posts = findPosts(pageMap);
const router = useRouter();
const { type } = frontMatter;
const tagName = type === 'tag' ? router.query.tag : null;

return (
<SimpleGrid columns={3} gap="xl">
{posts.map((post) => {
const tags = getTags(post.frontMatter);
if (tagName) {
if (!Array.isArray(tagName) && !tags.includes(tagName)) {
return null;
}
} else if (type === 'tag') {
return null;
}

const postTitle = post.frontMatter?.title || post.name;
const date: Date | null = post.frontMatter?.date ? new Date(post.frontMatter.date) : null;

return (
<Post
key={post.route}
title={postTitle}
description={post.frontMatter?.description}
publishDate={date}
tags={tags}
route={post.route}
image={post.frontMatter?.image}
/>
);
})}
</SimpleGrid>
);
}
14 changes: 14 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import { Title } from '@vkontakte/vkui';

export interface HeadingProps extends React.ComponentProps<'h2'> {
Tag?: `h${1 | 2 | 3}`;
}

export function Heading({ Tag = 'h2', children, ...props }: HeadingProps) {
return (
<Title Component={Tag} {...props}>
{children}
</Title>
);
}
40 changes: 40 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Post/Post.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.root {
display: flex;
flex-direction: column;
overflow: hidden;
}

.heading {
--vkui--color_text_link: var(--vkui--color_text_primary);

color: var(--vkui--color_text_primary);
}

.headingPrimary {
font-size: 40px;
line-height: 46px;
}

.date:not(:only-child)::after {
content: '•';
padding-inline: var(--vkui--spacing_size_s);
}

.content {
padding-block: var(--vkui--size_base_padding_vertical--regular);
padding-inline: var(--vkui--size_base_padding_horizontal--regular);
display: flex;
flex-direction: column;
flex-grow: 1;

--vkui--color_text_link: var(--vkui--color_text_primary);
}

.description {
margin-block-start: var(--vkui--spacing_size_s);
}

.meta {
margin-block-start: auto;
padding-block-start: var(--vkui--spacing_size_s);
}
40 changes: 40 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Post/Post.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import { Card, Link, Text } from '@vkontakte/vkui';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import { PostHeading } from './PostHeading';
import { PostMeta, type PostMetaProps } from './PostMeta';
import styles from './Post.module.css';

interface PostProps extends PostMetaProps {
title: React.ReactNode;
description?: React.ReactNode;
route?: string;
image?: string;
}

export function Post({ title, description, tags, publishDate, route, image }: PostProps) {
const router = useRouter();

return (
<Card className={styles.root} mode="outline-tint" Component="article">
<img src={`${router.basePath}${image}`} alt={`Лого для карточки ${title}`} width="100%" />
<div className={styles.content}>
<PostHeading>
<Link href={route} Component={NextLink}>
{title}
</Link>
</PostHeading>
{description && (
<Text className={styles.description}>
{description}{' '}
<Link href={route} Component={NextLink}>
Читать далее...
</Link>
</Text>
)}
<PostMeta className={styles.meta} publishDate={publishDate} tags={tags} />
</div>
</Card>
);
}
20 changes: 20 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Post/PostHeading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import { classNames } from '@vkontakte/vkjs';
import { Heading, type HeadingProps } from '../Heading';
import styles from './Post.module.css';

export interface PostHeadingProps extends HeadingProps {
children?: React.ReactNode;
}

export function PostHeading({ children, Tag }: PostHeadingProps) {
if (Tag === 'h1') {
return <h1 className={classNames(styles.heading, styles.headingPrimary)}>{children}</h1>;
}

return (
<Heading className={styles.heading} Tag={Tag}>
{children}
</Heading>
);
}
27 changes: 27 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Post/PostMeta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ContentBadge, Footnote } from '@vkontakte/vkui';
import NextLink from 'next/link';
import { DateFormatter } from '../../../helpers/date';
import styles from './Post.module.css';

export interface PostMetaProps {
publishDate?: Date | null;
tags?: string[];
className?: string;
}

export function PostMeta({ publishDate, tags = [], className }: PostMetaProps) {
return (
<div className={className}>
{publishDate && (
<Footnote className={styles.date} inline>
<time dateTime={publishDate.toISOString()}>{DateFormatter.format(publishDate)}</time>
</Footnote>
)}
{tags.map((tag) => (
<NextLink href={`/blog/tags/${tag}`} key={tag}>
<ContentBadge appearance="neutral"># {tag}</ContentBadge>
</NextLink>
))}
</div>
);
}
3 changes: 3 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/Post/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Post } from './Post';
export { PostMeta } from './PostMeta';
export { PostHeading } from './PostHeading';
29 changes: 29 additions & 0 deletions packages/vkui-docs-theme/src/blog/components/PostHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Icon12ChevronLeft } from '@vkontakte/icons';
import { Button } from '@vkontakte/vkui';
import { useRouter } from 'next/router';
import { useConfig } from '../../contexts';
import { getTags } from '../tags';
import { PostHeading, PostMeta } from './Post';

export function PostHeader() {
const config = useConfig();
const { frontMatter } = config;
const router = useRouter();
const tags = getTags(frontMatter);

const back = () => {
void router.push('/blog');
};

const date: Date | null = frontMatter.date ? new Date(frontMatter.date) : null;

return (
<div>
<Button onClick={back} mode="link" before={<Icon12ChevronLeft />} size="s">
Назад
</Button>
<PostMeta publishDate={date} tags={tags} />
<PostHeading Tag="h1">{frontMatter.title}</PostHeading>
</div>
);
}
33 changes: 33 additions & 0 deletions packages/vkui-docs-theme/src/blog/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { MdxFile, PageMapItem, PageOpts } from 'nextra';

const sortPosts = (a: MdxFile, b: MdxFile): number => {
if (!a.frontMatter?.date || !b.frontMatter?.date) {
return -1;
}

return new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime();
};

export const isPost = (page: PageMapItem): page is MdxFile => {
if ('frontMatter' in page) {
const { draft, type } = page.frontMatter || {};
return !draft && type === 'post';
}
return false;
};

export function findPosts(pageMap: PageOpts['pageMap']) {
const posts: MdxFile[] = [];

for (const item of pageMap) {
if ('children' in item && item.name === 'blog') {
for (const pageMapItem of item.children) {
if (isPost(pageMapItem)) {
posts.push(pageMapItem);
}
}
}
}
posts.sort(sortPosts);
return posts;
}
2 changes: 2 additions & 0 deletions packages/vkui-docs-theme/src/blog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PostsLayout } from './PostsLayout';
export { PostHeader } from './components/PostHeader';
54 changes: 54 additions & 0 deletions packages/vkui-docs-theme/src/blog/tags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { GetStaticPaths } from 'next';
import Head from 'next/head';
import type { FrontMatter, PageMapItem } from 'nextra';
import { useData } from 'nextra/hooks';
import { isPost } from './helpers';

const NEXTRA_INTERNAL = Symbol.for('__nextra_internal__');

export const TagTitle = () => {
const { tag } = useData();
const title = `Посты по теме ${tag}`;
return (
<Head>
<title>{title}</title>
</Head>
);
};

export const TagName = () => {
const { tag } = useData();
return tag || null;
};

export function getTags(frontMatter: FrontMatter | undefined) {
if (!frontMatter) {
return [];
}
const tags: string | string[] = frontMatter.tag || [];
return (Array.isArray(tags) ? tags : tags.split(',')).map((s) => s.trim());
}

const getStaticTags = (pageMap: PageMapItem[]) => {
const tags = [];

for (const item of pageMap) {
if ('children' in item && item.name === 'blog') {
for (const pageMapItem of item.children) {
if (isPost(pageMapItem)) {
tags.push(...getTags(pageMapItem.frontMatter));
}
}
}
}

return [...new Set(tags)];
};

export const getStaticPathsTags: GetStaticPaths = () => {
const tags = getStaticTags((globalThis as any)[NEXTRA_INTERNAL].pageMap);
return {
paths: tags.map((v: any) => ({ params: { tag: v } })),
fallback: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
inset-block-start: 0;
z-index: 20;
inline-size: 100%;
background-color: transparent;
color: var(--vkui--color_text_primary);
border-block-end: var(--vkui--size_border--regular) solid
var(--vkui_docs--color_stroke_separator_secondary);
box-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.04);
block-size: var(--vkui_docs--navbar-height);
background-color: var(--vkui--color_background_content);
}

.navbar {
display: flex;
margin-inline: auto;
align-items: center;
block-size: var(--vkui_docs--navbar-height);
max-inline-size: var(--vkui_docs--max-width);
justify-content: space-between;
padding-block: var(--vkui--spacing_size_xl);
padding-inline: var(--vkui--spacing_size_2xl);
background-color: var(--vkui--color_background_content);
}

@media (--viewWidth-desktopPlus) {
Expand Down Expand Up @@ -47,8 +46,8 @@

.navbarLink {
color: var(--vkui--color_text_primary);
padding-block: 8px;
padding-inline: 16px;
padding-block: var(--vkui--spacing_size_m);
padding-inline: var(--vkui--spacing_size_l);
text-decoration: none;
}

Expand Down
Loading

0 comments on commit 2611650

Please sign in to comment.