# @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. ## План (маршрут) 1. [x] Скелет репо — папка, git, package.json, зависимости 2. [x] Сборка (Vite) — vite.config.ts, пакет собирается в dist/ 3. [x] Первый компонент — Button перенесён, сборка чистая 4. [x] Дизайн-токены — src/tokens/_tokens.scss перенесён из request-portal 5. [x] Storybook — витрина работает (SB 10.4 + Vite), Button виден со всеми вариантами 6. [x] Публикация — @tnsu/ui-kit@0.0.1 опубликован в Gitea (это Gitea 1.26.4, НЕ GitLab!) 7. [~] Потребление проверено ПОЛНОСТЬЮ: 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` в 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. ### Порядок работ 1. [СДЕЛАНО в 0.1.0] Быстрая пачка (20 компонентов): вся Группа 1 (кроме ErrorOverlay/ StatusBadge — оказались связаны с diagnostics/statuses, отложены) + ConfirmDialog/ FileDropzone/useFlipRows + AttachmentList (тип развязан на локальный {url,fileName}) + PrintButton. Сборка чистая, типы ок. 2. DataTable — отдельной design-задачей. 3. Группа 3/4 — по мере надобности. ## Как мигрировать старый проект (памятка на будущее) 1. `npm i @tnsu/ui-kit` — ничего не ломается, пакет просто лежит рядом. 2. Заменяем импорты по одному компоненту/форме: `../shared/ui` → `@tnsu/ui-kit`. Пропсы те же, JSX не меняется. Старый и новый компонент могут сосуществовать. 3. Проверили форму → удалили старый компонент из `shared/ui`. 4. Повторяем, пока `shared/ui` не опустеет. 5. Перед миграцией проекта проверить: его версии 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=` - Токен создаётся в 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