Организацию на git.tnsu.kz переименовали в tansu. Адрес реестра @tnsu в .npmrc и resolved в package-lock.json приведены к новому имени. Gitea принимает оба написания, так что сборка работает и до влития. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: #2
17 KiB
@tnsu/ui-kit — память проекта
Единый пакет UI-компонентов для SPFx web part'ов ТНСУ + Storybook как витрина. Этот файл — источник правды по решениям, плану и прогрессу. Обновлять по ходу работы.
Зачем проект
У команды много SPFx web part'ов в разных репозиториях (request-portal, бывший
1c-ticket-form, pto-tracker и др.). В каждом дизайн разъезжается, компоненты копируются
и пишутся заново. Цель — вынести компоненты в один npm-пакет, подключать его в любую
web part через npm i, и держать единый вид через дизайн-токены.
Источник компонентов: request-portal/src/webparts/requestPortal/shared/ui/ — там уже
~30 чистых компонентов на SCSS-модулях с barrel-экспортом (ui/index.ts). Это база.
Ключевые архитектурные решения
- Гибридный подход: отдельный репозиторий
tnsu-ui-kit(пакет + Storybook), публикуется в GitLab Package Registry. Существующие web part'ы остаются в своих репах и просто ставят пакет. НЕ монорепо (не хотим переселять существующие проекты). - Сборка — Vite (library mode), не tsup. Причина: компоненты на
.module.scss, Vite понимает SCSS-модули из коробки и он же движок Storybook → один инструмент. - React/Fluent/tslib — только
peerDependencies(React 17, @fluentui/react 8, tslib). Защита от двух React в бандле («Invalid hook call»). Локально те же версии стоят как devDependencies для сборки и Storybook. - Дизайн-токены (
tokens.scss: цвета, отступы, радиусы) — единый рычаг общего вида. Компоненты тянут значения оттуда, а не хардкодят. - Скоуп имени
@tnsu— сверить с именем группы в GitLab на шаге публикации. Пока имя ни на что не влияет, переименование = find-replace. - Что переносим: только чистые UI-примитивы (Button, TextInput, FormCard, Select,
ItemsTable, PeopleField...). НЕ переносим бизнес-логику:
approval/,pnp/,dictionary/, сервисы SharePoint — они привязаны к спискам. Возможно позже отдельный пакет@tnsu/sp-core. - Расширение через пропсы, не форки. Если в старом проекте компонент выглядел иначе —
добавляем
variant/sizeв ui-kit, а не плодим копии. - Версии инструментов синхронны с SPFx: TypeScript пинить на
~5.8.0(как в request-portal). Без пинаnpm i typescriptставит TS 7 (нативный компилятор без JS Compiler API) — vite-plugin-dts падает. То же правило для React/Fluent.
План (маршрут)
- Скелет репо — папка, git, package.json, зависимости
- Сборка (Vite) — vite.config.ts, пакет собирается в dist/
- Первый компонент — Button перенесён, сборка чистая
- Дизайн-токены — src/tokens/_tokens.scss перенесён из request-portal
- Storybook — витрина работает (SB 10.4 + Vite), Button виден со всеми вариантами
- Публикация — @tnsu/ui-kit@0.0.1 опубликован в Gitea (это Gitea 1.26.4, НЕ GitLab!)
- [~] Потребление проверено ПОЛНОСТЬЮ: request-portal собрался (heft webpack ок) + кнопки из пакета отрисовались в SharePoint workbench со стилями (accent-золото, secondary-рамки). CSS из пакета применяется в рантайме. Осталась публикация в registry.
ВАЖНО про потребление (проверено 2026-08-13):
- Тест через локальный тарбол (
npm pack→npm i ../tnsu-ui-kit/*.tgz), НЕ npm link (link тащит второй React → Invalid hook call). - CSS подключается в SPFx через
import '@tnsu/ui-kit/styles.css'в корневом компоненте веб-парта (RequestPortal.tsx). SPFx-webpack (5.95) обрабатывает .css из node_modules — ОК. - Заменён Button в SubmitActions.tsx (
../Button/Button→@tnsu/ui-kit). Правки НЕ коммитить, пока пакет не опубликован в registry (сейчас ссылается на file:-тарбол).
Известный tech-debt: Sass @import устарел (Dart Sass 3.0). Мигрировать токены и
модули на @use/@forward — отдельной задачей, сейчас работает с варнингом.
Прогресс / журнал
- Шаг 1 готов: создан репо
C:\Users\gorodetskiy.timur\Documents\Projects\tnsu-ui-kit,git init, болванка package.json. - Шаг 2 в работе: переписан package.json (@tnsu/ui-kit, exports, peerDeps, Vite-скрипты), ставятся devDependencies, добавлен .gitignore.
План переноса компонентов (инвентаризация + решения, 2026-08-13)
Источник — request-portal shared/ui/, shared/approval/ui/, shared/dictionary/ui/,
и (фаза 2) components/.
Группа 1 — чистая презентация, переносим КАК ЕСТЬ
Alert, FormCard, FormGrid, FormField, TextInput, TextArea, PhoneInput, MoneyInput, Select, Toggle, CollapseSection, FormActions, Icon (+ IconSprite/iconNames), RequestNumber, StatusBadge, ErrorOverlay, FormMode (FormModeProvider + useFormReadOnly).
Группа 2 — презентация с нюансами (решения приняты)
- ConfirmDialog (+ confirmModal), FileDropzone, useFlipRows → переносим КАК ЕСТЬ (проверено: чисты).
- AttachmentList → переносим, но РАЗВЯЗАТЬ тип: вместо
import { IAttachmentInfo }из attachments.service объявить локальный{ url: string; fileName: string }. - PrintButton → переносим КАК ЕСТЬ; сохраняем соглашение классов
.rp-print-root/.rp-no-print— задокументировать в README, чтобы потребители их соблюдали. - ItemsTable → [DataTable СДЕЛАН в 0.4.0] generic
DataTable<T>в kit: columns ({key,title,width,align,render}) + rows + rowKey + onRowClick + emptyText. Editable — через render-инпуты (без спец-режима). Обвязка (поиск/пагинация/Excel/add-remove) — в проекте. overflow-x на мобилке. Осталось перевести на него RequestsTableView и ItemsTable в портале (обвязку оставить). Старый план ниже — для истории: - ItemsTable → ОБОБЩАЕМ в generic
DataTable(колонки через проп: {key,title,width?, align?,editable?,render?}, value/onChange/readOnly). Excel-вставка — ОПЦИОНАЛЬНАЯ/ подключаемая (не хардкодить parseExcelRows под ТМЦ; проект даёт свой маппинг/парсер). ТМЦ-таблица = конфиг колонок поверх DataTable. Это ОТДЕЛЬНАЯ design-задача (не copy-paste), делать позже, сверяя абстракцию со 2-м реальным примером таблицы.
Группа 3 — бизнес-логика / SharePoint, НЕ в ui-kit (как есть)
PeopleField/PeopleFieldMulti, UserPicker, InitiatorLine, SubmitActions, useRequestSubmit, DecisionPanel, ApprovalMatrix, DepartmentPicker, ProjectPicker.
- SubmitActions — на обсуждении: можно «очистить» от useRequestSubmit → презентационный → в ui-kit.
- Пикеры — возможно позже РАЗДЕЛИТЬ: презентационный UI (ui-kit) + data-обёртка (проект).
- Логику согласования — возможно в отдельный пакет @tnsu/approval (или @tnsu/sp-core). На будущее.
Адаптивность (жёсткое требование) — базовый проход СДЕЛАН
Часть DoD каждого компонента. Основа: в tokens.scss брейкпоинты
$bp-xs:350 / $bp-sm:640 / $bp-md:768 / $bp-lg:1024 / $bp-xl:1280 + миксины below()/above().
Проход по компонентам (в 0.2.0):
- FormGrid / CollapseSection — 2 колонки → 1 на < sm (через below($bp-sm)).
- TextInput/TextArea/Select — сделаны резиновыми (width:100% + box-sizing) — раньше зависели от глобального стиля портала.
- FormCard — padding 32→18 на < sm.
- FormActions — кнопки в столбец на всю ширину на < sm (поля согласованы с FormCard 18px).
- ConfirmDialog — уже адаптивна (overlay padding + max-width).
- Sidebar — рейл ↔ выезжающий drawer + SidebarToggle (бургер, виден только < md).
- Мелкие (Alert, Icon, Toggle, Button, AttachmentList, FileDropzone, PrintButton) — уже резиновые. ItemsTable→скролл сделаем вместе с обобщением в DataTable.
Группа 4 — навигация / app shell (ФАЗА 2)
- [СДЕЛАНО, не опубликовано] Sidebar — обобщён (items/brand пропсами, id=string) + адаптивный: десктоп-рейл со сворачиванием, < 768px — выезжающий drawer + затемнение (mobileOpen/onMobileClose, бургер рисует родитель). Собирается, в index экспортирован. Tabs (из CategoryTabs), Breadcrumbs (новый), Sidebar (из SideNav), AppLayout (из RequestPortal), Drawer(?). Сначала «расцепить» от роутинга портала → сделать controlled (items + activeId + onSelect), потом переносить. Даёт новым приложениям готовую оболочку.
Миграция request-portal (в процессе)
- [СДЕЛАНО] Формы переведены на пакет через переключение barrel shared/ui/index.ts (перенесённые компоненты реэкспортятся из @tnsu/ui-kit). FormMode объединён на пакетный.
- [СДЕЛАНО] Чистка: 12 прямых импортеров (SideNav, табы, Drawer, RequestGrid, пикеры, unsavedChanges, RequestPortal→IconSprite, ItemsTable, SubmitActions) → @tnsu/ui-kit; удалены 20 локальных папок-дублей. В shared/ui осталось: ItemsTable, PeopleField, SubmitActions, ErrorOverlay, InitiatorLine (+useRequestSubmit, index). Heft собирается.
- [СДЕЛАНО] B: локальный components/SideNav заменён на пакетный Sidebar + SidebarToggle. В RequestPortal: navItems (с counts), состояние mobileNavOpen, бургер в шапке main (.mobileNav, виден < 768px). Shell CSS: убран старый перелом 900px; на < 768 .side схлопывается (drawer position:fixed), main на всю ширину. SideNav удалён. Heft ок.
Badge (0.3.0) — паттерн «презентация в kit, смысл в проекте»
Generic Badge в kit (variant: neutral/muted/info/success/warning/danger/primary). Портальный StatusBadge переписан в тонкий маппер REQUEST_STATUS→variant поверх Badge; StatusBadge.module.scss удалён. Это шаблон для будущего разделения пикеров (group 3): вид в kit, привязка к данным в проекте.
Stepper + тесты (не опубликовано, будет 0.5.0)
- Добавлен generic
Stepper(степпер шагов) — эталон kps Breadcrumbs, но чистый (без шапки), логика навигации по индексу (highestReachableIndex, вынесена в stepperLogic.ts — тестируемо). Кликабельны шаги до highestId включительно (защита от прыжка вперёд). Адаптивный (подписи прячутся < sm). Экспортирован из index. - Заведён юнит-тест:
npm test(vitest, node-окружение, vitest.config.ts — ОТДЕЛЬНО от storybook-browser-проекта в vite.config). Первый тест stepperLogic.test.ts (4 кейса).*.test.tsисключены из сборки пакета (vite dts exclude + tsconfig exclude). - ПРАКТИКА тестов: чистую логику выносить в отдельные файлы (без React/scss) и покрывать в node.
Порядок работ
- [СДЕЛАНО в 0.1.0] Быстрая пачка (20 компонентов): вся Группа 1 (кроме ErrorOverlay/ StatusBadge — оказались связаны с diagnostics/statuses, отложены) + ConfirmDialog/ FileDropzone/useFlipRows + AttachmentList (тип развязан на локальный {url,fileName}) + PrintButton. Сборка чистая, типы ок.
- DataTable — отдельной design-задачей.
- Группа 3/4 — по мере надобности.
Как мигрировать старый проект (памятка на будущее)
npm i @tnsu/ui-kit— ничего не ломается, пакет просто лежит рядом.- Заменяем импорты по одному компоненту/форме:
../shared/ui→@tnsu/ui-kit. Пропсы те же, JSX не меняется. Старый и новый компонент могут сосуществовать. - Проверили форму → удалили старый компонент из
shared/ui. - Повторяем, пока
shared/uiне опустеет. - Перед миграцией проекта проверить: его версии React/Fluent совместимы с peerDeps ui-kit.
Публикация (Gitea, НЕ GitLab!)
- Хостинг git — Gitea 1.26.4 на https://git.tnsu.kz (не GitLab). Registry npm привязан к ВЛАДЕЛЬЦУ, не к проекту. Никакого "project ID" не нужно.
- Адрес registry:
https://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/ .npmrcв проекте (можно коммитить, НЕ секрет):@tnsu:registry=https://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/- Токен (СЕКРЕТ) — в
~/.npmrc(C:\Users\gorodetskiy.timur.npmrc), в git НЕ попадает://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/:_authToken=<TOKEN> - Токен создаётся в Gitea: аватар → Настройки → Приложения → права
package= Чтение и запись. - Публикация:
npm run build && npm publish. Бамп версии в package.json перед каждой публикацией (npm не даёт перезаписать уже опубликованную версию). - Потребителю (request-portal и др.) нужен такой же
@tnsu:registryв его.npmrc+ свой токен на чтение пакетов. - ПРОВЕРЕНО: коллега (другой пользователь Gitea) успешно установил @tnsu/ui-kit со своим read-токеном — кросс-доступ к личным пакетам работает, переносить в организацию пока НЕ нужно.
Окружение
- Node: v22.x (как в SPFx: >=22.14 <23)
- React 17.0.1, @fluentui/react ^8.106.4, tslib 2.3.1
- GitLab: https://git.tnsu.kz/timur.gorodetskiy/tnsu-ui-kit.git