From 4e961d0cc52487a813d3afaa3b4df32ca20c214d Mon Sep 17 00:00:00 2001 From: NikolaiKushner Date: Thu, 8 Oct 2026 21:44:45 +0300 Subject: [PATCH 1/6] =?UTF-8?q?Brief:=20rowkit.dev=202.0=20=E2=80=94=20a?= =?UTF-8?q?=20landing=20page=20and=20a=20docs=20layout,=20in=20both=20them?= =?UTF-8?q?es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- design/briefs/site.md | 123 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 design/briefs/site.md diff --git a/design/briefs/site.md b/design/briefs/site.md new file mode 100644 index 00000000..32882844 --- /dev/null +++ b/design/briefs/site.md @@ -0,0 +1,123 @@ +# rowkit.dev 2.0 — бриф на сайт + +8 октября 2026 · бриф для дизайнера. Файл: [rowkit × Windows 98](https://www.figma.com/design/hmDfFpjrDP6WtEary6U6SE/rowkit-%C3%97-Windows-98), новая страница «Site v2». Бренд — страница «Brand» (гайд 173:35331). + +## Зачем + +Сейчас rowkit.dev — эмуляция операционной системы: рабочий стол, Dock, «Пуск», каждая страница в окне Explorer или Finder. Это мило один раз и мешает каждый следующий: + +- главная не говорит, что это и почему это стоит взять — она играет в ОС; +- меню слева набрано мелко (13px), текст зажат рамками окна, на телефоне окна тесные; +- переключатель тем — мелкий текст в панели задач, его не находят. + +Нужен **сайт продукта**: красивый лендинг и удобная документация. Две темы остаются главным аргументом, но как витрина, а не как костюм: один и тот же сайт выглядит как Windows 98 или как modern, и это видно с первого экрана. + +## Аудитория и что она должна понять + +Фронтенд-разработчик или тимлид, который выбирает набор компонентов для продуктового интерфейса на Vue 3 (админка, внутренний инструмент, SaaS). За 10 секунд на главной он должен понять: + +1. **Что это:** профессиональный набор компонентов для продуктовых интерфейсов на Vue 3 и TypeScript. +2. **Чем отличается:** один набор — любой вид. Тема — это только токены; Windows 98 и modern в комплекте, свою можно сделать одним вызовом. +3. **Что он серьёзный:** типы, доступность с тестами, своё поведение без сторонних библиотек, SSR, лимит размера, инструкции для ИИ-агентов в пакете. +4. **Что он растёт:** 21 компонент сегодня, план на остальное — меню, даты, палитра команд и т. д. +5. **Как начать:** `npm i rowkit@beta`, две строки CSS. + +Тон: точный, спокойный, уверенный, с лёгкой игрой — игра в переключении тем, а не в декорациях. + +## Решения, которые уже приняты + +- **Весь сайт** — лендинг и документация — в обычном каркасе: шапка, меню слева, текст по центру, оглавление справа. Рабочий стол, Dock, «Пуск», панель задач, окна вокруг страниц уходят. +- **Windows 98 — «шкурка» того же каркаса**, а не другая раскладка: серая грань, фаски, PT Sans, синие выделения, пиксельные иконки. Читается как сайт, узнаётся как Windows 98. +- **Сайт открывается в modern**, Windows 98 — переключателем или `?theme=win98`. Выбор запоминается. +- **Бренд над темами:** знак, вордмарк, vermilion в шапке и подвале одинаковы в обеих темах (в Win98 — пиксельное прочтение знака 16/32). + +## Шрифты и размеры (обе темы) + +Читать должно быть удобно в первую очередь. Ориентир — modern; Win98 повторяет размеры в PT Sans. + +| Роль | Размер / строка | Примечание | +| ------------------------ | --------------- | ----------------------------------------- | +| Заголовок лендинга | 48–56 / 1.1 | на телефоне 34–36 | +| Подзаголовок лендинга | 20 / 30 | | +| H1 страницы документации | 32 / 40 | | +| H2 | 24 / 32 | | +| H3 | 18 / 26 | | +| Основной текст | 16 / 26 | колонка 680–720px, ~70 знаков | +| Меню слева | 15 / 22 | разделы — 13px заглавными или полужирным | +| «На этой странице» | 14 / 20 | | +| Код | 14 / 22 | моно; в Win98 — VT323 крупнее, как сейчас | + +Цели касания на телефоне ≥ 44px. Контраст текста ≥ 4.5:1 в обеих темах и обеих схемах. + +## Каркас документации + +**Шапка (64px; телефон 56px).** Логотип · Docs · Components · Patterns · Storybook · GitHub · поиск (⌘K) · **переключатель тем** · версия. Переключатель крупный и подписанный: сегменты «Windows 98 | Modern» и кнопка схемы Auto/Light/Dark. В Win98 — как переключатель на панели инструментов (вдавленная кнопка у выбранной), в modern — сегментированный контрол. + +**Меню слева (260px).** Разделы: Guide, Components (Foundations / Forms / Data / Overlays), Patterns, Foundations · Tokens, Decisions, Roadmap, Contributing. Все разделы раскрыты; текущая страница подсвечена; меню прокручивается отдельно от страницы. На телефоне — выезжающая панель из кнопки в шапке. + +**Содержимое (до 720px).** Хлебные крошки → H1 → вводный абзац (крупнее, 18/28) → разделы. Примеры: живой пример над кодом, код со строкой «Copy». Таблицы props — как сейчас (генерируются), но в ширину колонки без горизонтального скролла. Внизу: «Previous / Next», «Edit on GitHub», дата. + +**Справа (220px, ≥1280px).** «On this page» с подсветкой текущего раздела. + +**Поиск.** Одна палитра поиска по ⌘K в обеих темах (сейчас Spotlight в modern, окно Find в Win98): поле, недавние, результаты по группам, «ничего не найдено». В Win98 — диалог с фаской, в modern — карточка со стеклом. + +**404.** Обычная страница в каркасе: «Page not found», адрес, «Go to Home», поиск. + +## Лендинг + +Ширина контента 1200px, секции с воздухом (96–128px между ними, на телефоне 64). Сверху вниз: + +1. **Первый экран.** Слева: заголовок «Vue 3 components for product interfaces», подзаголовок («A professional toolkit — typed, accessible, themeable. Windows 98 and modern in the box, your own in one call.»), команда установки с кнопкой копирования, кнопки «Get started» и «Browse components». Справа: **живое демо** — таблица пользователей с фильтром, выбором и пагинацией в текущей теме (тот же код, что сейчас в «Live demo»). Над демо или под ним — крупный переключатель «Windows 98 | Modern»: клик перекрашивает весь сайт, и это первое, что хочется нажать. +2. **«One set, any look».** Одна и та же форма «New user» дважды рядом — в Windows 98 и в modern (`data-theme` на контейнере, вживую). Ниже — пример `defineTheme` из `rowkit/theme` и третья, «своя» тема рядом (например, фиолетовая): видно, что тема — это значения, а не компоненты. +3. **Что внутри.** Сетка компонентов по разделам (Foundations, Forms, Data, Overlays): у каждого — маленькое живое превью в текущей теме, имя, одна строка «для чего». Отдельной строкой — «Coming next»: меню, Popover, Sheet, даты, палитра команд, Tabs, Switch, Combobox — серыми карточками с пометкой «Planned», ссылка на Roadmap. Это и есть «их будет много». +4. **Почему это профессиональные компоненты.** 6 карточек: + - **Typed** — дженерики в `DataTable` и `Select`, строгий TypeScript, без `any`; + - **Accessible** — WAI-ARIA, клавиатура, a11y-проверка в CI на каждой истории; + - **Own behaviour layer** — фокус, слои, позиционирование без сторонних библиотек; + - **Tokens all the way down** — `@rowkit/tokens`, 313 токенов с описаниями; + - **SSR и Nuxt** — работает без настройки; + - **Ready for coding agents** — `AGENTS.md` в пакете. + Плюс строка цифр: тесты (~1500), размер (39 kB вся библиотека, 2.6 kB одна кнопка), 21 компонент. +5. **Пример кода.** «A data table page in 20 lines»: код слева, результат справа (FilterBar + DataTable + Pagination). +6. **Начать.** Три шага: установить, подключить стили, выбрать тему — и кнопка «Read the guide». +7. **Подвал.** Логотип, слоган, ссылки (Docs, Components, Roadmap, GitHub, npm, Storybook), MIT, «Built with rowkit». + +## Два стиля сайта + +| Часть | Windows 98 | Modern (light / dark) | +| ------------------ | ------------------------------------------------------------------- | --------------------------------------------------------- | +| Фон страницы | серый `#C0C0C0` для рамок; белый под текстом | `background` темы; секции лендинга — едва заметные полосы | +| Шапка | панель инструментов с фаской снизу, кнопки-иконки | полупрозрачная, размытие, линия снизу | +| Меню слева | белая колодезная панель (sunken), синее выделение, пиксельные папки | без рамки, скруглённое серое выделение, контурные иконки | +| Карточки | GroupBox с подписью на рамке или raised-панель | скруглённые карточки с тенью | +| Кнопки, поля | компоненты rowkit в теме — без своих стилей сайта | так же | +| Заголовок лендинга | PT Sans Bold, чёрный; акцент — синий `#000080` | системный шрифт, semibold; акцент — синий темы | +| Ссылки | синие, подчёркнутые | синие, подчёркивание при наведении | + +Вся «шкурка» — токены темы. Свои цвета сайта — только бренд (знак, vermilion) и подсветка кода `site/code-*`. + +## Телефон (390) + +Шапка: логотип, поиск, переключатель (сжатый: «98 | M» или иконка с меню), кнопка меню. Лендинг в одну колонку: демо под заголовком, компактная таблица (Name / Status), сетка компонентов в 2 колонки. Документация: меню и оглавление — выезжающие панели. + +## Что нарисовать + +Страница «Site v2» в Figma: + +- [ ] Компоненты сайта: Header, ThemeSwitch (обе темы, все состояния), SideNav и SideNavItem, Toc, Breadcrumbs, PrevNext, CodeBlock (есть — сверить), FeatureCard, ComponentCard (+ Planned), StatsRow, Footer, SearchPalette (3 состояния), Callout (есть). +- [ ] Лендинг: 1440 и 390 × Windows 98, Modern Light, Modern Dark. +- [ ] Шаблоны документации: страница компонента, статья (Guide), список (Components) — 1440 и 390 × те же три вида. +- [ ] 404 и поиск — 1440, три вида. +- [ ] Лист «Site v2 — decisions»: шкала шрифтов, сетка, отступы, что из старого сайта ушло. + +## Что сохраняется из кода + +Живое демо таблицы, «Side by side», конструктор тем, страница Tokens с таблицей, таблицы props, подсветка кода, индекс поиска, данные компонентов. Уходят: `DesktopHome`, `SiteDock`, `StartMenu`, `SiteTaskbar`, `SiteWallpaper`, `ExplorerLayout` (окна), `FindWindow` и `SpotlightSearch` (заменяет одна палитра), `SiteAddressBar`. + +## Готово, когда + +- главная за 10 секунд отвечает на пять вопросов из «Аудитории»; +- основной текст ≥ 16px, меню ≥ 15px, нигде нет горизонтального скролла на 390–1440; +- переключатель тем виден на любой странице без поиска глазами; +- обе темы и обе схемы проходят контраст AA; Lighthouse Accessibility ≥ 95; +- Windows 98 узнаётся с первого взгляда, но страница читается как обычный сайт. From 7ddc9f30d6cc9ce26ddc51edd01cfcc5581c8d5f Mon Sep 17 00:00:00 2001 From: NikolaiKushner Date: Fri, 9 Oct 2026 00:21:02 +0300 Subject: [PATCH 2/6] =?UTF-8?q?Brief:=20review=20of=209=20October=20?= =?UTF-8?q?=E2=80=94=20VitePress=20docs,=20a=20branded=20modern=20landing,?= =?UTF-8?q?=20lighter=20icons.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- design/briefs/site.md | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/design/briefs/site.md b/design/briefs/site.md index 32882844..aca4858c 100644 --- a/design/briefs/site.md +++ b/design/briefs/site.md @@ -27,8 +27,11 @@ ## Решения, которые уже приняты - **Весь сайт** — лендинг и документация — в обычном каркасе: шапка, меню слева, текст по центру, оглавление справа. Рабочий стол, Dock, «Пуск», панель задач, окна вокруг страниц уходят. -- **Windows 98 — «шкурка» того же каркаса**, а не другая раскладка: серая грань, фаски, PT Sans, синие выделения, пиксельные иконки. Читается как сайт, узнаётся как Windows 98. +- **Лендинг — в двух стилях.** Windows 98 — «шкурка» того же лендинга, а не другая раскладка: серая грань, фаски, PT Sans, синие выделения, пиксельные иконки. +- **Документация — стандартная тема VitePress с брендом** (вариант B, решение 9 октября; вариант A — свой каркас в двух шкурках — остаётся в Figma как отклонённый). Нейтрально и привычно; брендинг — логотип, цвет vermilion на ссылках, активном пункте меню и маркерах, врезка TIP в цвете бренда, меню 15px вместо 14. Светлая и тёмная схема — штатный переключатель VitePress. +- **Переключатель «Windows 98 | Modern» в документации** стоит в шапке VitePress («Components: Windows 98 | Modern») и меняет только живые примеры компонентов, а не оболочку страницы. - **Сайт открывается в modern**, Windows 98 — переключателем или `?theme=win98`. Выбор запоминается. +- **Modern-лендинг брендирован** — это вид по умолчанию: бумажный фон `#FAFAF8` (в тёмной — `night`), слова «product interfaces» в заголовке, метка над ним, главные кнопки, ссылки со стрелкой, подписи секций (THEMES, COMPONENTS, WHY ROWKIT…), номера шагов и плашки иконок — в vermilion; подвал — чернила `#111114` с белым логотипом. Живые компоненты внутри (демо, превью) — чистый modern, без цвета бренда. Win98-лендинг брендом не красится. - **Бренд над темами:** знак, вордмарк, vermilion в шапке и подвале одинаковы в обеих темах (в Win98 — пиксельное прочтение знака 16/32). ## Шрифты и размеры (обе темы) @@ -51,6 +54,8 @@ ## Каркас документации +> С 9 октября документация — стандартная тема VitePress с брендом (см. «Решения»). Описание ниже — вариант A, оставлено для истории; для варианта B действуют: шапка VitePress + переключатель компонентов, меню 15px, контент 688px, оглавление справа, «Previous page / Next page», TIP-врезка цветом бренда, код с подсветкой (github-light / github-dark), живые примеры в рамке. + **Шапка (64px; телефон 56px).** Логотип · Docs · Components · Patterns · Storybook · GitHub · поиск (⌘K) · **переключатель тем** · версия. Переключатель крупный и подписанный: сегменты «Windows 98 | Modern» и кнопка схемы Auto/Light/Dark. В Win98 — как переключатель на панели инструментов (вдавленная кнопка у выбранной), в modern — сегментированный контрол. **Меню слева (260px).** Разделы: Guide, Components (Foundations / Forms / Data / Overlays), Patterns, Foundations · Tokens, Decisions, Roadmap, Contributing. Все разделы раскрыты; текущая страница подсвечена; меню прокручивается отдельно от страницы. На телефоне — выезжающая панель из кнопки в шапке. @@ -102,17 +107,35 @@ ## Что нарисовать -Страница «Site v2» в Figma: +Страница «Site v2» в Figma (254:9584): + +- [x] Компоненты сайта: Header, HeaderMobile, ThemeSwitch (win98 / modern), Search, NavItem, SideNav, Toc, FeatureCard, ComponentCard, Footer, SearchPalette; иконки «Site icons» (контурные 24 и пиксельные 16/32). +- [x] Лендинг: 1440 и 390 × Windows 98, Modern Light, Modern Dark. +- [x] Вариант A документации (отклонён): страница компонента, статья, список, поиск, 404 — 1440 и 390 × три вида. +- [x] Вариант B документации: стандартная тема VitePress с брендом — страница компонента, светлая и тёмная. +- [x] Лист «Site v2 — decisions». +- [ ] Вариант B: телефон 390 и страница-статья — по необходимости, VitePress задаёт их сам. + +## Правки после ревью (9 октября) + +- [x] Карточки в одном ряду — одной высоты (Start in a minute, Built like a professional tool, сетки компонентов). +- [x] Рамка карточек: в modern — тонкая обводка 1px (`border-subtle`) без тени; в Win98 — фаска без обводки. Код в шагах — на своей подложке. +- [x] Свои иконки для «Built like a professional tool»: скобки, доступность, слои, образцы цвета, сервер, искра — контурные в modern, пиксельные 32px в Win98. +- [x] Подсветка синтаксиса во всех блоках кода лендинга (`site/code-*`: keyword, string, tag, attr, comment, number). +- [x] Иконки modern-темы тоньше: линия 2.25 → 1.75 (на сетке 24, то есть ≈1.2px в 16px), глифы 3 → 2.5, 32-px 1.5 → 1.25. **Это изменение библиотеки:** выгрузить SVG из Figma в `packages/ui/src/icons/modern/`, `pnpm icons:modern`, changeset (`rowkit` minor — «modern icons draw a lighter line»), проверить кадры Storybook в modern. +- [x] Брендирование modern-лендинга vermilion (см. «Решения»). +- [x] Документация → стандартная тема VitePress с брендом (см. «Решения»). + +## План вёрстки -- [ ] Компоненты сайта: Header, ThemeSwitch (обе темы, все состояния), SideNav и SideNavItem, Toc, Breadcrumbs, PrevNext, CodeBlock (есть — сверить), FeatureCard, ComponentCard (+ Planned), StatsRow, Footer, SearchPalette (3 состояния), Callout (есть). -- [ ] Лендинг: 1440 и 390 × Windows 98, Modern Light, Modern Dark. -- [ ] Шаблоны документации: страница компонента, статья (Guide), список (Components) — 1440 и 390 × те же три вида. -- [ ] 404 и поиск — 1440, три вида. -- [ ] Лист «Site v2 — decisions»: шкала шрифтов, сетка, отступы, что из старого сайта ушло. +1. **Иконки** — выгрузка и changeset (отдельный PR, библиотека). +2. **Документация** — вернуть стандартную тему VitePress: убрать `SiteLayout`/`ExplorerLayout`, рабочий стол и окна; бренд через `--vp-c-brand-*`, логотип, меню 15px; переключатель «Components: Windows 98 | Modern» в `nav-bar-content-after`, ставит `data-theme` на живые примеры (`DemoBox`), не на страницу; поиск — штатный local search; 404 — штатный с брендом. +3. **Лендинг** — `layout: home` со своим компонентом: секции по макету, живое демо и форма «New user» из кода, `data-theme` на корне лендинга по переключателю, бренд-цвета сайта. +4. **Проверка** — скриншоты 390/1440 в обеих схемах и в Win98-лендинге, контраст, Lighthouse, нет горизонтального скролла. ## Что сохраняется из кода -Живое демо таблицы, «Side by side», конструктор тем, страница Tokens с таблицей, таблицы props, подсветка кода, индекс поиска, данные компонентов. Уходят: `DesktopHome`, `SiteDock`, `StartMenu`, `SiteTaskbar`, `SiteWallpaper`, `ExplorerLayout` (окна), `FindWindow` и `SpotlightSearch` (заменяет одна палитра), `SiteAddressBar`. +Живое демо таблицы, «Side by side», конструктор тем, страница Tokens с таблицей, таблицы props, подсветка кода, данные компонентов. Уходят: `DesktopHome`, `SiteDock`, `StartMenu`, `SiteTaskbar`, `SiteWallpaper`, `SiteMenuBar`, `ExplorerLayout` и `SiteLayout` (окна), `FindWindow` и `SpotlightSearch` (поиск — штатный VitePress), `SiteAddressBar`, `FolderTree` (меню — штатный сайдбар VitePress). ## Готово, когда From 43bc96a406c1b3ae0de5a1b9674fc59578476030 Mon Sep 17 00:00:00 2001 From: NikolaiKushner Date: Fri, 9 Oct 2026 11:26:55 +0300 Subject: [PATCH 3/6] Brief: what makes the site more useful, and the Figma clean-up. Co-Authored-By: Claude Opus 5.5 --- design/briefs/site.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/design/briefs/site.md b/design/briefs/site.md index aca4858c..2541231a 100644 --- a/design/briefs/site.md +++ b/design/briefs/site.md @@ -126,6 +126,19 @@ - [x] Брендирование modern-лендинга vermilion (см. «Решения»). - [x] Документация → стандартная тема VitePress с брендом (см. «Решения»). +## Правки «полезнее для людей» (9 октября) + +- [x] Под установкой — требования: «Vue 3.5+ · Tailwind CSS v4 · TypeScript · MIT». +- [x] Под живым демо — «Live — sort, select and filter this table.» и «View source →». +- [x] После сетки компонентов — кнопка «See all 21 components» (на телефоне была). +- [x] Новая секция «Before you install» (FAQ) перед «Start in a minute»: готово ли к продакшену, нужен ли Tailwind, какая тема по умолчанию (Windows 98 без атрибута — путает, раз сайт открывается в modern), Nuxt и SSR, размер, лицензия. +- [x] Документация: под вводным абзацем — строка импорта (`import { DataTable } from 'rowkit'`) и ссылки «Storybook ↗ · Source ↗ · Report an issue ↗». + +## Уборка в Figma (9 октября) + +- Удалены кадры варианта A документации (страница компонента, меню, статья, список, поиск, 404 — все виды) и компоненты, нужные только им: `SiteV2/NavItem`, `SideNav`, `Toc`. +- Страницы старого сайта переименованы в «Archive — Site v1 · Windows 98 desktop» и «Archive — Site v1 · Modern desktop» и перенесены в конец файла: код сайта до вёрстки 2.0 ещё на них. + ## План вёрстки 1. **Иконки** — выгрузка и changeset (отдельный PR, библиотека). From 8b2e3e79201a9c192ee7973cdfe16e93d88954a4 Mon Sep 17 00:00:00 2001 From: NikolaiKushner Date: Fri, 9 Oct 2026 11:51:51 +0300 Subject: [PATCH 4/6] Docs: VitePress's default theme with the brand on it. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 2 of rowkit.dev 2.0 (design/briefs/site.md, option B). The docs leave the desktop metaphor: no Explorer or Finder windows, taskbar, Dock, Start menu, Spotlight or own folder tree. What is left is VitePress's layout — nav bar, sidebar, content, outline, search, 404 — light and dark. - The brand: vermilion links, active item and outline marker, the logo (light and dark) instead of a title, a 15px sidebar; the code in VitePress's own highlighting. - «Examples in Windows 98 | Modern»: the rowkit theme the live examples are drawn in, in the nav bar from 1280px, above the content where the nav bar is full, in the phone menu. VitePress's dark mode turns the modern examples dark too. The site still starts in modern. - A demo sits on its theme's own surface (Windows 98's grey, modern's white or dark) in VitePress's frame, as its own stacking context so a table's frame and sticky header stay under the nav bar. - Nav: Guide, Components, Patterns, Themes, Storybook and a «1.0 beta» menu (changelog, roadmap, npm); Decisions stay in the sidebar. - The home page is VitePress's hero until the landing (step 3) replaces it. Co-Authored-By: Claude Opus 5.5 --- docs/.vitepress/config.ts | 57 +- docs/.vitepress/syntax.ts | 69 --- .../theme/components/ComponentIndex.vue | 3 +- docs/.vitepress/theme/components/DemoBox.vue | 19 +- .../theme/components/home/AboutWindow.vue | 87 --- .../theme/components/home/CommandPrompt.vue | 77 --- .../theme/components/home/DesktopHome.vue | 417 -------------- .../theme/components/home/DesktopIcon.vue | 33 -- .../theme/components/home/EditUserDialog.vue | 120 ---- .../theme/components/home/shortcuts.ts | 31 - .../theme/components/home/useHomeDemo.ts | 150 ----- .../theme/components/site/ComponentsLook.vue | 146 +++++ .../theme/components/site/ExplorerLayout.vue | 322 ----------- .../theme/components/site/FindWindow.vue | 235 -------- .../theme/components/site/FolderTree.vue | 229 -------- .../theme/components/site/NotFound.vue | 152 ----- .../theme/components/site/PageHeader.vue | 43 -- .../theme/components/site/PageOutline.vue | 117 ---- .../theme/components/site/SiteAddressBar.vue | 45 -- .../theme/components/site/SiteDock.vue | 132 ----- .../theme/components/site/SiteLayout.vue | 43 -- .../theme/components/site/SiteMenu.vue | 360 ------------ .../theme/components/site/SiteMenuBar.vue | 271 --------- .../theme/components/site/SiteShell.vue | 37 ++ .../theme/components/site/SiteTaskbar.vue | 206 ------- .../components/site/SiteToolbarButton.vue | 33 -- .../theme/components/site/SiteWallpaper.vue | 26 - .../theme/components/site/SpotlightSearch.vue | 271 --------- .../theme/components/site/StartMenu.vue | 284 ---------- .../theme/components/site/ThemeSwitch.vue | 204 ------- docs/.vitepress/theme/components/site/menu.ts | 126 ----- .../theme/components/site/useFind.ts | 12 - .../theme/components/site/useFindIndex.ts | 159 ------ .../theme/components/site/useSiteNav.ts | 87 +-- .../theme/components/site/useSiteTheme.ts | 60 +- docs/.vitepress/theme/content.css | 531 ------------------ docs/.vitepress/theme/docs.css | 178 ++++++ docs/.vitepress/theme/index.ts | 4 +- docs/.vitepress/theme/modern.css | 132 ----- docs/.vitepress/theme/tokens.css | 49 +- docs/index.md | 14 +- 41 files changed, 443 insertions(+), 5128 deletions(-) delete mode 100644 docs/.vitepress/syntax.ts delete mode 100644 docs/.vitepress/theme/components/home/AboutWindow.vue delete mode 100644 docs/.vitepress/theme/components/home/CommandPrompt.vue delete mode 100644 docs/.vitepress/theme/components/home/DesktopHome.vue delete mode 100644 docs/.vitepress/theme/components/home/DesktopIcon.vue delete mode 100644 docs/.vitepress/theme/components/home/EditUserDialog.vue delete mode 100644 docs/.vitepress/theme/components/home/shortcuts.ts delete mode 100644 docs/.vitepress/theme/components/home/useHomeDemo.ts create mode 100644 docs/.vitepress/theme/components/site/ComponentsLook.vue delete mode 100644 docs/.vitepress/theme/components/site/ExplorerLayout.vue delete mode 100644 docs/.vitepress/theme/components/site/FindWindow.vue delete mode 100644 docs/.vitepress/theme/components/site/FolderTree.vue delete mode 100644 docs/.vitepress/theme/components/site/NotFound.vue delete mode 100644 docs/.vitepress/theme/components/site/PageHeader.vue delete mode 100644 docs/.vitepress/theme/components/site/PageOutline.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteAddressBar.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteDock.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteLayout.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteMenu.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteMenuBar.vue create mode 100644 docs/.vitepress/theme/components/site/SiteShell.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteTaskbar.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteToolbarButton.vue delete mode 100644 docs/.vitepress/theme/components/site/SiteWallpaper.vue delete mode 100644 docs/.vitepress/theme/components/site/SpotlightSearch.vue delete mode 100644 docs/.vitepress/theme/components/site/StartMenu.vue delete mode 100644 docs/.vitepress/theme/components/site/ThemeSwitch.vue delete mode 100644 docs/.vitepress/theme/components/site/menu.ts delete mode 100644 docs/.vitepress/theme/components/site/useFind.ts delete mode 100644 docs/.vitepress/theme/components/site/useFindIndex.ts delete mode 100644 docs/.vitepress/theme/content.css create mode 100644 docs/.vitepress/theme/docs.css delete mode 100644 docs/.vitepress/theme/modern.css diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c015d01e..e01598db 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,8 +1,6 @@ -import { createRequire } from 'node:module' import tailwindcss from '@tailwindcss/vite' import { defineConfig, type UserConfig } from 'vitepress' import { describe, pageHead, sharedHead } from './social' -import { win98Code } from './syntax' import { tables } from './tables' import { themeBootScript } from './theme/components/site/useSiteTheme' @@ -15,22 +13,16 @@ import { themeBootScript } from './theme/components/site/useSiteTheme' type VitePressPlugin = NonNullable['plugins']>[number] /** - * rowkit.dev as a desktop in either theme, from the Figma Site pages: the home - * page is the desktop, every docs page an Explorer window in Windows 98 and a - * Finder window in modern, built from rowkit's own components in - * `theme/components/site` and `theme/components/home`. - * - * VitePress still renders the markdown, builds the search index and supplies - * the `.vp-doc` content styles the site layers its own on top of. + * rowkit.dev 2.0 (design/briefs/site.md): the docs are VitePress's default + * theme with the brand on it, light and dark; the live examples are drawn in + * the rowkit theme the nav bar's «Components» switch picks. */ export default defineConfig({ title: 'rowkit', - // «DataTable — rowkit», as the Explorer window and the taskbar name a page. titleTemplate: ':title — rowkit', description: 'A professional Vue 3 toolkit — the components a product interface is built from.', lang: 'en-GB', cleanUrls: true, - // The Explorer window's status bar shows when the page last changed. lastUpdated: true, // Callout titles as the Figma panels read: «Tip», not «TIP». @@ -43,15 +35,8 @@ export default defineConfig({ detailsLabel: 'Details', }, config: (md) => md.use(tables), - // Code in a Windows 98 IDE's colours. See `syntax.ts`. - theme: win98Code, }, - // The site switches theme and scheme with its own control and rowkit's - // attributes. This removes VitePress's light/dark switch and its `.dark` - // class, rather than leaving a second toggle that restyles nothing. - appearance: false, - sitemap: { hostname: 'https://rowkit.dev' }, // Link previews: a description from each page's first paragraph, and the @@ -85,20 +70,6 @@ export default defineConfig({ * layer statement that restates a known order is a no-op. */ vite: { - resolve: { - /* - * The Find window reads VitePress's own search index with the same - * MiniSearch VitePress uses. pnpm keeps it out of the theme's reach, so - * point at VitePress's copy rather than adding a second one. - */ - alias: { - // The package's folder: it exports no package.json, so take its entry - // file and cut back to the folder, which Vite then resolves itself. - minisearch: createRequire(createRequire(import.meta.url).resolve('vitepress')) - .resolve('minisearch') - .replace(/[\\/]dist[\\/].*$/, ''), - }, - }, plugins: [ { name: 'rowkit-layer-vitepress-css', @@ -131,37 +102,33 @@ export default defineConfig({ ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32.png' }], ['link', { rel: 'icon', type: 'image/png', sizes: '48x48', href: '/favicon-48.png' }], ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }], - // The theme, set before the first paint so a reload never flashes the other one. + // The examples' theme, set before the first paint so a reload never flashes the other one. ['script', {}, themeBootScript], ...sharedHead, ], themeConfig: { - logo: { src: '/mark-light.svg', alt: 'rowkit' }, - siteTitle: 'rowkit', + // The logo is the mark and the wordmark; no second title beside it. + logo: { light: '/logo.svg', dark: '/logo-light.svg', alt: 'rowkit' }, + siteTitle: false, nav: [ { text: 'Guide', link: '/introduction' }, { text: 'Components', link: '/components/' }, { text: 'Patterns', link: '/patterns/data-table-page' }, + { text: 'Themes', link: '/foundations/themes' }, { text: 'Storybook', link: 'https://storybook.rowkit.dev' }, - { text: 'Decisions', link: '/decisions/001-typescript-pin' }, { - text: 'Help', + text: '1.0 beta', items: [ - { - text: 'Roadmap', - link: 'https://github.com/NikolaiKushner/rowkit/blob/main/ROADMAP.md', - }, { text: 'Changelog', link: 'https://github.com/NikolaiKushner/rowkit/releases' }, + { text: 'Roadmap', link: '/roadmap' }, + { text: 'npm', link: 'https://www.npmjs.com/package/rowkit' }, ], }, ], - /* - * The folder tree of the Explorer window: sections, groups, pages. Nested - * `items` are folders; an item with a `link` is a page. - */ + /* Sections, groups, pages: nested `items` are groups; an item with a `link` is a page. */ sidebar: [ { text: 'Guide', diff --git a/docs/.vitepress/syntax.ts b/docs/.vitepress/syntax.ts deleted file mode 100644 index 47108504..00000000 --- a/docs/.vitepress/syntax.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { tokens } from '@rowkit/tokens' -import type { UserConfig } from 'vitepress' - -type ShikiTheme = Exclude['theme']>, string> - -const { vga } = tokens.color - -/** - * Every colour is a variable, so the code follows the site's theme: the - * Windows 98 IDE's colours, or the modern theme's light and dark ones. The - * values live in `theme/modern.css`; the Windows 98 ones fall back to VGA. - */ -const code = (role: string, fallback: string) => `var(--rk-code-${role}, ${fallback})` - -/** - * Code colours as a Windows 98 IDE drew them — Visual C++ 6 and Visual Basic 6: - * keywords blue, comments green, everything else black, plus maroon strings, - * teal numbers and navy tags so a Vue template reads at a glance. - * - * Restraint is the point. Operators, punctuation, names and calls stay black; - * a block where every token has its own colour reads as noise, not structure. - * - * The colours are the VGA palette from `@rowkit/tokens`, and each clears 4.5:1 - * on the white code well: blue 8.6, green 5.1, maroon 11.4, teal 4.8, navy 16. - * Yellow, olive, grey and red do not, which is why none of them is here. - */ -export const win98Code: ShikiTheme = { - name: 'rowkit-win98', - type: 'light', - fg: code('fg', vga.black), - bg: code('bg', vga.white), - settings: [ - { settings: { foreground: code('fg', vga.black), background: code('bg', vga.white) } }, - { - scope: ['comment', 'punctuation.definition.comment'], - settings: { foreground: code('comment', vga.green) }, - }, - { - scope: [ - 'keyword', - 'storage', - 'storage.type', - 'storage.modifier', - 'variable.language', - 'constant.language', - ], - settings: { foreground: code('keyword', vga.blue) }, - }, - // `=`, `=>`, `+`: keywords by grammar, punctuation to a reader. - { scope: ['keyword.operator'], settings: { foreground: code('fg', vga.black) } }, - { - scope: ['keyword.operator.new', 'keyword.operator.expression', 'keyword.operator.typeof'], - settings: { foreground: code('keyword', vga.blue) }, - }, - { - scope: ['string', 'punctuation.definition.string'], - settings: { foreground: code('string', vga.maroon) }, - }, - { scope: ['constant.numeric'], settings: { foreground: code('number', vga.teal) } }, - { - scope: ['entity.name.tag', 'punctuation.definition.tag', 'support.class.component'], - settings: { foreground: code('tag', vga.navy) }, - }, - { - scope: ['entity.other.attribute-name'], - settings: { foreground: code('attribute', vga.teal) }, - }, - ], -} diff --git a/docs/.vitepress/theme/components/ComponentIndex.vue b/docs/.vitepress/theme/components/ComponentIndex.vue index 835cf6c3..8f7134b6 100644 --- a/docs/.vitepress/theme/components/ComponentIndex.vue +++ b/docs/.vitepress/theme/components/ComponentIndex.vue @@ -2,8 +2,7 @@ import { computed } from 'vue' import { withBase } from 'vitepress' import { DataTable, DocumentIcon, type DataTableColumn } from 'rowkit' -import { folder } from './site/menu' -import { useSiteNav } from './site/useSiteNav' +import { folder, useSiteNav } from './site/useSiteNav' import type { ComponentSummary } from '../data/components.data' /** diff --git a/docs/.vitepress/theme/components/DemoBox.vue b/docs/.vitepress/theme/components/DemoBox.vue index 85f32ed6..b20ba0a6 100644 --- a/docs/.vitepress/theme/components/DemoBox.vue +++ b/docs/.vitepress/theme/components/DemoBox.vue @@ -1,11 +1,12 @@ diff --git a/docs/.vitepress/theme/components/home/AboutWindow.vue b/docs/.vitepress/theme/components/home/AboutWindow.vue deleted file mode 100644 index 6bac32b2..00000000 --- a/docs/.vitepress/theme/components/home/AboutWindow.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/home/CommandPrompt.vue b/docs/.vitepress/theme/components/home/CommandPrompt.vue deleted file mode 100644 index 28ee9910..00000000 --- a/docs/.vitepress/theme/components/home/CommandPrompt.vue +++ /dev/null @@ -1,77 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/home/DesktopHome.vue b/docs/.vitepress/theme/components/home/DesktopHome.vue deleted file mode 100644 index c7ec269f..00000000 --- a/docs/.vitepress/theme/components/home/DesktopHome.vue +++ /dev/null @@ -1,417 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/home/DesktopIcon.vue b/docs/.vitepress/theme/components/home/DesktopIcon.vue deleted file mode 100644 index 5cf27b60..00000000 --- a/docs/.vitepress/theme/components/home/DesktopIcon.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/home/EditUserDialog.vue b/docs/.vitepress/theme/components/home/EditUserDialog.vue deleted file mode 100644 index da7b355a..00000000 --- a/docs/.vitepress/theme/components/home/EditUserDialog.vue +++ /dev/null @@ -1,120 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/home/shortcuts.ts b/docs/.vitepress/theme/components/home/shortcuts.ts deleted file mode 100644 index edfb1b3f..00000000 --- a/docs/.vitepress/theme/components/home/shortcuts.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Component } from 'vue' -import { withBase } from 'vitepress' -import { - Book32Icon, - Code32Icon, - Computer32Icon, - Document32Icon, - Folder32Icon, - Trash32Icon, -} from 'rowkit' - -export interface Shortcut { - label: string - href: string - icon: Component -} - -/** The desktop's shortcuts, top to bottom: the home page's and the 404's. */ -export const shortcuts: Shortcut[] = [ - { label: 'Guide', href: withBase('/introduction'), icon: Book32Icon }, - { label: 'Components', href: withBase('/components/'), icon: Folder32Icon }, - { label: 'Patterns', href: withBase('/patterns/data-table-page'), icon: Folder32Icon }, - { label: 'Storybook', href: 'https://storybook.rowkit.dev', icon: Computer32Icon }, - { label: 'GitHub', href: 'https://github.com/NikolaiKushner/rowkit', icon: Code32Icon }, - { label: 'Decisions', href: withBase('/decisions/001-typescript-pin'), icon: Document32Icon }, - { - label: 'Old versions', - href: 'https://github.com/NikolaiKushner/rowkit/releases', - icon: Trash32Icon, - }, -] diff --git a/docs/.vitepress/theme/components/home/useHomeDemo.ts b/docs/.vitepress/theme/components/home/useHomeDemo.ts deleted file mode 100644 index 5a0751ba..00000000 --- a/docs/.vitepress/theme/components/home/useHomeDemo.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { computed, ref, watch } from 'vue' -import { useClientSort } from 'rowkit' -import type { DataTableColumn, DataTableSort, FilterChip } from 'rowkit' -import { homeUsers, type HomeUser } from '../home-users' - -/** - * The live demo on the home page: a Users list with search, a status filter, - * sorting, selection, paging, and actions that really act — Export downloads - * the selected rows, Delete removes them from this tab's copy of the list, - * and in the modern theme New user adds one. - */ -export function useHomeDemo() { - const people = ref([...homeUsers]) - - const search = ref('') - // Seeded so the window opens with a chip, as the Figma desktop shows it. - const status = ref('active') - const sort = ref | undefined>({ key: 'name', direction: 'asc' }) - // Alan Turing and Katherine Johnson: on page one under the seeded sort. - const selected = ref([3, 5]) - const page = ref(1) - const pageSize = 25 - - const filtered = computed(() => { - const query = search.value.trim().toLowerCase() - return people.value.filter((row) => { - if (status.value && row.status !== status.value) return false - if (!query) return true - return row.name.toLowerCase().includes(query) || row.email.toLowerCase().includes(query) - }) - }) - - // Pagination never moves the page itself; the application does, so a - // narrowed list does not leave the reader on an empty page. - watch([search, status], () => { - page.value = 1 - }) - - const chips = computed(() => - status.value ? [{ id: 'status', label: 'Status', value: label[status.value] }] : [] - ) - - const sorted = useClientSort(filtered, sort, columns) - - const pageRows = computed(() => { - const start = (page.value - 1) * pageSize - return sorted.value.slice(start, start + pageSize) - }) - - const range = computed(() => { - const total = filtered.value.length - if (total === 0) return '0 of 0' - const from = (page.value - 1) * pageSize + 1 - return `${String(from)}–${String(Math.min(total, from + pageSize - 1))} of ${String(total)}` - }) - - function removeFilter(): void { - status.value = undefined - } - - function clearFilters(): void { - search.value = '' - status.value = undefined - } - - function remove(ids: number[]): void { - people.value = people.value.filter((row) => !ids.includes(row.id)) - selected.value = selected.value.filter((id) => !ids.includes(id)) - } - - /** A row not yet in the list, for New user: saved through `add`. */ - function blank(): HomeUser { - const id = Math.max(0, ...people.value.map((row) => row.id)) + 1 - return { - id, - name: '', - email: '', - role: 'Member', - status: 'invited', - seats: 0, - lastActive: 'never', - } - } - - /** Adds a new row and selects it, so it is easy to find in the list. */ - function add(user: HomeUser): void { - people.value = [...people.value, user] - selected.value = [user.id] - } - - /** Writes an edited row back, by id. */ - function update(user: HomeUser): void { - people.value = people.value.map((row) => (row.id === user.id ? user : row)) - } - - /** The selected rows as a CSV file, downloaded. */ - function exportSelected(): void { - const rows = people.value.filter((row) => selected.value.includes(row.id)) - const lines = [ - 'Name,Email,Role,Status,Seats', - ...rows.map((row) => [row.name, row.email, row.role, row.status, row.seats].join(',')), - ] - const url = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/csv' })) - const link = Object.assign(document.createElement('a'), { href: url, download: 'users.csv' }) - link.click() - URL.revokeObjectURL(url) - } - - return { - search, - status, - sort, - selected, - page, - pageSize, - filtered, - chips, - pageRows, - range, - removeFilter, - clearFilters, - remove, - blank, - add, - update, - exportSelected, - } -} - -export const columns: DataTableColumn[] = [ - // The modern window has 12px margins round the table; `modern.css` narrows - // Name and Seats there so the actions column is not cut off. - { key: 'name', header: 'Name', sortable: true, width: 'var(--rk-demo-name-width, 150px)' }, - { key: 'email', header: 'Email', width: '170px' }, - { key: 'role', header: 'Role', sortable: true, width: '96px' }, - { key: 'status', header: 'Status', width: '96px' }, - { - key: 'seats', - header: 'Seats', - sortable: true, - numeric: true, - width: 'var(--rk-demo-seats-width, 80px)', - }, - { id: 'actions', header: 'Actions', headerSrOnly: true, width: '56px' }, -] - -/** Status as the badges read it. */ -export const label = { active: 'Active', invited: 'Invited', suspended: 'Suspended' } as const - -export const tone = { active: 'success', invited: 'warning', suspended: 'danger' } as const diff --git a/docs/.vitepress/theme/components/site/ComponentsLook.vue b/docs/.vitepress/theme/components/site/ComponentsLook.vue new file mode 100644 index 00000000..27ac17b6 --- /dev/null +++ b/docs/.vitepress/theme/components/site/ComponentsLook.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/docs/.vitepress/theme/components/site/ExplorerLayout.vue b/docs/.vitepress/theme/components/site/ExplorerLayout.vue deleted file mode 100644 index 90ca6f44..00000000 --- a/docs/.vitepress/theme/components/site/ExplorerLayout.vue +++ /dev/null @@ -1,322 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/FindWindow.vue b/docs/.vitepress/theme/components/site/FindWindow.vue deleted file mode 100644 index be9ee258..00000000 --- a/docs/.vitepress/theme/components/site/FindWindow.vue +++ /dev/null @@ -1,235 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/FolderTree.vue b/docs/.vitepress/theme/components/site/FolderTree.vue deleted file mode 100644 index cb8a55f7..00000000 --- a/docs/.vitepress/theme/components/site/FolderTree.vue +++ /dev/null @@ -1,229 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/NotFound.vue b/docs/.vitepress/theme/components/site/NotFound.vue deleted file mode 100644 index f9a5141a..00000000 --- a/docs/.vitepress/theme/components/site/NotFound.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/PageHeader.vue b/docs/.vitepress/theme/components/site/PageHeader.vue deleted file mode 100644 index cbc332ef..00000000 --- a/docs/.vitepress/theme/components/site/PageHeader.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/PageOutline.vue b/docs/.vitepress/theme/components/site/PageOutline.vue deleted file mode 100644 index 03935f70..00000000 --- a/docs/.vitepress/theme/components/site/PageOutline.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SiteAddressBar.vue b/docs/.vitepress/theme/components/site/SiteAddressBar.vue deleted file mode 100644 index 53d695fe..00000000 --- a/docs/.vitepress/theme/components/site/SiteAddressBar.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SiteDock.vue b/docs/.vitepress/theme/components/site/SiteDock.vue deleted file mode 100644 index 0d4d6201..00000000 --- a/docs/.vitepress/theme/components/site/SiteDock.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SiteLayout.vue b/docs/.vitepress/theme/components/site/SiteLayout.vue deleted file mode 100644 index 7f47dacb..00000000 --- a/docs/.vitepress/theme/components/site/SiteLayout.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SiteMenu.vue b/docs/.vitepress/theme/components/site/SiteMenu.vue deleted file mode 100644 index cb99a96e..00000000 --- a/docs/.vitepress/theme/components/site/SiteMenu.vue +++ /dev/null @@ -1,360 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SiteMenuBar.vue b/docs/.vitepress/theme/components/site/SiteMenuBar.vue deleted file mode 100644 index 7cf6b79b..00000000 --- a/docs/.vitepress/theme/components/site/SiteMenuBar.vue +++ /dev/null @@ -1,271 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/SiteShell.vue b/docs/.vitepress/theme/components/site/SiteShell.vue new file mode 100644 index 00000000..02215d22 --- /dev/null +++ b/docs/.vitepress/theme/components/site/SiteShell.vue @@ -0,0 +1,37 @@ + + + diff --git a/docs/.vitepress/theme/components/site/SiteTaskbar.vue b/docs/.vitepress/theme/components/site/SiteTaskbar.vue deleted file mode 100644 index 714242a7..00000000 --- a/docs/.vitepress/theme/components/site/SiteTaskbar.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/SiteToolbarButton.vue b/docs/.vitepress/theme/components/site/SiteToolbarButton.vue deleted file mode 100644 index 1aa0dfd4..00000000 --- a/docs/.vitepress/theme/components/site/SiteToolbarButton.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/SiteWallpaper.vue b/docs/.vitepress/theme/components/site/SiteWallpaper.vue deleted file mode 100644 index 07bd9ed3..00000000 --- a/docs/.vitepress/theme/components/site/SiteWallpaper.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/SpotlightSearch.vue b/docs/.vitepress/theme/components/site/SpotlightSearch.vue deleted file mode 100644 index ab1e7cbd..00000000 --- a/docs/.vitepress/theme/components/site/SpotlightSearch.vue +++ /dev/null @@ -1,271 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/site/StartMenu.vue b/docs/.vitepress/theme/components/site/StartMenu.vue deleted file mode 100644 index c93b1c52..00000000 --- a/docs/.vitepress/theme/components/site/StartMenu.vue +++ /dev/null @@ -1,284 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/ThemeSwitch.vue b/docs/.vitepress/theme/components/site/ThemeSwitch.vue deleted file mode 100644 index 41882dc6..00000000 --- a/docs/.vitepress/theme/components/site/ThemeSwitch.vue +++ /dev/null @@ -1,204 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/site/menu.ts b/docs/.vitepress/theme/components/site/menu.ts deleted file mode 100644 index 689095d7..00000000 --- a/docs/.vitepress/theme/components/site/menu.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { Component } from 'vue' -import { withBase } from 'vitepress' -import { DocumentIcon, FolderIcon } from 'rowkit' -import type { NavNode } from './useSiteNav' -import { setSiteScheme, siteScheme, type SiteScheme } from './useSiteTheme' - -/** One entry of a drop-down or Start menu. */ -export type MenuEntry = - | { - kind: 'item' - id: string - text: string - /** A page to open. External links open in a new tab. */ - href?: string - /** What the item does instead of opening a page. */ - action?: () => void - /** A submenu: the item opens it rather than doing anything itself. */ - children?: MenuEntry[] - /** The icon in the gutter: 16px in a drop-down, 32px in the Start menu. */ - icon?: Component - shortcut?: string - disabled?: boolean - /** One of a set of choices: drawn with a check mark when chosen. */ - checked?: boolean - } - | { kind: 'separator'; id: string } - /** A heading over the items after it. Modern menus only; not an item. */ - | { kind: 'header'; id: string; text: string } - -export type MenuItem = Extract - -export const isExternal = (href: string) => /^https?:/.test(href) - -/** A folder of the docs tree as menu entries: folders become submenus, pages links. */ -export function fromTree(nodes: NavNode[]): MenuEntry[] { - return nodes.map((node) => { - if (node.children.length > 0) { - return { - kind: 'item', - id: node.id, - text: node.text, - icon: FolderIcon, - children: fromTree(node.children), - } - } - return { - kind: 'item', - id: node.id, - text: node.text, - icon: DocumentIcon, - href: withBase(node.link ?? '/'), - } - }) -} - -/** The folder of the tree with this text, or nothing. */ -export function folder(tree: NavNode[], text: string): NavNode | undefined { - return tree.find((node) => node.text === text) -} - -/** - * A folder's menu: its subfolders and pages, then — under a separator — the - * folder's own page, as Figma's Components cascade ends on «All components». - */ -export function folderMenu(node: NavNode | undefined, label: string): MenuEntry[] { - if (!node) return [] - const entries = fromTree(node.children) - if (node.link === undefined) return entries - return [ - ...entries, - { kind: 'separator', id: `${node.id}-sep` }, - { - kind: 'item', - id: `${node.id}-all`, - text: label, - icon: FolderIcon, - href: withBase(node.link), - }, - ] -} - -/** - * The modern Components menu (Figma SiteModern/Menu/Components): the overview - * first, then the areas under a «By area» heading, each a submenu. - */ -export function componentsMenu(node: NavNode | undefined): MenuEntry[] { - if (!node) return [] - return [ - ...(node.link === undefined - ? [] - : [ - { - kind: 'item', - id: `${node.id}-all`, - text: 'Overview', - href: withBase(node.link), - } as const, - { kind: 'separator', id: `${node.id}-sep` } as const, - ]), - { kind: 'header', id: `${node.id}-areas`, text: 'By area' }, - ...fromTree(node.children), - ] -} - -/** The search shortcut as this platform writes it. */ -export const searchShortcut = (): string => - typeof document !== 'undefined' && document.documentElement.classList.contains('mac') - ? '⌘K' - : 'Ctrl+K' - -/** The colour schemes (Figma SiteModern/Menu/Scheme), the one in use checked. */ -export function schemeMenu(): MenuEntry[] { - const SCHEMES: [SiteScheme, string, string?][] = [ - ['system', 'Auto', 'follows the system'], - ['light', 'Light'], - ['dark', 'Dark'], - ] - return SCHEMES.map(([scheme, text, shortcut]) => ({ - kind: 'item', - id: `scheme-${scheme}`, - text, - shortcut, - checked: siteScheme.value === scheme, - action: () => setSiteScheme(scheme), - })) -} diff --git a/docs/.vitepress/theme/components/site/useFind.ts b/docs/.vitepress/theme/components/site/useFind.ts deleted file mode 100644 index 724d9ea0..00000000 --- a/docs/.vitepress/theme/components/site/useFind.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ref } from 'vue' - -/** - * Whether the Find window is open. One flag for the whole site, so the - * toolbar's Find, the Start menu's «Find…» and Ctrl+K all open the same one, - * on the desktop as well as in a docs page. - */ -export const finding = ref(false) - -export function openFind(): void { - finding.value = true -} diff --git a/docs/.vitepress/theme/components/site/useFindIndex.ts b/docs/.vitepress/theme/components/site/useFindIndex.ts deleted file mode 100644 index c21a2a6b..00000000 --- a/docs/.vitepress/theme/components/site/useFindIndex.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { shallowRef } from 'vue' -import { withBase } from 'vitepress' -import localSearchIndex from '@localSearchIndex' -import MiniSearch from 'minisearch' -import { normalize, type NavNode } from './useSiteNav' - -/** One row of the Find window: Name · In folder · Type. */ -export interface FindResult { - id: string - name: string - folder: string - type: string - href: string - /** The top folder it sits in, which Spotlight groups by; «Pages» at the top. */ - group: string - /** The folders above it — and the page, for a section — outermost first. */ - trail: string[] - /** For a folder, how many pages it holds. */ - pages?: number -} - -interface Section { - id: string - title: string - titles: string[] -} - -let index: MiniSearch | undefined -const ready = shallowRef(false) - -/** Loads VitePress's search index once, the first time Find opens. */ -export async function loadIndex(): Promise { - if (index) return - const json = (await localSearchIndex.root?.())?.default - if (json === undefined) return - index = MiniSearch.loadJSON(json, { - fields: ['title', 'titles', 'text'], - storeFields: ['title', 'titles'], - searchOptions: { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } }, - }) - ready.value = true -} - -/** What kind of thing a page is, by the top folder it sits in. */ -const kinds: Record = { - Guide: 'Guide', - Components: 'Component', - Patterns: 'Pattern', - Decisions: 'Decision', - 'Foundations · Tokens': 'Foundation', -} - -const folderOf = (node: NavNode) => node.path.map((folder) => folder.text).join('\\') - -/** The top folder a node sits in — or, for a top folder itself, its own name. */ -function groupOf(node: NavNode): string { - return node.path[0]?.text ?? (node.children.length > 0 ? node.text : 'Pages') -} - -function flatten(nodes: NavNode[]): NavNode[] { - return nodes.flatMap((node) => [node, ...flatten(node.children)]) -} - -function firstPage(node: NavNode): string { - return flatten(node.children).find((child) => child.link !== undefined)?.link ?? '/' -} - -/** - * Finds pages and folders by name first, as «Find: Files and Folders» does, - * then sections of pages by their text from VitePress's index. - */ -export function find(query: string, tree: NavNode[]): FindResult[] { - const term = query.trim().toLowerCase() - if (term === '') return [] - - const nodes = flatten(tree) - const byName: FindResult[] = nodes - .filter((node) => node.text.toLowerCase().includes(term)) - // Pages before folders, and names that start with the term first. - .sort( - (a, b) => - Number(a.children.length > 0) - Number(b.children.length > 0) || - Number(!a.text.toLowerCase().startsWith(term)) - - Number(!b.text.toLowerCase().startsWith(term)) - ) - .map((node) => { - const isFolder = node.children.length > 0 - return { - id: `name:${node.id}`, - name: node.text, - folder: folderOf(node), - type: isFolder ? 'Folder' : (kinds[node.path[0]?.text ?? ''] ?? 'Page'), - href: withBase(isFolder ? firstPage(node) : (node.link ?? '/')), - group: groupOf(node), - trail: node.path.map((folder) => folder.text), - ...(isFolder - ? { pages: flatten(node.children).filter((child) => child.link !== undefined).length } - : {}), - } - }) - - const pageByPath = new Map( - nodes.filter((node) => node.link).map((node) => [node.link?.replace(/\/$/, ''), node]) - ) - /* - * Text matches are kept tight, as a Find window should be: every word must - * match, short words only as prefixes (a typo allowance on «datat» matches - * «data» everywhere), and sections far weaker than the best one dropped. - */ - const hits = - index?.search(query, { - prefix: true, - fuzzy: term.length > 5 ? 0.2 : false, - combineWith: 'AND', - }) ?? [] - const best = hits[0]?.score ?? 0 - const bySection: FindResult[] = hits - .filter((hit) => hit.score >= best * 0.25) - .map((hit) => { - const [path = '', hash] = String(hit.id).split('#') - const page = pageByPath.get(path.replace(/\.html$/, '').replace(/\/$/, '')) - const section = hit as unknown as Section - return { - id: `text:${String(hit.id)}`, - name: section.title, - folder: page ? [folderOf(page), page.text].filter(Boolean).join('\\') : path, - type: hash ? 'Section' : 'Page', - href: withBase(String(hit.id)), - group: page ? groupOf(page) : 'Pages', - trail: page ? [...page.path.map((folder) => folder.text), page.text] : [], - } - }) - - const seen = new Set(byName.map((result) => result.href)) - return [...byName, ...bySection.filter((result) => !seen.has(result.href))].slice(0, 50) -} - -export { ready as indexReady } - -/** Pages as Spotlight lists them under «Recent»: the given paths, newest first. */ -export function recent(paths: string[], tree: NavNode[], limit = 3): FindResult[] { - const pages = flatten(tree).filter((node) => node.link !== undefined) - const out: FindResult[] = [] - for (const path of [...paths].reverse()) { - const node = pages.find((page) => withBase(normalize(page.link ?? '')) === path) - if (!node || out.some((result) => result.id === `recent:${node.id}`)) continue - out.push({ - id: `recent:${node.id}`, - name: node.text, - folder: folderOf(node), - type: 'Page', - href: withBase(node.link ?? '/'), - group: 'Recent', - trail: node.path.map((folder) => folder.text), - }) - if (out.length === limit) break - } - return out -} diff --git a/docs/.vitepress/theme/components/site/useSiteNav.ts b/docs/.vitepress/theme/components/site/useSiteNav.ts index 23a6706a..825c821f 100644 --- a/docs/.vitepress/theme/components/site/useSiteNav.ts +++ b/docs/.vitepress/theme/components/site/useSiteNav.ts @@ -1,7 +1,7 @@ -import { computed, ref } from 'vue' -import { useData, useRoute, withBase, type DefaultTheme } from 'vitepress' +import { computed } from 'vue' +import { useData, type DefaultTheme } from 'vitepress' -/** A folder or a page of the Explorer window's tree. */ +/** A group or a page of the sidebar tree. */ export interface NavNode { /** Stable id: the link for a page, the folder path for a folder. */ id: string @@ -29,86 +29,17 @@ function build(items: DefaultTheme.SidebarItem[], path: NavNode[], prefix: strin }) } -function flatten(nodes: NavNode[]): NavNode[] { - return nodes.flatMap((node) => [node, ...flatten(node.children)]) -} - -/** - * The site's navigation, read from the sidebar config: the tree, the page on - * screen and where it sits in the tree. - */ +/** The site's navigation as a tree, read from the sidebar config (the Components page lists from it). */ export function useSiteNav() { - const { theme, page } = useData() - const route = useRoute() - + const { theme } = useData() const tree = computed(() => { const sidebar = theme.value.sidebar return Array.isArray(sidebar) ? build(sidebar, [], '') : [] }) - - const pages = computed(() => flatten(tree.value).filter((node) => node.link !== undefined)) - - const current = computed(() => { - const here = normalize(route.path) - return pages.value.find((node) => withBase(normalize(node.link ?? '')) === here) - }) - - /** `rowkit:\Components\Data\DataTable`, as the Explorer address bar shows it. */ - const address = computed(() => { - const node = current.value - if (!node) return `rowkit:\\${page.value.title}` - return ['rowkit:', ...node.path.map((folder) => folder.text), node.text].join('\\') - }) - - /** - * Up: the nearest folder above. A folder with a page of its own — the - * Components overview — opens it; one without opens its first page that is - * not this one. Above the top folder is the desktop. - */ - const up = computed(() => { - const node = current.value - if (!node) return '/' - for (const folder of [...node.path].reverse()) { - if (folder.link !== undefined) return folder.link - const first = flatten(folder.children).find((child) => child.link !== undefined) - if (first && first.id !== node.id) return first.link ?? '/' - } - return '/' - }) - - return { tree, pages, current, address, up } -} - -/* - * Back and Forward. The browser keeps the real history, but cannot say whether - * there is anything to go back to, so the buttons would always look enabled. - * This tracks the pages visited in this tab, which is what Explorer's buttons - * reflect, and leaves the actual moving to the browser. - */ -const visited = ref([]) -const position = ref(-1) - -/** Every page opened in this tab, in order, for Spotlight's «Recent». */ -export const recentPaths = ref([]) - -/** Records a navigation. Called after every route change. */ -export function recordVisit(path: string, kind: 'push' | 'back' | 'forward'): void { - recentPaths.value = [...recentPaths.value.filter((seen) => seen !== path), path] - if (kind === 'back') position.value = Math.max(0, position.value - 1) - else if (kind === 'forward') - position.value = Math.min(visited.value.length - 1, position.value + 1) - else { - visited.value = [...visited.value.slice(0, position.value + 1), path] - position.value = visited.value.length - 1 - } + return { tree } } -export const canGoBack = computed(() => position.value > 0) -export const canGoForward = computed(() => position.value < visited.value.length - 1) - -/** Whether `path` is the page one step back or forward, for popstate. */ -export function stepOf(path: string): 'back' | 'forward' | 'push' { - if (visited.value[position.value - 1] === path) return 'back' - if (visited.value[position.value + 1] === path) return 'forward' - return 'push' +/** The folder of the tree with this text, or nothing. */ +export function folder(tree: NavNode[], text: string): NavNode | undefined { + return tree.find((node) => node.text === text) } diff --git a/docs/.vitepress/theme/components/site/useSiteTheme.ts b/docs/.vitepress/theme/components/site/useSiteTheme.ts index fcc1968f..b6114a9f 100644 --- a/docs/.vitepress/theme/components/site/useSiteTheme.ts +++ b/docs/.vitepress/theme/components/site/useSiteTheme.ts @@ -1,15 +1,17 @@ -import { ref } from 'vue' +import { computed, ref } from 'vue' /** - * Which rowkit theme the site is drawn in, and its colour scheme. + * Which rowkit theme the site's live examples are drawn in, and their scheme. * - * The site is one theme at a time, set on : before the first paint by - * the inline script in `config.ts` (so a reload never flashes the other - * theme), then by the switch in the taskbar or menu bar. The choice is kept - * in localStorage; `?theme=win98` in a link opens the site in that theme. + * The docs chrome is VitePress's, with its own light/dark switch. The rowkit + * theme — Windows 98 or modern — is the «Components» switch in the nav bar, + * set on as an app sets it, so overlays teleported to follow. + * The site starts in modern; `?theme=win98` in a link opens it in Windows 98. + * That is the site's choice, not the library's: rowkit's own default, without + * an attribute, is still Windows 98. * - * The site opens in the modern theme. That is the site's choice, not the - * library's: rowkit's own default, without an attribute, is still Windows 98. + * The scheme is VitePress's: its dark mode puts `data-color-scheme="dark"` on + * too, so the modern examples turn dark with the page. */ export type SiteTheme = 'win98' | 'modern' export type SiteScheme = 'system' | 'light' | 'dark' @@ -17,43 +19,39 @@ export type SiteScheme = 'system' | 'light' | 'dark' // `-2` since the site opens in modern: a choice stored while Windows 98 was // the default is not kept, so every visitor starts in modern once. export const THEME_KEY = 'rowkit-theme-2' -export const SCHEME_KEY = 'rowkit-scheme' +/** VitePress's own key for its appearance switch. */ +const APPEARANCE_KEY = 'vitepress-theme-appearance' export const siteTheme = ref('modern') -export const siteScheme = ref('system') +const dark = ref(false) + +/** The examples' scheme, as the page draws it. */ +export const siteScheme = computed(() => (dark.value ? 'dark' : 'light')) /** Reads what the inline script set. Client only. */ export function readSiteTheme(): void { - const root = document.documentElement - siteTheme.value = root.dataset.theme === 'win98' ? 'win98' : 'modern' - const scheme = root.dataset.colorScheme - siteScheme.value = scheme === 'light' || scheme === 'dark' ? scheme : 'system' + siteTheme.value = document.documentElement.dataset.theme === 'win98' ? 'win98' : 'modern' } -function store(key: string, value: string): void { +export function setSiteTheme(theme: SiteTheme): void { + siteTheme.value = theme + document.documentElement.dataset.theme = theme try { - localStorage.setItem(key, value) + localStorage.setItem(THEME_KEY, theme) } catch { // Private windows and blocked storage: the switch still works for this visit. } } -export function setSiteTheme(theme: SiteTheme): void { - siteTheme.value = theme - document.documentElement.dataset.theme = theme - store(THEME_KEY, theme) -} - -export function setSiteScheme(scheme: SiteScheme): void { - siteScheme.value = scheme - const root = document.documentElement - if (scheme === 'system') delete root.dataset.colorScheme - else root.dataset.colorScheme = scheme - store(SCHEME_KEY, scheme) +/** Follows VitePress's dark mode. */ +export function setSiteDark(isDark: boolean): void { + dark.value = isDark + document.documentElement.dataset.colorScheme = isDark ? 'dark' : 'light' } /** - * The script that sets the theme before the page paints, inlined into . - * Kept here, beside the keys it reads, so the two cannot drift. + * The script that sets the theme before the page paints, inlined into + * beside VitePress's own appearance script. Kept here, beside the keys it + * reads, so the two cannot drift. */ -export const themeBootScript = `(function(){var r=document.documentElement,t,s;try{var q=new URLSearchParams(location.search);t=q.get('theme')||localStorage.getItem('${THEME_KEY}');s=q.get('scheme')||localStorage.getItem('${SCHEME_KEY}');if(q.get('theme'))localStorage.setItem('${THEME_KEY}',t);if(q.get('scheme'))localStorage.setItem('${SCHEME_KEY}',s)}catch(e){}r.dataset.theme=t==='win98'?'win98':'modern';if(s==='light'||s==='dark')r.dataset.colorScheme=s})()` +export const themeBootScript = `(function(){var r=document.documentElement,t,a;try{var q=new URLSearchParams(location.search);t=q.get('theme')||localStorage.getItem('${THEME_KEY}');if(q.get('theme'))localStorage.setItem('${THEME_KEY}',t);a=localStorage.getItem('${APPEARANCE_KEY}')}catch(e){}r.dataset.theme=t==='win98'?'win98':'modern';var d=a==='dark'||((!a||a==='auto')&&matchMedia('(prefers-color-scheme: dark)').matches);r.dataset.colorScheme=d?'dark':'light'})()` diff --git a/docs/.vitepress/theme/content.css b/docs/.vitepress/theme/content.css deleted file mode 100644 index bd2cc314..00000000 --- a/docs/.vitepress/theme/content.css +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Page content in the Explorer window's "web view", from the Figma Site page: - * body text 15/24, h2 18/22, a 16px rhythm between blocks, code in a sunken - * white field, tips as small system-message panels, every table drawn like a - * DataTable. - * - * In `components`: above VitePress's own `.vp-doc` rules (`vp-theme`), below - * Tailwind utilities, so a live demo's own classes always win. Every selector - * also stops at `.rk-demo`, so properties a component leaves unset do not - * leak into it. - */ -@layer components { - .rk-page { - font-family: var(--font-sans); - font-size: var(--text-doc); - line-height: var(--text-doc--line-height); - color: var(--color-foreground); - } - - /* One rhythm: 16px between blocks, headings included. */ - .rk-page - :where(h2, h3, h4, p, ul, ol, .rk-table, div[class*='language-'], .custom-block, hr):not( - .rk-demo * - ) { - margin: 16px 0 0; - } - - /* - * Inside a live demo, VitePress's article styles step back. Its `.vp-doc` - * rules give every

