From ab72c06fd503d217754ac78e6f74e0ce9511b83b Mon Sep 17 00:00:00 2001 From: "S.Gromov" Date: Wed, 13 May 2026 16:23:08 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B8=20fallback=20=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - подключён рендер task-list чекбоксов в VitePress - добавлена общая навигация к списку документаций и репозиторию - синхронизирована тема главной страницы и VitePress - добавлен HTML-каркас главной страницы для клиентов без JS - обновлено авторство документации в футере --- .../.vitepress/config.ts | 8 + .../.vitepress/theme/index.ts | 1 + docs/shared/vitepress/HomeLink.vue | 174 ++++++++++++++++++ docs/shared/vitepress/theme.ts | 15 ++ docs/shared/vitepress/themeHead.ts | 18 ++ docs/slm-design/.vitepress/config.ts | 8 + docs/slm-design/.vitepress/theme/index.ts | 1 + index.html | 164 ++++++++++++++++- package-lock.json | 8 + package.json | 1 + src/App.css | 9 + src/App.tsx | 62 +++++-- 12 files changed, 451 insertions(+), 18 deletions(-) create mode 100644 docs/figma-adaptive-standards/.vitepress/theme/index.ts create mode 100644 docs/shared/vitepress/HomeLink.vue create mode 100644 docs/shared/vitepress/theme.ts create mode 100644 docs/shared/vitepress/themeHead.ts create mode 100644 docs/slm-design/.vitepress/theme/index.ts diff --git a/docs/figma-adaptive-standards/.vitepress/config.ts b/docs/figma-adaptive-standards/.vitepress/config.ts index 55477f3..1075be6 100644 --- a/docs/figma-adaptive-standards/.vitepress/config.ts +++ b/docs/figma-adaptive-standards/.vitepress/config.ts @@ -1,5 +1,7 @@ import { defineConfig } from 'vitepress'; +import taskLists from 'markdown-it-task-lists'; import llmstxt from 'vitepress-plugin-llms'; +import { themeSyncHead } from '../../shared/vitepress/themeHead'; import { sidebar, site } from '../docs.config'; export default defineConfig({ @@ -9,9 +11,15 @@ export default defineConfig({ outDir: site.outDir, srcDir: 'content', cleanUrls: true, + head: [...themeSyncHead], vite: { plugins: [llmstxt()], }, + markdown: { + config(md) { + md.use(taskLists); + }, + }, themeConfig: { sidebar, socialLinks: [], diff --git a/docs/figma-adaptive-standards/.vitepress/theme/index.ts b/docs/figma-adaptive-standards/.vitepress/theme/index.ts new file mode 100644 index 0000000..e0013e9 --- /dev/null +++ b/docs/figma-adaptive-standards/.vitepress/theme/index.ts @@ -0,0 +1 @@ +export { default } from '../../../shared/vitepress/theme'; diff --git a/docs/shared/vitepress/HomeLink.vue b/docs/shared/vitepress/HomeLink.vue new file mode 100644 index 0000000..8c60463 --- /dev/null +++ b/docs/shared/vitepress/HomeLink.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/docs/shared/vitepress/theme.ts b/docs/shared/vitepress/theme.ts new file mode 100644 index 0000000..751bd31 --- /dev/null +++ b/docs/shared/vitepress/theme.ts @@ -0,0 +1,15 @@ +import { h } from 'vue'; +import DefaultTheme from 'vitepress/theme'; +import type { Theme } from 'vitepress'; +import HomeLink from './HomeLink.vue'; + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + 'nav-bar-content-before': () => h(HomeLink, { variant: 'back' }), + 'nav-bar-content-after': () => h(HomeLink, { variant: 'repo' }), + 'nav-screen-content-before': () => h(HomeLink, { variant: 'screen' }), + }); + }, +} satisfies Theme; diff --git a/docs/shared/vitepress/themeHead.ts b/docs/shared/vitepress/themeHead.ts new file mode 100644 index 0000000..02239a2 --- /dev/null +++ b/docs/shared/vitepress/themeHead.ts @@ -0,0 +1,18 @@ +import type { HeadConfig } from 'vitepress'; + +export const themeSyncHead: HeadConfig[] = [ + [ + 'script', + { id: 'sync-docs-theme' }, + `;(() => { + const theme = localStorage.getItem('vitepress-theme-appearance') + if (theme) return + + const legacyTheme = localStorage.getItem('all-docs-theme') + if (!legacyTheme) return + + localStorage.setItem('vitepress-theme-appearance', legacyTheme === 'system' ? 'auto' : legacyTheme) + localStorage.removeItem('all-docs-theme') + })()`, + ], +]; diff --git a/docs/slm-design/.vitepress/config.ts b/docs/slm-design/.vitepress/config.ts index 55477f3..1075be6 100644 --- a/docs/slm-design/.vitepress/config.ts +++ b/docs/slm-design/.vitepress/config.ts @@ -1,5 +1,7 @@ import { defineConfig } from 'vitepress'; +import taskLists from 'markdown-it-task-lists'; import llmstxt from 'vitepress-plugin-llms'; +import { themeSyncHead } from '../../shared/vitepress/themeHead'; import { sidebar, site } from '../docs.config'; export default defineConfig({ @@ -9,9 +11,15 @@ export default defineConfig({ outDir: site.outDir, srcDir: 'content', cleanUrls: true, + head: [...themeSyncHead], vite: { plugins: [llmstxt()], }, + markdown: { + config(md) { + md.use(taskLists); + }, + }, themeConfig: { sidebar, socialLinks: [], diff --git a/docs/slm-design/.vitepress/theme/index.ts b/docs/slm-design/.vitepress/theme/index.ts new file mode 100644 index 0000000..e0013e9 --- /dev/null +++ b/docs/slm-design/.vitepress/theme/index.ts @@ -0,0 +1 @@ +export { default } from '../../../shared/vitepress/theme'; diff --git a/index.html b/index.html index 6309299..141be25 100644 --- a/index.html +++ b/index.html @@ -5,10 +5,172 @@ + + Документация -
+
+
+
+

Документация

+

+ Единое пространство для идей, черновиков и первых версий документаций, + которые ещё формируются и постепенно становятся самостоятельными материалами. +

+ +
+ +
+

Список документаций

+
    +
  • +
    +
    Архитектура · Доступно
    +

    SLM Design

    +

    + Архитектура frontend-приложений, где слои задают направление зависимостей, + модули становятся границами ответственности, а явный DI через фабрики удерживает домены изолированными и предсказуемыми. +

    + +
    +
  • + +
  • +
    +
    Стиль проекта · Скоро
    +

    NextJS Style Guide

    +

    + Правила организации Next.js-приложений, роутинга, серверных границ и проектных соглашений. +

    +
    +
  • + +
  • +
    +
    Стиль кода · Скоро
    +

    React Style Guide

    +

    + Практики написания React-компонентов, хуков, состояния и клиентского UI-кода. +

    +
    +
  • + +
  • +
    +
    Макеты · Доступно
    +

    Figma Adaptive Standards

    +

    + Стандарты и требования к подготовке адаптивных макетов в Figma: брейкпоинты, + ресайз в диапазоне, Auto Layout/Constraints, компоненты, сетка, типографика, состояния UI, A11y и передача в разработку. +

    + +
    +
  • +
+
+ + +
+
diff --git a/package-lock.json b/package-lock.json index 242c115..11ff7e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", + "markdown-it-task-lists": "^2.1.1", "tsx": "^4.21.0", "typescript": "~5.8.3", "typescript-eslint": "^8.59.2", @@ -3862,6 +3863,13 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true, + "license": "ISC" + }, "node_modules/markdown-it/node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", diff --git a/package.json b/package.json index 1fb318d..1eb3133 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.6.0", + "markdown-it-task-lists": "^2.1.1", "tsx": "^4.21.0", "typescript": "~5.8.3", "typescript-eslint": "^8.59.2", diff --git a/src/App.css b/src/App.css index 5fc404c..9a82476 100644 --- a/src/App.css +++ b/src/App.css @@ -378,6 +378,15 @@ text-align: center; } +.footer a { + color: var(--text-secondary); + text-decoration: none; +} + +.footer a:hover { + color: var(--accent-cool); +} + @media (max-width: 768px) { .page { justify-content: flex-start; diff --git a/src/App.tsx b/src/App.tsx index 51d8632..69c99a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,15 +3,17 @@ import { useEffect, useLayoutEffect, useState } from 'react' import { docs } from './config/docs.config' import './App.css' -type ThemeMode = 'system' | 'dark' | 'light' -type ResolvedTheme = Exclude +type ThemeMode = 'auto' | 'dark' | 'light' +type ResolvedTheme = Exclude const DARK_THEME_QUERY = '(prefers-color-scheme: dark)' -const THEME_STORAGE_KEY = 'all-docs-theme' +const THEME_STORAGE_KEY = 'vitepress-theme-appearance' +const LEGACY_THEME_STORAGE_KEY = 'all-docs-theme' const repositoryUrl = 'https://gromlab.ru/gromov/docs' +const authorUrl = 'https://gromlab.ru/gromov' const themeOptions: ReadonlyArray<{ - value: Exclude + value: Exclude ariaLabel: string title: string }> = [ @@ -55,7 +57,7 @@ const themeIconByMode = { const canUseDom = () => typeof window !== 'undefined' && typeof document !== 'undefined' const isThemeMode = (value: string | null): value is ThemeMode => { - return value === 'system' || value === 'dark' || value === 'light' + return value === 'auto' || value === 'dark' || value === 'light' } const getSystemTheme = (): ResolvedTheme => { @@ -65,15 +67,20 @@ const getSystemTheme = (): ResolvedTheme => { } const resolveTheme = (theme: ThemeMode): ResolvedTheme => { - return theme === 'system' ? getSystemTheme() : theme + return theme === 'auto' ? getSystemTheme() : theme } const getStoredTheme = (): ThemeMode => { - if (!canUseDom()) return 'dark' + if (!canUseDom()) return 'auto' const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY) + const legacyTheme = window.localStorage.getItem(LEGACY_THEME_STORAGE_KEY) - return isThemeMode(storedTheme) ? storedTheme : 'dark' + if (isThemeMode(storedTheme)) return storedTheme + if (legacyTheme === 'system') return 'auto' + if (isThemeMode(legacyTheme)) return legacyTheme + + return 'auto' } const applyTheme = (theme: ThemeMode) => { @@ -82,14 +89,11 @@ const applyTheme = (theme: ThemeMode) => { const resolvedTheme = resolveTheme(theme) document.documentElement.dataset.theme = theme + document.documentElement.classList.toggle('dark', resolvedTheme === 'dark') document.documentElement.style.colorScheme = resolvedTheme - if (theme === 'system') { - window.localStorage.removeItem(THEME_STORAGE_KEY) - return - } - window.localStorage.setItem(THEME_STORAGE_KEY, theme) + window.localStorage.removeItem(LEGACY_THEME_STORAGE_KEY) } function useTheme() { @@ -106,11 +110,12 @@ function useTheme() { const mediaQuery = window.matchMedia(DARK_THEME_QUERY) const handleSystemThemeChange = () => { - if (theme !== 'system') return + if (theme !== 'auto') return const nextResolvedTheme = resolveTheme(theme) document.documentElement.style.colorScheme = nextResolvedTheme + document.documentElement.classList.toggle('dark', nextResolvedTheme === 'dark') setResolvedTheme(nextResolvedTheme) } @@ -119,13 +124,34 @@ function useTheme() { return () => mediaQuery.removeEventListener('change', handleSystemThemeChange) }, [theme]) + useEffect(() => { + if (!canUseDom()) return undefined + + const syncStoredTheme = () => { + setTheme(getStoredTheme()) + } + const handleStorageChange = (event: StorageEvent) => { + if (event.key === THEME_STORAGE_KEY || event.key === LEGACY_THEME_STORAGE_KEY) { + syncStoredTheme() + } + } + + window.addEventListener('storage', handleStorageChange) + window.addEventListener('focus', syncStoredTheme) + + return () => { + window.removeEventListener('storage', handleStorageChange) + window.removeEventListener('focus', syncStoredTheme) + } + }, []) + return { theme, resolvedTheme, setTheme } } function ThemeToggle() { const { theme, resolvedTheme, setTheme } = useTheme() - const toggleTheme = (value: Exclude) => { - setTheme(theme === value ? 'system' : value) + const toggleTheme = (value: Exclude) => { + setTheme(theme === value ? 'auto' : value) } return ( @@ -282,7 +308,9 @@ function App() { })} -
Рабочее пространство документаций
+ ) }