Skip to content

Commit

Permalink
feat(docs): add nextra v4 support (#8289)
Browse files Browse the repository at this point in the history
* feat(docs): add nextra v4 support

* Resolve yarn.lock

* fix next public env

* fix types linting

* fix types linting

* fix types linting
  • Loading branch information
BlackySoul authored Feb 21, 2025
1 parent 2bb7778 commit bd05a01
Show file tree
Hide file tree
Showing 86 changed files with 644 additions and 788 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy_docs_on_branch_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
- name: Build
run: yarn docs:beta:build
env:
VKUI_DOCS_BASE_PATH: /next/docs-beta
NEXT_PUBLIC_VKUI_DOCS_BASE_PATH: /next/docs-beta

- name: Upload to S3
id: deploy
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_request_packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ jobs:
- name: Build
run: yarn docs:beta:build
env:
VKUI_DOCS_BASE_PATH: /pull/${{ github.event.pull_request.number }}/${{ github.event.pull_request.head.sha }}/docs-beta
NEXT_PUBLIC_VKUI_DOCS_BASE_PATH: /pull/${{ github.event.pull_request.number }}/${{ github.event.pull_request.head.sha }}/docs-beta

- name: Upload dist
uses: actions/upload-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions .stylelintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ coverage/
!packages/vkui
tmp/
storybook-static/
out/
3 changes: 3 additions & 0 deletions packages/vkui-docs-theme/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"root": false,
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"react/react-in-jsx-scope": "off",
"no-restricted-globals": "off"
Expand Down
13 changes: 6 additions & 7 deletions packages/vkui-docs-theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Layout from './src/index';
import type { PartialDocsThemeConfig as DocsThemeConfig } from './src/types';

export { useFetch } from './src/hooks/useFetch';
export { StorybookIcon, GithubIcon, FigmaIcon } from './src/icons';
export { type DocsThemeConfig };
export default Layout;
export { StorybookIcon, GithubIcon, FigmaIcon, LogoIcon } from './src/icons';

export { getStaticPathsTags } from './src/blog/tags';

export { TagTitle, TagName, getStaticPathsTags } from './src/blog/tags';
export { Head, Navbar } from './src/components';
export { Layout } from './src/layout';
export { getMdxComponents } from './src/mdx';
4 changes: 2 additions & 2 deletions packages/vkui-docs-theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
},
"devDependencies": {
"next": "^15.1.7",
"nextra": "^3.2.5",
"nextra": "^4.2.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"peerDependencies": {
"next": "^15.1.7",
"nextra": "^3.2.5",
"nextra": "^4.2.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
34 changes: 13 additions & 21 deletions packages/vkui-docs-theme/src/blog/PostsLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,34 @@
'use client';

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;

const {
normalizePagesResult: { activePath },
} = config;
const posts = (
activePath.find((item) => item.type === 'page' && item.children.length > 0)?.children || []
).filter((post) => post.name !== 'index');
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;
const tags = getTags(post.frontMatter.tags);
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}
description={post.frontMatter.description}
publishDate={date}
tags={tags}
route={post.route}
image={post.frontMatter?.image}
image={post.frontMatter.image}
/>
);
})}
Expand Down
9 changes: 5 additions & 4 deletions packages/vkui-docs-theme/src/blog/components/Post/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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';
Expand All @@ -14,11 +13,13 @@ interface PostProps extends PostMetaProps {
}

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%" />
<img
src={`${process.env.NEXT_PUBLIC_VKUI_DOCS_BASE_PATH || ''}${image}`}
alt={`Лого для карточки ${title}`}
width="100%"
/>
<div className={styles.content}>
<PostHeading>
<Link href={route} Component={NextLink}>
Expand Down
19 changes: 13 additions & 6 deletions packages/vkui-docs-theme/src/blog/components/PostHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
'use client';

import { Icon12ChevronLeft } from '@vkontakte/icons';
import { Button } from '@vkontakte/vkui';
import { useRouter } from 'next/router';
import { useConfig } from '../../contexts';
import { useRouter } from 'next/navigation';
import { getTags } from '../tags';
import { PostHeading, PostMeta } from './Post';