16px margins and a 28px line, underline and recolour - * every , frame every table cell — and a component that leaves one of - * those properties unset would inherit it: a Field's hint drifting 30px - * below its input, a Button rendered as a link underlined. This layer sits - * after VitePress's and before the components' own utilities, so it - * clears the leak without overriding anything a component sets. - */ - .rk-page .rk-demo :where(p, blockquote, h1, h2, h3, h4, h5, h6, ul, ol, hr, summary) { - margin: 0; - } - - .rk-page .rk-demo :where(p, blockquote) { - line-height: inherit; - } - - .rk-page .rk-demo :where(h1, h2, h3, h4, h5, h6) { - padding: 0; - border: 0; - font-size: inherit; - line-height: inherit; - font-weight: var(--font-weight-strong); - letter-spacing: 0; - } - - .rk-page .rk-demo :where(a) { - color: inherit; - font-weight: inherit; - text-decoration: none; - } - - .rk-page .rk-demo :where(strong) { - font-weight: var(--font-weight-strong); - } - - .rk-page .rk-demo :where(ul, ol) { - padding-left: 0; - list-style: none; - } - - .rk-page .rk-demo :where(li + li) { - margin-top: 0; - } - - .rk-page .rk-demo :where(table) { - display: table; - margin: 0; - overflow: visible; - } - - .rk-page .rk-demo :where(tr, th, td) { - border: 0; - background: none; - } - - .rk-page .rk-demo :where(th, td) { - padding: 0; - font-size: inherit; - line-height: inherit; - font-weight: inherit; - color: inherit; - } - - .rk-page .rk-demo :where(:not(pre) > code) { - padding: 0; - border-radius: 0; - background: none; - font-size: inherit; - color: inherit; - } - - /* The page's own h1 is the header above the content. */ - .rk-page h1:not(.rk-demo *) { - display: none; - } - - .rk-page :where(h2):not(.rk-demo *) { - padding: 0; - border: 0; - font-size: var(--text-doc-h2); - line-height: var(--text-doc-h2--line-height); - font-weight: var(--font-weight-strong); - letter-spacing: 0; - } - - .rk-page :where(h3, h4):not(.rk-demo *) { - font-size: var(--text-doc-h3); - line-height: var(--text-doc-h3--line-height); - font-weight: var(--font-weight-strong); - letter-spacing: 0; - } - - .rk-page :where(h2, h3, h4) .header-anchor { - display: none; - } - - .rk-page :where(p, li):not(.rk-demo *) { - font-size: var(--text-doc); - line-height: var(--text-doc--line-height); - } - - .rk-page :where(li + li):not(.rk-demo *) { - margin-top: 0; - } - - .rk-page :where(a):not(.rk-demo *, .header-anchor) { - color: var(--color-link); - text-decoration: underline; - font-weight: inherit; - } - - /* An etched line. */ - .rk-page hr:not(.rk-demo *) { - height: 2px; - border: 0; - box-shadow: - inset 0 1px var(--color-bevel-shadow), - inset 0 -1px var(--color-bevel-highlight); - } - - /* Inline code: the mono face at its own size, no pill. */ - .rk-page :where(:not(pre) > code):not(.rk-demo *) { - padding: 0; - border-radius: 0; - background: none; - font-family: var(--font-mono); - font-size: var(--text-doc-mono); - color: inherit; - } - - /* Keys, as the Figma keyboard list sets them: the mono face, no keycap. */ - .rk-page kbd:not(.rk-demo *) { - padding: 0; - border: 0; - border-radius: 0; - background: none; - box-shadow: none; - font-family: var(--font-mono); - font-size: var(--text-doc-mono); - line-height: var(--text-doc-mono--line-height); - color: inherit; - } - - /* - * Code blocks: a sunken white field, VT323 16/16 in black — Notepad has no - * syntax colours — and Copy at the top right. Long lines scroll with the - * Windows 98 bar. - */ - .rk-page div[class*='language-']:not(.rk-demo *) { - position: relative; - margin-left: 0; - margin-right: 0; - border-radius: var(--radius-lg); - background: var(--color-input); - box-shadow: var(--shadow-table); - } - - .rk-page div[class*='language-'] pre { - @apply scrollbar-themed; - /* - * The code scrolls in its own column, which stops short of Copy — 75px - * and 4px in, 8px between, as Figma lays them side by side — so a long - * line scrolls beside the button rather than under it. - */ - margin: 0 87px 0 0; - padding: 8px 0 8px 12px; - background: none; - } - - .rk-page div[class*='language-'] code { - padding: 0; - font-family: var(--font-mono); - font-size: var(--text-doc-mono); - line-height: var(--text-doc-mono--line-height); - color: var(--color-foreground); - } - - .rk-page div[class*='language-'] > span.lang { - display: none; - } - - /* VitePress's copy button, redrawn as a 75×21 command button: icon and «Copy». */ - .rk-page div[class*='language-'] > button.copy { - top: 8px; - right: 4px; - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - width: 75px; - height: 21px; - border: 0; - border-radius: var(--radius-sm); - opacity: 1; - background: var(--color-control) url('/icons/copy-16.svg') no-repeat 13px center; - box-shadow: var(--shadow-raised); - font-family: var(--font-sans); - font-size: var(--text-ui); - color: var(--color-foreground); - } - - /* - * The label rides on ::before, which VitePress also uses for its floating - * «Copied» bubble — reset that bubble's box and offset so the word stays in - * the button. - */ - .rk-page div[class*='language-'] > button.copy::before { - content: 'Copy'; - position: static; - top: auto; - /* VitePress slides its «Copied» bubble out to the left of the button. */ - transform: none; - width: auto; - height: auto; - padding: 0 0 0 20px; - border: 0; - border-radius: 0; - background: none; - box-shadow: none; - font-size: var(--text-ui); - line-height: var(--text-ui--line-height); - color: var(--color-foreground); - } - - .rk-page div[class*='language-'] > button.copy.copied { - border-radius: 0; - background-image: url('/icons/copy-16.svg'); - } - - .rk-page div[class*='language-'] > button.copy.copied::before { - content: 'Copied'; - } - - .rk-page div[class*='language-'] > button.copy.copied::after { - display: none; - } - - .rk-page div[class*='language-'] > button.copy:active { - box-shadow: var(--shadow-pressed); - padding: 1px 0 0 1px; - } - - /* - * Modern (Figma Site/CodeBlock): 12px and 14px in, and Copy as a 74×28 - * secondary button with the modern copy glyph. - */ - /* As tall as Copy and its margins, so a one-line block still holds the button. */ - [data-theme='modern'] .rk-page div[class*='language-']:not(.rk-demo *) { - min-height: 52px; - } - - [data-theme='modern'] .rk-page div[class*='language-'] pre { - margin-right: 96px; - padding: 12px 0 12px 14px; - } - - [data-theme='modern'] .rk-page div[class*='language-'] > button.copy { - top: 12px; - right: 14px; - width: 74px; - height: 28px; - border-radius: 7px; - background-image: none; - color: var(--color-control-foreground); - } - - [data-theme='modern'] .rk-page div[class*='language-'] > button.copy::before { - color: var(--color-control-foreground); - } - - [data-theme='modern'] .rk-page div[class*='language-'] > button.copy::after, - [data-theme='modern'] .rk-page div[class*='language-'] > button.copy.copied::after { - content: ''; - position: absolute; - top: 7px; - left: 12px; - display: block; - width: 14px; - height: 14px; - background: currentColor; - mask: var(--rk-icon-copy) no-repeat center / contain; - } - - [data-theme='modern'] .rk-page div[class*='language-'] > button.copy:active { - padding: 0; - } - - /* - * Tips, warnings and dangers: a system-message panel in the window bevel, - * the 32px icon at the left, a bold 13px title over the text. - */ - .rk-page .custom-block:not(.rk-demo *) { - position: relative; - padding: 12px 12px 12px 56px; - border: 0; - border-radius: var(--radius-lg); - background: var(--color-card) no-repeat 12px 12px; - box-shadow: var(--shadow-window); - color: var(--color-foreground); - } - - .rk-page .custom-block.tip, - .rk-page .custom-block.info { - background-image: url('/icons/info-32.svg'); - } - - .rk-page .custom-block.warning { - background-image: url('/icons/warning-32.svg'); - } - - .rk-page .custom-block.danger { - background-image: url('/icons/error-32.svg'); - } - - .rk-page .custom-block p { - margin: 0; - color: var(--color-foreground); - } - - .rk-page .custom-block p.custom-block-title { - margin-bottom: 4px; - font-size: var(--text-heading); - line-height: var(--text-heading--line-height); - font-weight: var(--font-weight-strong); - } - - .rk-page .custom-block p + p { - margin-top: 4px; - } - - /* - * Modern (Figma Site/Callout): a tinted panel with a 1px border, a 20px icon - * in the tone's colour, the title small and coloured over the text. - */ - [data-theme='modern'] .rk-page .custom-block:not(.details, .rk-demo *) { - --rk-callout-tone: var(--color-primary-solid); - --rk-callout-title: var(--color-primary-on-subtle); - --rk-callout-glyph: var(--rk-icon-info); - padding: 14px 16px 14px 48px; - border: 1px solid var(--color-primary-border); - border-radius: 10px; - background: var(--color-primary-subtle); - box-shadow: none; - } - - [data-theme='modern'] .rk-page .custom-block:not(.details, .rk-demo *)::before { - content: ''; - position: absolute; - top: 14px; - left: 16px; - width: 20px; - height: 20px; - background: var(--rk-callout-tone); - mask: var(--rk-callout-glyph) no-repeat center / contain; - } - - [data-theme='modern'] .rk-page .custom-block.warning:not(.rk-demo *) { - --rk-callout-tone: var(--color-warning-solid); - --rk-callout-title: var(--color-warning-on-subtle); - --rk-callout-glyph: var(--rk-icon-warning); - border-color: var(--color-warning-border); - background: var(--color-warning-subtle); - } - - [data-theme='modern'] .rk-page .custom-block.danger:not(.rk-demo *) { - --rk-callout-tone: var(--color-danger-solid); - --rk-callout-title: var(--color-danger-on-subtle); - --rk-callout-glyph: var(--rk-icon-error); - border-color: var(--color-danger-border); - background: var(--color-danger-subtle); - } - - [data-theme='modern'] .rk-page .custom-block p.custom-block-title { - margin-bottom: 2px; - font-size: var(--text-ui); - line-height: var(--text-ui--line-height); - color: var(--rk-callout-title); - } - - /* - * Markdown tables as DataTables: a sunken white frame, raised 22px header - * cells, 11px text, no grid lines. - */ - .rk-page .rk-table:not(.rk-demo *) { - @apply scrollbar-themed; - overflow-x: auto; - } - - .rk-page table:not(.rk-demo *) { - display: table; - width: 100%; - margin: 0; - border: 0; - border-collapse: separate; - border-spacing: 0; - padding: var(--spacing-frame); - border-radius: var(--radius-lg); - background: var(--color-input); - box-shadow: var(--shadow-table); - font-size: var(--text-ui); - line-height: var(--text-ui--line-height); - } - - .rk-page :where(th, td):not(.rk-demo *) { - padding: 4px 6px; - border: 0; - background: none; - font-size: var(--text-ui); - line-height: var(--text-ui--line-height); - text-align: left; - vertical-align: middle; - } - - .rk-page th:not(.rk-demo *) { - height: 22px; - padding-top: 0; - padding-bottom: 0; - background: var(--color-table-header); - box-shadow: var(--shadow-header); - font-weight: 400; - color: var(--color-table-header-foreground); - white-space: nowrap; - } - - .rk-page tr:not(.rk-demo *) { - border: 0; - background: none; - } - - .rk-page :where(td code):not(.rk-demo *) { - font-size: var(--text-mono); - } - - /* - * A props table on a phone: the definition list Figma draws at 390px. The - * name in bold and the type in the mono face on one line — the default - * after it, «= 'md'» — and the description under them in body text. - */ - @media (max-width: 767px) { - .rk-page .rk-props table:not(.rk-demo *) { - display: block; - padding: 0; - background: none; - box-shadow: none; - } - - .rk-page .rk-props table thead { - display: none; - } - - .rk-page .rk-props table tbody { - display: flex; - flex-direction: column; - gap: 16px; - } - - .rk-page .rk-props table tr { - display: flex; - flex-wrap: wrap; - align-items: baseline; - gap: 2px 8px; - } - - .rk-page .rk-props table td { - padding: 0; - } - - .rk-page .rk-props table td[data-label='Prop'], - .rk-page .rk-props table td[data-label='Prop'] code { - font-family: var(--font-sans); - font-size: var(--text-ui); - line-height: var(--text-ui--line-height); - font-weight: var(--font-weight-strong); - } - - .rk-page .rk-props table :where(td[data-label='Type'], td[data-label='Default']), - .rk-page .rk-props table :where(td[data-label='Type'], td[data-label='Default']) code { - font-family: var(--font-mono); - font-size: var(--text-mono); - line-height: var(--text-mono--line-height); - color: var(--color-text-subtle); - } - - .rk-page .rk-props table td[data-label='Default']::before { - content: '= '; - } - - .rk-page .rk-props table td[data-empty] { - display: none; - } - - /* «required» is not a default; it reads as itself, without the «=». */ - .rk-page .rk-props table td[data-label='Default']:has(strong)::before { - content: none; - } - - .rk-page .rk-props table td[data-label='Description'] { - flex-basis: 100%; - font-size: var(--text-doc); - line-height: var(--text-doc--line-height); - } - } -} diff --git a/docs/.vitepress/theme/docs.css b/docs/.vitepress/theme/docs.css new file mode 100644 index 00000000..bc3918a6 --- /dev/null +++ b/docs/.vitepress/theme/docs.css @@ -0,0 +1,178 @@ +/* + * rowkit.dev's docs: VitePress's default theme with the brand on it — the + * vermilion accent on links, the active sidebar item and the outline marker, + * the logo, a 15px sidebar. The chrome is the brand's, in light and dark; the + * live examples inside it are drawn in whichever rowkit theme the nav bar's + * «Components» switch picks. + * + * In `components`: above VitePress's own rules (`vp-theme`, see config.ts), + * below Tailwind utilities, so a demo's own classes always win. + */ + +/* The brand (Figma Brand — rowkit, Colour). Text in vermilion clears 4.5:1 on paper and on night. */ +:root { + --vp-c-brand-1: #c8361c; + --vp-c-brand-2: #b32e16; + --vp-c-brand-3: #d63a1f; + --vp-c-brand-soft: rgb(214 58 31 / 0.12); + --vp-button-brand-bg: #d63a1f; + --vp-button-brand-hover-bg: #b32e16; + --vp-button-brand-active-bg: #b32e16; + --vp-home-hero-name-color: var(--vp-c-brand-1); +} + +.dark { + --vp-c-brand-1: #f0846e; + --vp-c-brand-2: #e8735b; + --vp-c-brand-3: #d63a1f; + --vp-c-brand-soft: rgb(214 58 31 / 0.16); +} + +@layer components { + /* The sidebar a size up from VitePress's 14px, so the tree reads without squinting. */ + .VPSidebarItem .text { + font-size: 15px; + line-height: 22px; + } + + /* + * Inside a live demo, VitePress's article styles step back. Its `.vp-doc` + * rules give every

margins and a 28px line, underline and recolour every + * , frame every table cell — and a component that leaves one of those + * properties unset would inherit it. This layer sits after VitePress's and + * before the components' own utilities, so it clears the leak without + * overriding anything a component sets. + */ + .vp-doc .rk-demo :where(p, blockquote, h1, h2, h3, h4, h5, h6, ul, ol, hr, summary) { + margin: 0; + } + + .vp-doc .rk-demo :where(p, blockquote) { + line-height: inherit; + } + + .vp-doc .rk-demo :where(h1, h2, h3, h4, h5, h6) { + padding: 0; + border: 0; + font-size: inherit; + line-height: inherit; + font-weight: var(--font-weight-strong); + letter-spacing: 0; + } + + .vp-doc .rk-demo :where(a) { + color: inherit; + font-weight: inherit; + text-decoration: none; + } + + .vp-doc .rk-demo :where(strong) { + font-weight: var(--font-weight-strong); + } + + .vp-doc .rk-demo :where(ul, ol) { + padding-left: 0; + list-style: none; + } + + .vp-doc .rk-demo :where(li + li) { + margin-top: 0; + } + + .vp-doc .rk-demo :where(table) { + display: table; + margin: 0; + overflow: visible; + } + + .vp-doc .rk-demo :where(tr, th, td) { + border: 0; + background: none; + } + + .vp-doc .rk-demo :where(th, td) { + padding: 0; + font-size: inherit; + line-height: inherit; + font-weight: inherit; + color: inherit; + } + + .vp-doc .rk-demo :where(:not(pre) > code) { + padding: 0; + border-radius: 0; + background: none; + font-size: inherit; + color: inherit; + } + + /* + * A demo is its own stacking context. rowkit's z-index scale assumes rowkit + * is the page's chrome (a table's frame and sticky header sit at 100); here + * it is the guest, under VitePress's nav bar at 30. + */ + .rk-demo { + isolation: isolate; + } + + /* + * A props table on a phone: name in bold and type in mono on one line — the + * default after it, «= 'md'» — and the description under them. The cells + * carry their column's header as `data-label` (see tables.ts). + */ + @media (max-width: 767px) { + .vp-doc .rk-props table { + display: block; + border: 0; + } + + .vp-doc .rk-props table thead { + display: none; + } + + .vp-doc .rk-props table tbody { + display: flex; + flex-direction: column; + gap: 16px; + } + + .vp-doc .rk-props table tr { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 2px 8px; + border: 0; + background: none; + } + + .vp-doc .rk-props table td { + padding: 0; + border: 0; + } + + .vp-doc .rk-props table td[data-label='Prop'] code { + font-weight: 600; + } + + .vp-doc .rk-props table :where(td[data-label='Type'], td[data-label='Default']) { + color: var(--vp-c-text-2); + } + + .vp-doc .rk-props table td[data-label='Default']::before { + content: '= '; + } + + .vp-doc .rk-props table td[data-empty] { + display: none; + } + + /* «required» is not a default; it reads as itself, without the «=». */ + .vp-doc .rk-props table td[data-label='Default']:has(strong)::before { + content: none; + } + + .vp-doc .rk-props table td[data-label='Description'] { + flex-basis: 100%; + } + } +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 977fe7e2..2f8c1c80 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -6,7 +6,7 @@ import BevelTiles from './components/BevelTiles.vue' import ColorList from './components/ColorList.vue' import ColorPalette from './components/ColorPalette.vue' import DemoBox from './components/DemoBox.vue' -import SiteLayout from './components/site/SiteLayout.vue' +import SiteShell from './components/site/SiteShell.vue' import NpmVersion from './components/NpmVersion.vue' import TokenGrid from './components/TokenGrid.vue' import TokenTable from './components/TokenTable.vue' @@ -28,7 +28,7 @@ import './tokens.css' */ export default { extends: DefaultTheme, - Layout: SiteLayout, + Layout: SiteShell, enhanceApp({ app }) { for (const [name, value] of Object.entries(rowkit)) { // Every component export is PascalCase; the composables, the `cn` helper diff --git a/docs/.vitepress/theme/modern.css b/docs/.vitepress/theme/modern.css deleted file mode 100644 index 416ccf51..00000000 --- a/docs/.vitepress/theme/modern.css +++ /dev/null @@ -1,132 +0,0 @@ -/* - * The docs site in the modern theme: the parts of its chrome that are plain - * CSS rather than utilities. Everything here is scoped to the modern theme; - * the Windows 98 site is untouched by this file. - */ - -/* - * The wallpaper (Figma SiteModern/Wallpaper): the desktop colour under three - * blurred ellipses, drawn by SiteWallpaper. Their colours, light and dark. - */ -[data-theme='modern'] .rk-desktop { - background: var(--color-desktop); - --rk-wallpaper-a: var(--color-modern-white); - --rk-wallpaper-b: var(--color-modern-blue); - --rk-wallpaper-c: var(--color-modern-blue-link-dark); -} - -@media (prefers-color-scheme: dark) { - [data-theme='modern']:not([data-color-scheme='light']) .rk-desktop { - --rk-wallpaper-a: #22345f; - --rk-wallpaper-c: #4b2f7a; - } -} - -[data-theme='modern'][data-color-scheme='dark'] .rk-desktop { - --rk-wallpaper-a: #22345f; - --rk-wallpaper-c: #4b2f7a; -} - -/* The home page's live demo: narrower Name and Seats columns, to fit inside the window's margins. */ -[data-theme='modern'] { - --rk-demo-name-width: 144px; - --rk-demo-seats-width: 64px; -} - -/* The Terminal on the home page: the same in light and dark. */ -[data-theme='modern'] { - --rk-terminal-bg: #1e1e1e; - --rk-terminal-fg: #e5e5e7; -} - -/* - * The menu bar: translucent, a hairline under it, no bevel. The glass is drawn - * by a layer behind its content rather than by the bar itself: an element with - * a backdrop filter is the backdrop of everything inside it, and the menus - * that drop from the bar would blur only the bar. - */ -[data-theme='modern'] .rk-taskbar { - position: relative; - background: none; - box-shadow: inset 0 -1px 0 var(--color-border); -} - -[data-theme='modern'] .rk-taskbar::before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - background: var(--color-popover); - backdrop-filter: var(--rk-popover-backdrop); -} - -/* Menu-bar items (Figma SiteModern/MenuBarItem): rounded ghost buttons, grey while open. */ -:root[data-theme='modern'] .rk-bar-item { - padding: 0 8px; - border-radius: 5px; -} - -:root[data-theme='modern'] .rk-bar-item:hover { - background: var(--color-control-ghost-hover); - box-shadow: none; -} - -:root[data-theme='modern'] .rk-bar-item-open, -:root[data-theme='modern'] .rk-bar-item-open:hover { - background: var(--color-control-ghost-active); - box-shadow: none; -} - -:root[data-theme='modern'] .rk-bar-item:focus-visible:not(.rk-bar-item-open) { - background: none; - color: var(--color-foreground); - outline: var(--rk-focus-outer-width) solid var(--color-focus-ring); - outline-offset: var(--rk-focus-outer-offset); -} - -/* - * The folder tree as a sidebar (Figma SiteModern/SidebarItem): the whole row - * is the selection, drawn on the row in FolderTree; the label is plain text. - */ -:root[data-theme='modern'] .rk-tree-label, -:root[data-theme='modern'] .rk-tree-label-selected { - padding: 0; - background: none; - color: inherit; -} - -:root[data-theme='modern'] li[role='treeitem']:focus-visible > .rk-tree-label { - outline: none; -} - -/* Code colours, as a current IDE draws them: light, and dark. */ -[data-theme='modern'] { - --rk-code-fg: var(--color-foreground); - --rk-code-bg: var(--color-input); - --rk-code-comment: #5d6c79; - --rk-code-keyword: #9b2393; - --rk-code-string: #c41a16; - --rk-code-number: #1c00cf; - --rk-code-tag: #0b4f79; - --rk-code-attribute: #815f03; -} - -@media (prefers-color-scheme: dark) { - [data-theme='modern']:not([data-color-scheme='light']) { - --rk-code-comment: #7f8c98; - --rk-code-keyword: #fc5fa3; - --rk-code-string: #fc6a5d; - --rk-code-number: #d0bf69; - --rk-code-tag: #5dd8ff; - --rk-code-attribute: #ffa14f; - } -} - -[data-theme='modern'][data-color-scheme='dark'] { - --rk-code-comment: #7f8c98; - --rk-code-keyword: #fc5fa3; - --rk-code-string: #fc6a5d; - --rk-code-number: #d0bf69; - --rk-code-tag: #5dd8ff; - --rk-code-attribute: #ffa14f; -} diff --git a/docs/.vitepress/theme/tokens.css b/docs/.vitepress/theme/tokens.css index e552bbcf..81ae599c 100644 --- a/docs/.vitepress/theme/tokens.css +++ b/docs/.vitepress/theme/tokens.css @@ -1,13 +1,7 @@ /* - * The docs site brands itself from rowkit's own tokens. - * - * VitePress's default theme is entirely CSS-variable driven, so mapping its - * brand slots onto `@rowkit/tokens` means the site is a consumer of the package - * it documents. If a token changes, the site changes — there is no second - * palette to keep in sync, and no screenshot to go stale. - * - * Tailwind and rowkit's theme are imported here too, because the live demos on - * component pages are real rowkit components and need real rowkit utilities. + * Tailwind and rowkit's theme, because the live demos on the docs pages are + * real rowkit components and need real rowkit utilities. The docs chrome is + * VitePress's default theme with the brand on it — see docs.css. * * The `vp-theme` layer order is *not* set here. It rides on the wrapped * VitePress CSS in `config.ts`, which lands above this file in the bundle — an @@ -16,15 +10,13 @@ @import 'tailwindcss'; @import '../../../packages/ui/src/styles/index.css'; -@import './content.css'; -@import './modern.css'; +@import './docs.css'; /* - * The site is drawn in one rowkit theme at a time, set on (see - * `useSiteTheme.ts`). rowkit's components follow the theme through their - * tokens; the site's own chrome — the taskbar that becomes a menu bar, the - * Explorer toolbar that becomes Finder's — is styled per theme with these. - * They are the site's, not rowkit's: a library cannot assume one theme per page. + * The live examples are drawn in one rowkit theme at a time, set on + * (see `useSiteTheme.ts`). These variants style the site's own pieces per + * theme — the landing page above all. They are the site's, not rowkit's: a + * library cannot assume one theme per page. */ @custom-variant modern (&:where([data-theme='modern'], [data-theme='modern'] *)); @custom-variant win98 (&:where(:root:not([data-theme='modern']), :root:not([data-theme='modern']) *)); @@ -122,28 +114,3 @@ .rk-demo thead tr { z-index: 1; } - -:root { - --vp-font-family-base: var(--font-sans); - --vp-font-family-mono: var(--font-mono); - - --vp-c-brand-1: var(--color-primary-solid); - --vp-c-brand-2: var(--color-link); - --vp-c-brand-3: var(--color-titlebar-to); - --vp-c-brand-soft: var(--color-border-subtle); - - --vp-c-bg: var(--color-background); - --vp-c-bg-alt: var(--color-muted); - --vp-c-bg-soft: var(--color-muted); - - --vp-c-text-1: var(--color-foreground); - --vp-c-text-2: var(--color-muted-foreground); - --vp-c-text-3: var(--color-text-subtle); - - --vp-c-divider: var(--color-border); - --vp-c-gutter: var(--color-border); - - --vp-button-brand-bg: var(--color-primary-solid); - --vp-button-brand-hover-bg: var(--color-primary-solid-hover); - --vp-button-brand-text: var(--color-primary-on-solid); -} diff --git a/docs/index.md b/docs/index.md index 68800ece..55ec75fb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,6 +4,16 @@ title: rowkit # The browser tab: the brand's line from the link preview (og-image.png). titleTemplate: ':title — Vue 3 components for product interfaces' description: A professional Vue 3 toolkit — the components a product interface is built from. ---- - +hero: + name: rowkit + text: Vue 3 components for product interfaces + tagline: A professional toolkit — typed, accessible, themeable. Windows 98 and modern in the box; your own look in one call. + actions: + - theme: brand + text: Get started + link: /installation + - theme: alt + text: Browse components + link: /components/ +--- From 57d463c258f8d0a2da3e2256ca13dac6f6f8592d Mon Sep 17 00:00:00 2001 From: NikolaiKushner Date: Fri, 9 Oct 2026 12:18:11 +0300 Subject: [PATCH 5/6] Site: the landing page, in Windows 98 and in the modern brand. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rowkit.dev's home page as Figma Site v2 draws it, at 1440 and 390, in Windows 98, modern light and modern dark: the hero with the install command and the live Users table, «One set of components. Any look.» (one form in Windows 98, modern and a defineTheme() theme), eight live component previews and what comes next, «Built like a professional tool» with the numbers, a data table page in code, «Before you install», «Start in a minute», and the footer. The page is drawn by the site's rowkit theme; the switch over the table redraws the whole site. Its own values — paper and night under the page, vermilion on the modern accents, the code colours — are in landing.css. Vermilion as small text is #c8361c: the brand's #d63a1f is 4.48:1 on paper. Axe finds nothing in any of the three looks; no horizontal scroll at 375 or 1440. The old desktop shell's leftover assets go. Co-Authored-By: Claude Opus 5.5 --- design/briefs/site.md | 12 +- .../theme/components/landing/CodeBlock.vue | 39 ++++ .../components/landing/EditUserDialog.vue | 118 +++++++++++ .../components/landing/InstallCommand.vue | 36 ++++ .../theme/components/landing/LandingCode.vue | 83 ++++++++ .../components/landing/LandingComponents.vue | 193 ++++++++++++++++++ .../theme/components/landing/LandingDemo.vue | 124 +++++++++++ .../theme/components/landing/LandingFaq.vue | 62 ++++++ .../components/landing/LandingFooter.vue | 82 ++++++++ .../theme/components/landing/LandingHero.vue | 67 ++++++ .../theme/components/landing/LandingPage.vue | 34 +++ .../theme/components/landing/LandingStart.vue | 47 +++++ .../components/landing/LandingThemes.vue | 114 +++++++++++ .../theme/components/landing/LandingWhy.vue | 93 +++++++++ .../theme/components/landing/LookSwitch.vue | 60 ++++++ .../components/landing/SectionHeading.vue | 41 ++++ .../theme/components/landing/SiteIcon.vue | 59 ++++++ .../theme/components/landing/highlight.ts | 66 ++++++ .../theme/components/landing/links.ts | 14 ++ .../components/landing/useLandingDemo.ts | 102 +++++++++ docs/.vitepress/theme/landing.css | 162 +++++++++++++++ docs/.vitepress/theme/tokens.css | 1 + docs/index.md | 21 +- docs/public/icons/copy-16.svg | 1 - docs/public/icons/error-32.svg | 1 - docs/public/icons/info-32.svg | 1 - docs/public/icons/modern/auto.svg | 5 - docs/public/icons/modern/external.svg | 3 - docs/public/icons/modern/menu.svg | 3 - docs/public/icons/modern/moon.svg | 3 - docs/public/icons/modern/sidebar.svg | 4 - docs/public/icons/modern/sun.svg | 4 - docs/public/icons/site/accessible-32.png | Bin 0 -> 230 bytes docs/public/icons/site/agents-32.png | Bin 0 -> 280 bytes docs/public/icons/site/behaviour-32.png | Bin 0 -> 278 bytes docs/public/icons/site/ssr-32.png | Bin 0 -> 242 bytes docs/public/icons/site/tokens-32.png | Bin 0 -> 239 bytes docs/public/icons/site/typed-32.png | Bin 0 -> 224 bytes docs/public/icons/warning-32.svg | 1 - docs/public/mark-win98-16.png | Bin 262 -> 0 bytes docs/public/mark-win98-32.png | Bin 308 -> 0 bytes 41 files changed, 1614 insertions(+), 42 deletions(-) create mode 100644 docs/.vitepress/theme/components/landing/CodeBlock.vue create mode 100644 docs/.vitepress/theme/components/landing/EditUserDialog.vue create mode 100644 docs/.vitepress/theme/components/landing/InstallCommand.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingCode.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingComponents.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingDemo.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingFaq.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingFooter.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingHero.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingPage.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingStart.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingThemes.vue create mode 100644 docs/.vitepress/theme/components/landing/LandingWhy.vue create mode 100644 docs/.vitepress/theme/components/landing/LookSwitch.vue create mode 100644 docs/.vitepress/theme/components/landing/SectionHeading.vue create mode 100644 docs/.vitepress/theme/components/landing/SiteIcon.vue create mode 100644 docs/.vitepress/theme/components/landing/highlight.ts create mode 100644 docs/.vitepress/theme/components/landing/links.ts create mode 100644 docs/.vitepress/theme/components/landing/useLandingDemo.ts create mode 100644 docs/.vitepress/theme/landing.css delete mode 100644 docs/public/icons/copy-16.svg delete mode 100644 docs/public/icons/error-32.svg delete mode 100644 docs/public/icons/info-32.svg delete mode 100644 docs/public/icons/modern/auto.svg delete mode 100644 docs/public/icons/modern/external.svg delete mode 100644 docs/public/icons/modern/menu.svg delete mode 100644 docs/public/icons/modern/moon.svg delete mode 100644 docs/public/icons/modern/sidebar.svg delete mode 100644 docs/public/icons/modern/sun.svg create mode 100644 docs/public/icons/site/accessible-32.png create mode 100644 docs/public/icons/site/agents-32.png create mode 100644 docs/public/icons/site/behaviour-32.png create mode 100644 docs/public/icons/site/ssr-32.png create mode 100644 docs/public/icons/site/tokens-32.png create mode 100644 docs/public/icons/site/typed-32.png delete mode 100644 docs/public/icons/warning-32.svg delete mode 100644 docs/public/mark-win98-16.png delete mode 100644 docs/public/mark-win98-32.png diff --git a/design/briefs/site.md b/design/briefs/site.md index 2541231a..7f3f8403 100644 --- a/design/briefs/site.md +++ b/design/briefs/site.md @@ -141,10 +141,14 @@ ## План вёрстки -1. **Иконки** — выгрузка и changeset (отдельный PR, библиотека). -2. **Документация** — вернуть стандартную тему VitePress: убрать `SiteLayout`/`ExplorerLayout`, рабочий стол и окна; бренд через `--vp-c-brand-*`, логотип, меню 15px; переключатель «Components: Windows 98 | Modern» в `nav-bar-content-after`, ставит `data-theme` на живые примеры (`DemoBox`), не на страницу; поиск — штатный local search; 404 — штатный с брендом. -3. **Лендинг** — `layout: home` со своим компонентом: секции по макету, живое демо и форма «New user» из кода, `data-theme` на корне лендинга по переключателю, бренд-цвета сайта. -4. **Проверка** — скриншоты 390/1440 в обеих схемах и в Win98-лендинге, контраст, Lighthouse, нет горизонтального скролла. +1. [x] **Иконки** — выгрузка и changeset (отдельный PR, библиотека). +2. [x] **Документация** — вернуть стандартную тему VitePress: убрать `SiteLayout`/`ExplorerLayout`, рабочий стол и окна; бренд через `--vp-c-brand-*`, логотип, меню 15px; переключатель «Components: Windows 98 | Modern» в `nav-bar-content-after`, ставит `data-theme` на живые примеры (`DemoBox`), не на страницу; поиск — штатный local search; 404 — штатный с брендом. +3. [x] **Лендинг** — `layout: home` со своим компонентом: секции по макету, живое демо и форма «New user» из кода, `data-theme` на корне лендинга по переключателю, бренд-цвета сайта. Код — `docs/.vitepress/theme/components/landing/`, значения сайта — `landing.css`. Отступления от макета: + - vermilion как цвет текста — `#c8361c` (у `#d63a1f` на бумаге 4.48:1, чуть ниже AA); кнопки и заголовок — `#d63a1f`; + - «Your theme» задаёт пять значений, а не два: без hover, active и `--color-checked` третья панель была бы наполовину синей, а код под ней — неправдой; + - «A data table page in twenty lines»: `columns` — массив объектов, как его на самом деле принимает `DataTable` (в макете — массив строк); + - данные демо — из `home-users.ts` (адреса `@example.com`, колонка Seats вместо Logins). +4. [ ] **Проверка** — скриншоты 390/1440 в обеих схемах и в Win98-лендинге, контраст, Lighthouse, нет горизонтального скролла. ## Что сохраняется из кода diff --git a/docs/.vitepress/theme/components/landing/CodeBlock.vue b/docs/.vitepress/theme/components/landing/CodeBlock.vue new file mode 100644 index 00000000..5eb42f6b --- /dev/null +++ b/docs/.vitepress/theme/components/landing/CodeBlock.vue @@ -0,0 +1,39 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/EditUserDialog.vue b/docs/.vitepress/theme/components/landing/EditUserDialog.vue new file mode 100644 index 00000000..df48970f --- /dev/null +++ b/docs/.vitepress/theme/components/landing/EditUserDialog.vue @@ -0,0 +1,118 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/InstallCommand.vue b/docs/.vitepress/theme/components/landing/InstallCommand.vue new file mode 100644 index 00000000..f5ec716b --- /dev/null +++ b/docs/.vitepress/theme/components/landing/InstallCommand.vue @@ -0,0 +1,36 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingCode.vue b/docs/.vitepress/theme/components/landing/LandingCode.vue new file mode 100644 index 00000000..1c86ca42 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingCode.vue @@ -0,0 +1,83 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingComponents.vue b/docs/.vitepress/theme/components/landing/LandingComponents.vue new file mode 100644 index 00000000..848553e9 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingComponents.vue @@ -0,0 +1,193 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingDemo.vue b/docs/.vitepress/theme/components/landing/LandingDemo.vue new file mode 100644 index 00000000..7e2334db --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingDemo.vue @@ -0,0 +1,124 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingFaq.vue b/docs/.vitepress/theme/components/landing/LandingFaq.vue new file mode 100644 index 00000000..51d6fa29 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingFaq.vue @@ -0,0 +1,62 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingFooter.vue b/docs/.vitepress/theme/components/landing/LandingFooter.vue new file mode 100644 index 00000000..e106bde4 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingFooter.vue @@ -0,0 +1,82 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingHero.vue b/docs/.vitepress/theme/components/landing/LandingHero.vue new file mode 100644 index 00000000..61fc39b6 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingHero.vue @@ -0,0 +1,67 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingPage.vue b/docs/.vitepress/theme/components/landing/LandingPage.vue new file mode 100644 index 00000000..39afc1c5 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingPage.vue @@ -0,0 +1,34 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingStart.vue b/docs/.vitepress/theme/components/landing/LandingStart.vue new file mode 100644 index 00000000..375e68bf --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingStart.vue @@ -0,0 +1,47 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingThemes.vue b/docs/.vitepress/theme/components/landing/LandingThemes.vue new file mode 100644 index 00000000..4b472dfd --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingThemes.vue @@ -0,0 +1,114 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LandingWhy.vue b/docs/.vitepress/theme/components/landing/LandingWhy.vue new file mode 100644 index 00000000..137e70e7 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LandingWhy.vue @@ -0,0 +1,93 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/LookSwitch.vue b/docs/.vitepress/theme/components/landing/LookSwitch.vue new file mode 100644 index 00000000..09b712ca --- /dev/null +++ b/docs/.vitepress/theme/components/landing/LookSwitch.vue @@ -0,0 +1,60 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/SectionHeading.vue b/docs/.vitepress/theme/components/landing/SectionHeading.vue new file mode 100644 index 00000000..5e34994e --- /dev/null +++ b/docs/.vitepress/theme/components/landing/SectionHeading.vue @@ -0,0 +1,41 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/SiteIcon.vue b/docs/.vitepress/theme/components/landing/SiteIcon.vue new file mode 100644 index 00000000..c50ec4b3 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/SiteIcon.vue @@ -0,0 +1,59 @@ + + + diff --git a/docs/.vitepress/theme/components/landing/highlight.ts b/docs/.vitepress/theme/components/landing/highlight.ts new file mode 100644 index 00000000..617553b2 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/highlight.ts @@ -0,0 +1,66 @@ +/** + * Syntax colours for the landing page's few code samples: TypeScript, a Vue + * template and two CSS imports. + * + * Not a general highlighter, and deliberately so. The samples are fixed and + * short; a dozen lines of rules colour them as the Figma page does (`site/code-*`: + * keyword, string, number, comment, tag, attribute) in each theme, where + * VitePress's highlighter would bring its own palette and only one of it. + */ +export type TokenKind = 'plain' | 'keyword' | 'string' | 'number' | 'comment' | 'tag' | 'attr' + +export interface Token { + kind: TokenKind + text: string +} + +const KEYWORDS = new Set(['import', 'from', 'export', 'const', 'let', 'return']) + +/** + * One rule per token, tried in order at the current position. A tag opens + * «inside a tag», where a bare word is an attribute; `>` closes it — across + * lines, since a component's attributes are one per line. + */ +const RULES: [RegExp, (text: string, inTag: boolean) => TokenKind][] = [ + [/\/\/[^\n]*/y, () => 'comment'], + [/'[^'\n]*'|"[^"\n]*"|`[^`\n]*`/y, () => 'string'], + [/<\/?[A-Za-z][\w.-]*/y, () => 'tag'], + [/\/?>/y, (_, inTag) => (inTag ? 'tag' : 'plain')], + // `@import`, not the `@beta` of a package's tag. + [/(? 'keyword'], + [ + /[:@]?[A-Za-z_$][\w$.-]*(?::[\w-]+)?/y, + (text, inTag) => (inTag ? 'attr' : KEYWORDS.has(text) ? 'keyword' : 'plain'), + ], + [/\d+(?:\.\d+)?/y, () => 'number'], +] + +export function highlight(code: string): Token[][] { + let inTag = false + return code.split('\n').map((line) => { + const tokens: Token[] = [] + const push = (kind: TokenKind, text: string) => { + const last = tokens.at(-1) + if (last?.kind === kind) last.text += text + else tokens.push({ kind, text }) + } + + let at = 0 + outer: while (at < line.length) { + for (const [pattern, kindOf] of RULES) { + pattern.lastIndex = at + const match = pattern.exec(line) + if (!match) continue + const text = match[0] + const kind = kindOf(text, inTag) + if (kind === 'tag') inTag = !text.endsWith('>') + push(kind, text) + at += text.length + continue outer + } + push('plain', line[at] ?? '') + at += 1 + } + return tokens + }) +} diff --git a/docs/.vitepress/theme/components/landing/links.ts b/docs/.vitepress/theme/components/landing/links.ts new file mode 100644 index 00000000..6ae2f520 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/links.ts @@ -0,0 +1,14 @@ +/** Where the landing page points outside the docs. */ +const REPO = 'https://github.com/NikolaiKushner/rowkit' + +export const LINKS = { + github: REPO, + npm: 'https://www.npmjs.com/package/rowkit', + storybook: 'https://storybook.rowkit.dev', + changelog: `${REPO}/releases`, +} + +/** The code behind what the page shows running. */ +export const SOURCE = { + demo: `${REPO}/blob/main/docs/.vitepress/theme/components/landing/LandingDemo.vue`, +} diff --git a/docs/.vitepress/theme/components/landing/useLandingDemo.ts b/docs/.vitepress/theme/components/landing/useLandingDemo.ts new file mode 100644 index 00000000..ce17db10 --- /dev/null +++ b/docs/.vitepress/theme/components/landing/useLandingDemo.ts @@ -0,0 +1,102 @@ +import { computed, ref, watch } from 'vue' +import { useClientSort } from 'rowkit' +import type { DataTableColumn, DataTableSort, FilterChip } from 'rowkit' +import { homeUsers, type HomeUser } from '../home-users' + +/** + * The live table at the top of the landing page: a Users list with search, a + * status filter, sorting, selection and paging. The pencil edits a row and the + * bin removes it — from this tab's copy of the list, nowhere else. + */ +export function useLandingDemo() { + const people = ref([...homeUsers]) + + const search = ref('') + // Seeded so the table opens with a chip, as the Figma hero draws it. + const status = ref('active') + const sort = ref | undefined>({ key: 'name', direction: 'asc' }) + // Alan Turing and Barbara Liskov: both on page one under the seeded sort. + const selected = ref([3, 4]) + const page = ref(1) + const pageSize = ref(25) + + const filtered = computed(() => { + const query = search.value.trim().toLowerCase() + return people.value.filter((row) => { + if (status.value && row.status !== status.value) return false + if (!query) return true + return row.name.toLowerCase().includes(query) || row.email.toLowerCase().includes(query) + }) + }) + + // Pagination never moves the page itself; the application does, so a + // narrowed list does not leave the reader on an empty page. + watch([search, status, pageSize], () => { + page.value = 1 + }) + + const chips = computed(() => + status.value ? [{ id: 'status', label: 'Status', value: label[status.value] }] : [] + ) + + const sorted = useClientSort(filtered, sort, columns) + + const pageRows = computed(() => { + const start = (page.value - 1) * pageSize.value + return sorted.value.slice(start, start + pageSize.value) + }) + + function removeFilter(): void { + status.value = undefined + } + + function clearFilters(): void { + search.value = '' + status.value = undefined + } + + function remove(id: number): void { + people.value = people.value.filter((row) => row.id !== id) + selected.value = selected.value.filter((other) => other !== id) + } + + /** Writes an edited row back, by id. */ + function update(user: HomeUser): void { + people.value = people.value.map((row) => (row.id === user.id ? user : row)) + } + + return { + search, + status, + sort, + selected, + page, + pageSize, + filtered, + chips, + pageRows, + removeFilter, + clearFilters, + remove, + update, + } +} + +export const columns: DataTableColumn[] = [ + { key: 'name', header: 'Name', sortable: true, width: '170px' }, + { key: 'email', header: 'Email', width: '220px' }, + { key: 'role', header: 'Role', sortable: true, width: '96px' }, + { key: 'status', header: 'Status', width: '104px' }, + { key: 'seats', header: 'Seats', sortable: true, numeric: true, width: '72px' }, + { id: 'actions', header: 'Actions', headerSrOnly: true, width: '64px' }, +] + +/** The phone's table (Figma Landing, 390): name and status. */ +export const phoneColumns = columns.filter( + (column) => 'key' in column && (column.key === 'name' || column.key === 'status') +) + +/** Status as the badges read it. */ +export const label = { active: 'Active', invited: 'Invited', suspended: 'Suspended' } as const + +export const tone = { active: 'success', invited: 'warning', suspended: 'danger' } as const diff --git a/docs/.vitepress/theme/landing.css b/docs/.vitepress/theme/landing.css new file mode 100644 index 00000000..759a2d55 --- /dev/null +++ b/docs/.vitepress/theme/landing.css @@ -0,0 +1,162 @@ +/* + * rowkit.dev's landing page (Figma Site v2, Landing): the site's own values + * for it, per theme. Everything else on the page is a rowkit token. + * + * Windows 98 is the page without an attribute, as it is for rowkit: VGA + * colours, no brand on the page itself. The modern theme carries the brand — + * paper or night under the page, vermilion on the accents and the main + * buttons (Figma Brand — rowkit, Colour). + */ +:root { + --lp-page: var(--color-background); + /* The few words a section stresses: VGA navy. */ + --lp-accent: #000080; + --lp-link: var(--color-link); + + /* `site/code-*` in Figma, Windows 98: the VGA palette on white. */ + --lp-code-fg: #000; + --lp-code-bg: #fff; + --lp-code-keyword: #0000ff; + --lp-code-string: #800000; + --lp-code-number: #008080; + --lp-code-comment: #008000; + --lp-code-tag: #000080; + --lp-code-attr: #008080; +} + +:root[data-theme='modern'] { + --lp-page: #fafaf8; + /* + * Vermilion as text is a shade deeper than the brand's own (4.5:1 on paper, + * a hair short): 5.0:1 on paper, 5.3:1 on white — the docs' link colour. + */ + --lp-accent: #c8361c; + --lp-accent-soft: #fbe7e2; + /* Text on the soft fill (the hero's badge): vermilion-strong, 5.3:1. */ + --lp-accent-on-soft: #b32e16; + --lp-link: var(--lp-accent); + + --lp-code-fg: #1d1d1f; + --lp-code-bg: #fff; + --lp-code-keyword: #9b2393; + --lp-code-string: #c41a16; + --lp-code-number: #1c00cf; + --lp-code-comment: #5d6c79; + --lp-code-tag: #0b4f79; + --lp-code-attr: #815f03; +} + +:root[data-theme='modern'][data-color-scheme='dark'] { + --lp-page: #0b0b0d; + /* Vermilion itself is 4.2:1 on night: fine for the headline, short for small text. */ + --lp-accent: #f0846e; + --lp-accent-soft: rgb(214 58 31 / 0.18); + --lp-accent-on-soft: #f0846e; + + --lp-code-fg: #f5f5f7; + --lp-code-bg: #1c1c1e; + --lp-code-keyword: #fc5fa3; + --lp-code-string: #fc6a5d; + --lp-code-number: #d0bf69; + --lp-code-comment: #7f8c98; + --lp-code-tag: #5dd8ff; + --lp-code-attr: #ffa14f; +} + +/* + * The main buttons in vermilion: the modern theme's primary button, its + * colour tokens set to the brand's on the button itself. White on vermilion: 4.7:1. + */ +:root[data-theme='modern'] .lp-brand { + --color-control-primary: #d63a1f; + --color-control-primary-hover: #c43319; + --color-control-primary-active: #b32e16; + --color-control-primary-foreground: #fff; +} + +.lp-headline-accent { + color: inherit; +} + +:root[data-theme='modern'] .lp-headline-accent { + color: #d63a1f; +} + +/* Code: 14/22 in the modern theme; VT323 reads small, so a size up in Windows 98. */ +.lp-code { + font-size: 14px; + line-height: 22px; +} + +:root:not([data-theme='modern']) .lp-code { + font-size: 18px; + line-height: 22px; +} + +.lp-code-keyword { + color: var(--lp-code-keyword); +} + +.lp-code-string { + color: var(--lp-code-string); +} + +.lp-code-number { + color: var(--lp-code-number); +} + +.lp-code-comment { + color: var(--lp-code-comment); +} + +.lp-code-tag { + color: var(--lp-code-tag); +} + +.lp-code-attr { + color: var(--lp-code-attr); +} + +/* + * VitePress's chrome on the landing page. Unlayered, like VitePress's own + * component styles, which these have to beat. + * + * The nav bar takes the page's colour, so it reads as one surface with the + * hero: paper or night in the modern theme, silver in Windows 98. Windows 98 + * has no dark scheme; in VitePress's dark mode its nav bar keeps its own dark + * colour, and is solid even at the top of the page, where VitePress would make + * it transparent over the silver. + */ +html[data-theme='modern']:has(.lp), +html:not(.dark):has(.lp) { + --vp-c-bg: var(--lp-page); + --vp-nav-bg-color: var(--lp-page); +} + +html.dark:not([data-theme='modern']) .VPNavBar.home.top { + background-color: var(--vp-nav-bg-color); +} + +/* The landing ends in its own footer: no gap under it for VitePress's. */ +.VPContent .VPHome:has(.lp) { + margin-bottom: 0; +} + +/* + * Under the nav bar once the page scrolls: VitePress's divider in the modern + * theme, a bevel's lower edge in Windows 98 (Figma SiteV2/Header). + */ +html[data-theme='modern'] .VPNavBar.home:not(.top) { + box-shadow: 0 1px var(--vp-c-divider); +} + +html:not([data-theme='modern']):not(.dark) .VPNavBar.home:not(.top) { + box-shadow: + 0 1px #808080, + 0 2px #fff; +} + +/* Code in a line of text: VT323 is small for its size, so a step up in Windows 98. */ +:root:not([data-theme='modern']) .lp :not(pre) > code:not(.lp-code) { + font-size: 1.25em; +} diff --git a/docs/.vitepress/theme/tokens.css b/docs/.vitepress/theme/tokens.css index 81ae599c..9a0b66b3 100644 --- a/docs/.vitepress/theme/tokens.css +++ b/docs/.vitepress/theme/tokens.css @@ -11,6 +11,7 @@ @import 'tailwindcss'; @import '../../../packages/ui/src/styles/index.css'; @import './docs.css'; +@import './landing.css'; /* * The live examples are drawn in one rowkit theme at a time, set on diff --git a/docs/index.md b/docs/index.md index 55ec75fb..21215608 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,16 +4,13 @@ title: rowkit # The browser tab: the brand's line from the link preview (og-image.png). titleTemplate: ':title — Vue 3 components for product interfaces' description: A professional Vue 3 toolkit — the components a product interface is built from. - -hero: - name: rowkit - text: Vue 3 components for product interfaces - tagline: A professional toolkit — typed, accessible, themeable. Windows 98 and modern in the box; your own look in one call. - actions: - - theme: brand - text: Get started - link: /installation - - theme: alt - text: Browse components - link: /components/ +# The landing page is its own: no article styles round it, and its own footer. +markdownStyles: false +footer: false --- + + + + diff --git a/docs/public/icons/copy-16.svg b/docs/public/icons/copy-16.svg deleted file mode 100644 index 4bea8bde..00000000 --- a/docs/public/icons/copy-16.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/icons/error-32.svg b/docs/public/icons/error-32.svg deleted file mode 100644 index 95ea9dce..00000000 --- a/docs/public/icons/error-32.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/icons/info-32.svg b/docs/public/icons/info-32.svg deleted file mode 100644 index f03daacf..00000000 --- a/docs/public/icons/info-32.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/icons/modern/auto.svg b/docs/public/icons/modern/auto.svg deleted file mode 100644 index a146f9e2..00000000 --- a/docs/public/icons/modern/auto.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/docs/public/icons/modern/external.svg b/docs/public/icons/modern/external.svg deleted file mode 100644 index 3ecbbee3..00000000 --- a/docs/public/icons/modern/external.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/modern/menu.svg b/docs/public/icons/modern/menu.svg deleted file mode 100644 index fffe8f4f..00000000 --- a/docs/public/icons/modern/menu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/modern/moon.svg b/docs/public/icons/modern/moon.svg deleted file mode 100644 index 99289f3b..00000000 --- a/docs/public/icons/modern/moon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/modern/sidebar.svg b/docs/public/icons/modern/sidebar.svg deleted file mode 100644 index 6fe79432..00000000 --- a/docs/public/icons/modern/sidebar.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/modern/sun.svg b/docs/public/icons/modern/sun.svg deleted file mode 100644 index 1a6c52c9..00000000 --- a/docs/public/icons/modern/sun.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/site/accessible-32.png b/docs/public/icons/site/accessible-32.png new file mode 100644 index 0000000000000000000000000000000000000000..0450674dee09f41f2434a9525f8388608448a4b1 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zGo_Pfhh zatqHlT)E3s&A`Y};nLvnu0B7y@89!;gT9gxUn}&vBMO|^w$!aZm&jPoxMMQIV<8EJ U|0b+IfR-_My85}Sb4q9e0Ls=$y8r+H literal 0 HcmV?d00001 diff --git a/docs/public/icons/site/agents-32.png b/docs/public/icons/site/agents-32.png new file mode 100644 index 0000000000000000000000000000000000000000..9f99b71fcc2640f95ed87a111dcec9898afbf2c5 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zfb7Pn?h{S(8`o&S27;LC>!Oep`1mP4cF>Cmwa(zVXatx^Y*#vExqFHUansbl&?K%f1^U#Sv;Ws&F%;P z`mXZ6=P2!Cl*0xN%T z{1{#O= SY@Z8s9D}E;pUXO@geCyDJYM4f literal 0 HcmV?d00001 diff --git a/docs/public/icons/site/ssr-32.png b/docs/public/icons/site/ssr-32.png new file mode 100644 index 0000000000000000000000000000000000000000..b962c545d7d31c129d9eafe0e0e3ed7274f24cbe GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG z&vLHzzSQ%36=s(1 zF)-fKxvK1A4VTH4elOpot{v6l-6x*zt`zMkySL+Yg-R^ryT&GqubulZi6$`4VcMm5 hcS7C?TL+Gt4AOEkI+hjB_<$BOc)I$ztaD0e0sv3-Q;Yxr literal 0 HcmV?d00001 diff --git a/docs/public/icons/site/tokens-32.png b/docs/public/icons/site/tokens-32.png new file mode 100644 index 0000000000000000000000000000000000000000..80c91d35456c6bc2d0cd44ea6af249271c1d4112 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG z&bt=-^ZT=nb&PBV e4UB4v2RL8b@Eh3K<#hsWW$<+Mb6Mw<&;$UH&rn(b literal 0 HcmV?d00001 diff --git a/docs/public/icons/site/typed-32.png b/docs/public/icons/site/typed-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5eec1ea063d04c971485a7e7e9ac5d4d82d6df GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG z*v0D*1Fp|D%96351Myu_SWye7vESs>-@L> z=R(gjFmWgte0cr${o@ts3MI2INB#HeJ}tXII;cay;eeBV#KZ6L4CgdtZYj@H_u

gTe~DWM4fP#a6W literal 0 HcmV?d00001 diff --git a/docs/public/icons/warning-32.svg b/docs/public/icons/warning-32.svg deleted file mode 100644 index caf509de..00000000 --- a/docs/public/icons/warning-32.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/mark-win98-16.png b/docs/public/mark-win98-16.png deleted file mode 100644 index e8391606582cdefa1d106fdb8da671aefc7c6513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBK3|DzL`iUdT1k0gQ7VI5W_oVoyp7Y6fijJr zE{-7;j8`WwQp zxT-@En+27H+{30%ciQ^QF+$UoN!G!j^lzlZv({N#Dq6P6y-d(lOuNC9sh?pT)zG-L z(ft0?-;#eq66^z~9B7T_u929SXDA}?#!^1FyzPaV+=`5da-d@vJYD@<);T3K0RY_c BR7L;* diff --git a/docs/public/mark-win98-32.png b/docs/public/mark-win98-32.png deleted file mode 100644 index 19c5a926e064d5eeb72404a48985e198f02cac47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zZRt-&Y8v>U8nHhu0se3&!vJ9~VOVR5u>HUsM`opL{&Sz?p&8SFwrIg_8= znJy*3D0tyDqfJ&0Q^m<8^Pd0F3Sw4LV0teUv7hw>>ox`98@Fs%HgF3uUVrDw^6iiy yFF%`d^}~LSIlN_DKHPVn=6q Date: Fri, 9 Oct 2026 12:43:00 +0300 Subject: [PATCH 6/6] =?UTF-8?q?Site:=20the=20checks=20=E2=80=94=20code=20c?= =?UTF-8?q?olours,=20hydration,=20the=20nav=20bar=20at=201280.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lighthouse over six pages, phone and desktop: Accessibility and SEO 100 everywhere. What it and the browser found on the way: - Code blocks are GitHub's high-contrast light and default dark: the plain github-light has red, green and orange at 3.2–4.3:1 on VitePress's #f6f6f7, github-dark's comments 3.8:1. Inline code is brand-2 (brand-1 is 4.3:1 on its grey), the language label text-2. - The «Your theme» panel and the theme builder hand their