export function PostHeader() {
const config = useConfig();
const { frontMatter } = config;
interface PostHeaderProps {
frontMatter: {
tags?: string;
date?: string;
title?: string;
};
}

export function PostHeader({ frontMatter }: PostHeaderProps) {
const router = useRouter();
const tags = getTags(frontMatter);
const tags = getTags(frontMatter.tags);

const back = () => {
void router.push('/blog');
Expand Down
4 changes: 2 additions & 2 deletions packages/vkui-docs-theme/src/blog/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MdxFile, PageMapItem, PageOpts } from 'nextra';
import type { MdxFile, PageMapItem } from 'nextra';

const sortPosts = (a: MdxFile, b: MdxFile): number => {
if (!a.frontMatter?.date || !b.frontMatter?.date) {
Expand All @@ -16,7 +16,7 @@ export const isPost = (page: PageMapItem): page is MdxFile => {
return false;
};

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

for (const item of pageMap) {
Expand Down
1 change: 1 addition & 0 deletions packages/vkui-docs-theme/src/blog/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { PostsLayout } from './PostsLayout';
export { PostHeader } from './components/PostHeader';
export { getTags } from './tags';
26 changes: 4 additions & 22 deletions packages/vkui-docs-theme/src/blog/tags.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
import type { GetStaticPaths } from 'next';
import Head from 'next/head';
import type { FrontMatter, PageMapItem } from 'nextra';
import { useData } from 'nextra/hooks';
import type { PageMapItem } from 'nextra';
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) {
export function getTags(tags?: string[] | string) {
if (!tags) {
return [];
}
const tags: string | string[] = frontMatter.tag || [];
return (Array.isArray(tags) ? tags : tags.split(',')).map((s) => s.trim());
}

Expand All @@ -36,7 +18,7 @@ const getStaticTags = (pageMap: PageMapItem[]) => {
if ('children' in item && item.name === 'blog') {
for (const pageMapItem of item.children) {
if (isPost(pageMapItem)) {
tags.push(...getTags(pageMapItem.frontMatter));
tags.push(...getTags(pageMapItem.frontMatter?.tags));
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vkui-docs-theme/src/components/Anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import * as React from 'react';
import { type LinkProps, Link as VKUILink } from '@vkontakte/vkui';
import NextLink from 'next/link';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
--vkui--color_text_link: var(--vkui--color_text_secondary);
}

.icon {
color: var(--vkui--color_icon_secondary);
.item:last-child {
color: var(--vkui--color_text_primary);
}

.activeItem {
color: var(--vkui--color_text_primary);
.icon {
color: var(--vkui--color_icon_secondary);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Fragment } from 'react';
'use client';

import * as React from 'react';
import { Icon12ChevronOutline } from '@vkontakte/icons';
import { classNames, Footnote } from '@vkontakte/vkui';
import type { Item } from 'nextra/normalize-pages';
Expand All @@ -8,17 +10,17 @@ import styles from './Breadcrumbs.module.css';
export function Breadcrumbs({ activePath }: { activePath: Item[] }) {
return (
<div className={styles.root}>
{activePath.map((item, index) => {
const isLink = !item.children || item.withIndexPage;
const isActive = index === activePath.length - 1;
{activePath.map((item, index, arr) => {
const nextItem = arr[index + 1];
const href = nextItem ? (item.frontMatter ? item.route : '') : '';

return (
<Fragment key={`${item.route}-${item.name}`}>
<React.Fragment key={`${item.route}-${item.name}`}>
{index > 0 && <Icon12ChevronOutline aria-hidden className={classNames(styles.icon)} />}
<Footnote className={classNames(styles.item, isActive && styles.activeItem)}>
{isLink && !isActive ? <Anchor href={item.route}>{item.title}</Anchor> : item.title}
<Footnote className={styles.item}>
{href ? <Anchor href={href}>{item.title}</Anchor> : item.title}
</Footnote>
</Fragment>
</React.Fragment>
);
})}
</div>
Expand Down
22 changes: 11 additions & 11 deletions packages/vkui-docs-theme/src/components/ColorSchemeSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { Icon20MoonOutline, Icon20SunOutline } from '@vkontakte/icons';
import { AdaptivityProvider, SegmentedControl, Skeleton } from '@vkontakte/vkui';
import { SegmentedControl, Skeleton } from '@vkontakte/vkui';
import { useMounted } from 'nextra/hooks';
import { useColorScheme } from '../contexts';

Expand All @@ -19,15 +21,13 @@ export function ColorSchemeSwitch() {
}

return (
<AdaptivityProvider sizeY="compact">
<SegmentedControl
size="l"
style={{ width: SWITCH_WIDTH }}
value={resolvedColorScheme}
// @ts-expect-error: TS2322 VKUI fix types?
onChange={setColorScheme}
options={options}
/>
</AdaptivityProvider>
<SegmentedControl
size="l"
style={{ width: SWITCH_WIDTH }}
value={resolvedColorScheme}
// @ts-expect-error: TS2322 VKUI fix types?
onChange={setColorScheme}
options={options}
/>
);
}
30 changes: 8 additions & 22 deletions packages/vkui-docs-theme/src/components/Head.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import NextHead from 'next/head';
import { useMounted } from 'nextra/hooks';
import { useColorScheme, useThemeConfig } from '../contexts';

export function Head() {
const themeConfig = useThemeConfig();
const { resolvedColorScheme } = useColorScheme();
const mounted = useMounted();

const ThemeConfigHead = themeConfig.head;
const head = typeof ThemeConfigHead === 'function' ? <ThemeConfigHead /> : ThemeConfigHead;
import * as React from 'react';

export function Head({ children }: React.PropsWithChildren) {
return (
<NextHead>
{mounted ? (
<meta name="theme-color" content={resolvedColorScheme === 'dark' ? '#111' : '#fff'} />
) : (
<>
<meta name="theme-color" content="#fff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#111" media="(prefers-color-scheme: dark)" />
</>
)}
<head>
{children}
<meta name="theme-color" content="#fff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#111" media="(prefers-color-scheme: dark)" />

<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
{head}
</NextHead>
</head>
);
}
Loading

0 comments on commit bd05a01

Please sign in to comment.