elcrm 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/index.js +548 -33
  2. package/dist/vite/concat-css.js +13 -1
  3. package/dist/vite/postbuild.js +13 -1
  4. package/package.json +4 -3
  5. package/templates/elcrm-cursor/AGENTS.md +3 -1
  6. package/templates/elcrm-cursor/rules/elcrm-jsdoc.mdc +89 -0
  7. package/templates/elcrm-cursor/rules/elcrm-lib.mdc +1 -0
  8. package/templates/elcrm-cursor/rules/elcrm-packages.mdc +2 -2
  9. package/templates/elcrm-cursor/rules/elcrm-theme.mdc +1 -0
  10. package/templates/elcrm-cursor/rules/elcrm-tokens.mdc +40 -0
  11. package/templates/elcrm-cursor/rules/elcrm-ui.mdc +1 -0
  12. package/templates/elcrm-cursor/rules/elcrm.mdc +1 -1
  13. package/templates/elcrm-docs/CLI.elCRM.md +4 -3
  14. package/templates/elcrm-docs/COMPONENTS.elCRM.md +51 -46
  15. package/templates/panel/web/src/App.tsx +15 -1
  16. package/templates/panel/web/src/component/Header.tsx +19 -6
  17. package/templates/panel/web/src/lazyRoute.ts +6 -0
  18. package/templates/panel/web/src/lib/routeKey.ts +8 -1
  19. package/templates/panel/web/src/lib/theme.ts +20 -3
  20. package/templates/panel/web/src/modules/Account/index.tsx +12 -8
  21. package/templates/panel/web/src/modules/Account/router.sections.ts +5 -3
  22. package/templates/panel/web/src/modules/Account/sections/Profile.tsx +9 -4
  23. package/templates/panel/web/src/modules/Account/sections/Sessions.tsx +1 -1
  24. package/templates/panel/web/src/modules/Home/index.tsx +11 -4
  25. package/templates/panel/web/src/modules/Home/modal/Demo.tsx +10 -0
  26. package/templates/panel/web/src/modules/List/index.tsx +12 -11
  27. package/templates/panel/web/src/modules/List/router.sections.ts +6 -4
  28. package/templates/panel/web/src/modules/List/sections/Basic.tsx +3 -3
  29. package/templates/panel/web/src/modules/List/sections/Horizontal.tsx +1 -1
  30. package/templates/panel/web/src/modules/List/sections/Rows.tsx +6 -3
  31. package/templates/panel/web/src/modules/Settings/index.tsx +14 -8
  32. package/templates/panel/web/src/modules/Settings/router.sections.ts +7 -4
  33. package/templates/panel/web/src/modules/Settings/sections/Appearance.tsx +8 -4
  34. package/templates/panel/web/src/modules/Settings/sections/General.tsx +5 -1
  35. package/templates/panel/web/src/modules/Settings/sections/Profile.tsx +5 -1
  36. package/templates/panel/web/src/pages/Auth.tsx +10 -0
  37. package/templates/panel/web/src/pages/Main.tsx +7 -1
  38. package/templates/panel/web/src/state.ts +9 -0
  39. package/templates/panel/web/src/style/theme-dark.css +18 -87
  40. package/templates/panel/web/src/style/theme-light.css +18 -87
  41. package/templates/panel/web/src/style/theme.css +29 -78
@@ -1,9 +1,21 @@
1
1
  // src/vite/concat-css.ts
2
2
  import { readdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ var SKIP_CSS = new Set([
5
+ "index.css",
6
+ "tokens.css",
7
+ "light.css",
8
+ "dark.css",
9
+ "themes.css"
10
+ ]);
4
11
  function concatDistCss(options) {
5
12
  const outFile = options.outFile ?? "index.css";
6
- const files = readdirSync(options.distDir).filter((f) => f.endsWith(".css") && f !== outFile).sort();
13
+ const skip = new Set([
14
+ ...SKIP_CSS,
15
+ outFile,
16
+ ...options.skip ?? []
17
+ ]);
18
+ const files = readdirSync(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
7
19
  const css = files.map((f) => readFileSync(join(options.distDir, f), "utf8")).join(`
8
20
  `);
9
21
  const outPath = join(options.distDir, outFile);
@@ -12,9 +12,21 @@ import { join as join2 } from "node:path";
12
12
  // src/vite/concat-css.ts
13
13
  import { readdirSync, readFileSync, writeFileSync } from "node:fs";
14
14
  import { join } from "node:path";
15
+ var SKIP_CSS = new Set([
16
+ "index.css",
17
+ "tokens.css",
18
+ "light.css",
19
+ "dark.css",
20
+ "themes.css"
21
+ ]);
15
22
  function concatDistCss(options) {
16
23
  const outFile = options.outFile ?? "index.css";
17
- const files = readdirSync(options.distDir).filter((f) => f.endsWith(".css") && f !== outFile).sort();
24
+ const skip = new Set([
25
+ ...SKIP_CSS,
26
+ outFile,
27
+ ...options.skip ?? []
28
+ ]);
29
+ const files = readdirSync(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
18
30
  const css = files.map((f) => readFileSync(join(options.distDir, f), "utf8")).join(`
19
31
  `);
20
32
  const outPath = join(options.distDir, outFile);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elcrm",
3
- "version": "1.1.2",
4
- "description": "CLI @elcrm/*: update, create, migrate front, audit, doctor, test, build, css",
3
+ "version": "1.1.3",
4
+ "description": "CLI @elcrm/*: update, create, migrate front/components, audit, doctor, test, build, css",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "MaSkal <dev@elcrm.online>",
@@ -47,8 +47,9 @@
47
47
  "build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm && bun build ./src/vite/plugin-css-scoped.ts --outfile ./dist/vite/plugin-css-scoped.js --target node --format esm --packages external && bun build ./src/vite/discover-lib.ts --outfile ./dist/vite/discover-lib.js --target node --format esm --packages external && bun build ./src/vite/concat-css.ts --outfile ./dist/vite/concat-css.js --target node --format esm --packages external && bun build ./src/vite/check-tokens.ts --outfile ./dist/vite/check-tokens.js --target node --format esm --packages external && bun build ./src/vite/postbuild.ts --outfile ./dist/vite/postbuild.js --target node --format esm --packages external && node ./scripts/chmod-bin.mjs && node ./scripts/copy-vite-dts.mjs",
48
48
  "dev": "bun run ./src/index.ts",
49
49
  "elcrm": "bun run ./src/index.ts",
50
+ "test": "bun test src/lib/migrate",
50
51
  "link:local": "bun run build && bun link",
51
- "prepublishOnly": "bun run build",
52
+ "prepublishOnly": "bun run test && bun run build",
52
53
  "publish:npm": "npm publish"
53
54
  },
54
55
  "keywords": [
@@ -6,7 +6,9 @@
6
6
 
7
7
  Корень UI: `Alert.Init` / `Modal.Init` / `Notice.Init` / `Form.Init` + `LoadRoute`. Кнопки `@elcrm/button`, поля `@elcrm/form`, модалки `Modal.Create`.
8
8
 
9
- Нельзя: native input/button, react-router, axios, MUI/antd/tailwind, `themes.css`, `size="sm"|"md"`.
9
+ Свой компонент: JSDoc на русском (зачем, props, слоты, `@example`). Без него не сдавать.
10
+
11
+ Нельзя: native input/button, react-router, axios, MUI/antd/tailwind, `themes.css`, `size="sm"|"md"`. Свой CSS — `var(--button-*)` / `--field-*` / `--item-*` из theme, не `#hex`.
10
12
 
11
13
  Перед сдачей: `elcrm test` (в panel — из корня). После смены пакетов: `elcrm update --fix --test`.
12
14
 
@@ -0,0 +1,89 @@
1
+ ---
2
+ description: JSDoc у каждого компонента — props, слоты, пример
3
+ globs: "**/*.{tsx,ts,jsx,js}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # JSDoc компонентов (обязательно)
8
+
9
+ У **каждого** React-компонента и публичной функции — блок JSDoc на русском **до** объявления. Без него компонент не считают законченным. Цель: по ховеру в IDE видно props, слоты, что рендерится.
10
+
11
+ Не дублировать шпаргалку пакета (`docs/BUTTON.elCRM.md`) целиком — кратко + ссылка, если это обёртка над `@elcrm/*`.
12
+
13
+ ## Шаблон компонента
14
+
15
+ ```tsx
16
+ /**
17
+ * Шапка приложения: слоты brand / nav / search / actions.
18
+ *
19
+ * @example
20
+ * <Header />
21
+ *
22
+ * Слоты (что можно передать):
23
+ * - brand — логотип (`Brand`)
24
+ * - nav — пункты (`Menu`)
25
+ * - search — `@elcrm/search`
26
+ * - actions — кнопки, `Avatar`, `Tooltip`
27
+ *
28
+ * @see Header из `@elcrm/components` — те же слоты
29
+ */
30
+ export default function Header() { … }
31
+ ```
32
+
33
+ ## Шаблон с props
34
+
35
+ Тип props — отдельный `type` / `interface`, каждое поле с `@param` **или** JSDoc на поле типа:
36
+
37
+ ```ts
38
+ type Props = {
39
+ /** Логин для текста в теле модалки */
40
+ login?: string;
41
+ /** Закрытие; `Modal.Create` прокидывает сам */
42
+ onClose?: () => void;
43
+ };
44
+
45
+ /**
46
+ * Демо-модалка модуля Home.
47
+ *
48
+ * @param props.login - отображаемое имя
49
+ * @returns очередь `Modal.Create` → `{ Open, Icon }`
50
+ *
51
+ * @example
52
+ * Open({ login: "admin" })
53
+ */
54
+ function Screen({ login }: Props) { … }
55
+ ```
56
+
57
+ ## Что обязательно описать
58
+
59
+ | Тема | Как писать |
60
+ | --- | --- |
61
+ | Зачем компонент | первая строка |
62
+ | Props | имя, тип в коде, смысл, default если не очевиден |
63
+ | Слоты / children | что класть: `Button`, `StringField`, не «react node» |
64
+ | Размер | `"s" \| "m" \| "l"` |
65
+ | Роутер | какой ключ (`module`, `section`, `listTab`) |
66
+ | Связь с пакетом | `@elcrm/form` поле, `@elcrm/button` `onSend` |
67
+ | Токены | если кастомный CSS: какие `--*` |
68
+
69
+ ## Функции / хуки
70
+
71
+ ```ts
72
+ /**
73
+ * Берёт ключ секции из роутера, только если он есть в списке модуля.
74
+ *
75
+ * @param value - `Router.useRouter("section")`
76
+ * @param keys - допустимые value секций
77
+ * @param fallback - секция по умолчанию
78
+ */
79
+ export function pickRouteKey(value: unknown, keys: readonly string[], fallback: string): string
80
+ ```
81
+
82
+ ## Нельзя
83
+
84
+ - Пустой `/** Header */` без props/слотов
85
+ - JSDoc на английском в app (код и доки — русские)
86
+ - Описывать внутренности пакета вместо его шпаргалки — для `@elcrm/button` достаточно «см. docs/BUTTON.elCRM.md», для **своей** обёртки — полный разбор слотов
87
+ - Новый компонент в `src/` без JSDoc (в т.ч. `sections/*.tsx`, `modal/*.tsx`)
88
+
89
+ При правке существующего компонента без JSDoc — **добавить блок сразу**, не ждать отдельной задачи.
@@ -59,6 +59,7 @@ alwaysApply: true
59
59
 
60
60
  ### 1. Публичный API (компонент, хук, функция, тип, props)
61
61
 
62
+ - [ ] JSDoc на русском у каждого экспорта: props, слоты, `@example`
62
63
  - [ ] Экспорт в `index` и `package.json` `exports`
63
64
  - [ ] `README.md` этого пакета
64
65
  - [ ] **Шпаргалка в elCRM.cli** (таблица выше) — те же можно/нельзя, что в README
@@ -13,7 +13,7 @@ alwaysApply: true
13
13
 
14
14
  | Файл | Пакет | Делать так |
15
15
  | ------------------------ | ---------------------- | ------------------------------------------------------- |
16
- | `CLI.elCRM.md` | `elcrm` | `update --fix --test`, migrate, test, css, docs, cursor |
16
+ | `CLI.elCRM.md` | `elcrm` | `upd` (сам CLI), `update --fix --test`, docs, cursor |
17
17
  | `API.elCRM.md` | `@elcrm/api` | `Api.create` + `Api.query("router/method", body)` |
18
18
  | `ALERT.elCRM.md` | `@elcrm/alert` | `<Alert.Init />`, `Alert.Send` / `Confirm` |
19
19
  | `BUTTON.elCRM.md` | `@elcrm/button` | `<Button onSend>`, size s/m/l, не `<button>` |
@@ -26,7 +26,7 @@ alwaysApply: true
26
26
  | `TOOLTIP.elCRM.md` | `@elcrm/tooltip` | `<Tooltip content>`, не `title` |
27
27
  | `ICONS.elCRM.md` | `@elcrm/icons` | `Icons.Line`, vite `icons()`, `icons update` |
28
28
  | `FORM.elCRM.md` | `@elcrm/form` | поля + useForm, не native input |
29
- | `COMPONENTS.elCRM.md` | `@elcrm/components` | Layout/Header/Section/NavSections |
29
+ | `COMPONENTS.elCRM.md` | `@elcrm/components` | Layout/Header/Section/Stack/NavSections; migrate components |
30
30
  | `SERVER.elCRM.md` | `@elcrm/server` | createServer + createRouter, не REST, не node:fs |
31
31
 
32
32
  ## Корень приложения (panel)
@@ -11,3 +11,4 @@ alwaysApply: false
11
11
  - Не класть `--*` в `modules/**/style.css`, если это цвет/размер оболочки.
12
12
  - Не `import "@elcrm/*/themes.css"`. Не shorthand `1px solid` в `--field-border-hover`.
13
13
  - Недостающие дефолты: `elcrm css` (уже заданные не затирает).
14
+ - Кастомный UI — `var(--layout-*)` / `--button-*` / `--field-*` / `--item-*`, не новые цвета в модуле.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Кастомный UI — сначала токены @elcrm/*, не свои цвета и px
3
+ globs: "**/*.{tsx,jsx,css}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Кастомные элементы → токены библиотек
8
+
9
+ Свой блок/обёртка/CSS модуля — **не** новый `#hex` и не магические `14px`, если уже есть переменная пакета. Смотри `src/style/theme.css` + light/dark (их заполняет `elcrm css`).
10
+
11
+ ## Сначала существующий токен
12
+
13
+ | Нужно | Брать из |
14
+ | --- | --- |
15
+ | Фон/текст/бордер страницы | `--layout-*`, `--section-*`, `--card-*` |
16
+ | Шапка / подвал | `--header-*`, `--footer-*` |
17
+ | Список / строка | `--list-*`, `--item-*` |
18
+ | Кнопка | `--button-*` (variant + hover/active/disabled) |
19
+ | Поле ввода | `--field-*`, `--popup-shadow` |
20
+ | Меню / вкладки | `--menu-*`, `--nav-sections-*`, `--tab-sections-*` |
21
+ | Тост / алерт / тултип | `--notice-*`, `--alert-*`, `--tooltip-*` |
22
+ | Отступ стека | `--block-gap`, `--row-gap`, `--column-gap` |
23
+ | Размер контрола | `s` / `m` / `l` у компонента, не свои px |
24
+
25
+ В CSS: `color: var(--item-color)`, `padding: var(--card-padding)`, `border-radius: var(--card-radius)`.
26
+
27
+ В inline-style допустимо только `var(--…)`, не литерал цвета.
28
+
29
+ ## Если токена нет
30
+
31
+ 1. Похожий из той же группы (`--item-background-hover` вместо нового `--my-hover`).
32
+ 2. Редко — объявить `--*` в theme (цвет в **light и dark**, геометрия в `theme.css`) и использовать `var()`.
33
+ 3. Не копировать значения из `node_modules/@elcrm/*/dist` в модуль.
34
+
35
+ ## Нельзя
36
+
37
+ - Свой `style={{ color: "#333" }}` / `background: white`
38
+ - Дублировать `--button-primary-bg` под другим именем
39
+ - `themes.css` из пакета
40
+ - shorthand `1px solid …` в `--field-border-hover|focus|error|disabled`
@@ -37,5 +37,6 @@ src/style/theme*.css + elcrm.css
37
37
 
38
38
  ## CSS / запреты
39
39
 
40
+ - Кастомный блок: `var(--токен)` из theme / `@elcrm/*` — правило `elcrm-tokens`. Не свой `#hex` и не случайные px.
40
41
  - Токены в theme, не `themes.css`, не `size="sm"|"md"`, не native input/select/textarea.
41
42
  - Border hover/focus полей — только цвет.
@@ -7,7 +7,7 @@ alwaysApply: true
7
7
 
8
8
  **Всегда** сверяйся со шпаргалками: `docs/*.elCRM.md` и `.cursor/docs/*.elCRM.md` (каталог пакетов — правило `elcrm-packages`). Не выдумывай методы. Обновить: `elcrm docs && elcrm cursor`.
9
9
 
10
- Комментарии и документация — **на русском**. Минимум зависимостей. Компоненты разделяй по обязанностям.
10
+ Комментарии и документация — **на русском**. У каждого компонента — JSDoc (props, слоты, пример): правило `elcrm-jsdoc`. Минимум зависимостей. Компоненты разделяй по обязанностям.
11
11
 
12
12
  ## Стек
13
13
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # `elcrm` CLI
4
4
 
5
- Глобально: `bun i -g elcrm`. В приложении — после смены `@elcrm/*`:
5
+ Глобально: `bun i -g elcrm` или `elcrm upd` (latest). В приложении — после смены `@elcrm/*`:
6
6
 
7
7
  ```bash
8
8
  elcrm update --fix --test
@@ -13,9 +13,10 @@ elcrm cursor
13
13
 
14
14
  | Команда | Зачем |
15
15
  | --- | --- |
16
- | `update [--dry] [--core] [--fix] [--test]` | latest `@elcrm/*`; `--fix` = migrate front + css; `--test` = elcrm test |
16
+ | `upd` | сам CLI → latest (`bun i -g elcrm@latest`) |
17
+ | `update [--dry] [--core] [--fix] [--test]` | latest `@elcrm/*` в проекте; `--fix` = migrate front + css; `--test` = elcrm test |
17
18
  | `create` / `init` | шаблоны `app` \| `lib` \| `panel` |
18
- | `migrate <name> [--dry]` | `front` (пакет), `form-aliases`, `form-tokens`, `size-sml`, `field-border`, `modal-create`, `legacy-hacks`, `socket-server` |
19
+ | `migrate <name> [--dry]` | `front` (пакет), `form-aliases`, `form-tokens`, `components`, `size-sml`, `field-border`, `modal-create`, `legacy-hacks`, `socket-server` |
19
20
  | `audit [--fix]` | старые API; `--fix` = migrate front |
20
21
  | `doctor [--fix]` | стандарты; сервер --fix → `bunx elcrm-server update` |
21
22
  | `test [--front\|--server] [--no-scripts]` | статический анализ + scripts |
@@ -2,67 +2,72 @@
2
2
 
3
3
  # `@elcrm/components`
4
4
 
5
- Shell UI: оболочка страницы, шапка, секции, списки. **Цвета и размеры задаёт приложение** (`theme.css` + `theme-light` / `theme-dark`). Fallback в пакете нет. `elcrm css` дописывает недостающие токены.
5
+ Shell UI: оболочка, шапка, секции, списки, Dropdown / RadioGroup / Pricing* / Avatar* / Breadcrumb / Stack.
6
+ **Цвета и размеры задаёт приложение** (`theme.css` + `theme-light` / `theme-dark`). Fallback в пакете нет.
7
+
8
+ Токены как у form: сначала общие **`--shell-*`** (+ `--control-*`, `--popup-shadow`), потом узкие (`--header-height`, `--section-padding`, `--avatar-size-*`, …).
9
+ Дефолты подтягивает `elcrm css` из `@elcrm/components/tokens.css` + `light.css` / `dark.css`.
10
+
11
+ Группы — через `items` / `groups`, идентификатор пункта — **`value`**.
6
12
 
7
13
  ```ts
8
14
  import {
9
- Layout, Header, Footer, Brand, Avatar, Badge,
10
- Menu, Section, NavSections, TabSections,
11
- Card, List, Item, Block, Row, Column,
12
- Loading, EmptyState, createLazyResolver,
15
+ Layout, Header, Footer, Brand, Avatar, AvatarName, AvatarGroup, Badge,
16
+ Menu, Dropdown, Section, NavSections, TabSections, Breadcrumb, Stack,
17
+ Card, List, Item, PageHead, PageShell,
18
+ Loading, EmptyState, RadioGroup, PricingGroup, PricingTable,
19
+ createLazyResolver,
13
20
  } from "@elcrm/components";
21
+ import type { PricingPlan } from "@elcrm/components";
14
22
  ```
15
23
 
16
- Точечно: `import { Header } from "@elcrm/components/Header"`.
24
+ ## Роли layout
17
25
 
18
- ## Компоненты
26
+ - **PageShell** — каркас модуля (скролл + footer).
27
+ - **PageHead** — title + actions.
28
+ - **Section** — блок контента / auth.
29
+ - **Layout / Header** — оболочка приложения.
30
+ - **Stack** — ряд/колонка (`direction="row"|"column"`). Не `Block` / `Row` / `Column`.
19
31
 
20
- | | Назначение | Ключевые props |
21
- | --- | --- | --- |
22
- | Layout | страница | `header`, `footer`, `children` |
23
- | Header | слоты шапки | `brand`, `nav`, `search`, `actions` |
24
- | Footer | подвал | `children` |
25
- | Brand | логотип | `as`, `onClick` |
26
- | Avatar | фото / инициалы | `src`, `name`, `size` (`s`/`m`/`l`) |
27
- | Badge | статус | `tone` |
28
- | Menu | нав (не overlay) | `items`, `activeKey`, `onSelect` |
29
- | Section | блок / auth | `title`, `description`, `icon`, `actions`, `variant` |
30
- | NavSections | сайдбар + lazy | `groups`, `resolveSection` |
31
- | TabSections | вкладки + lazy | `items`, `resolveSection` |
32
- | Card / List / Item | карточка, список | Item: `as`, `active` |
33
- | Block / Row / Column | стеки | `gap` |
34
- | Loading / EmptyState | плейсхолдеры | |
35
- | createLazyResolver | кэш `import()` | `(key) => import(…)` |
32
+ ## Меню
36
33
 
37
- Слоты Header заполняет приложение: поиск `@elcrm/search`, кнопки `@elcrm/button`, иконки `@elcrm/icons`.
34
+ - **Menu** — inline-навигация в шапке.
35
+ - **Dropdown** — выпадающее меню по `items`/`groups`.
36
+ - **`@elcrm/overlay`** — portal / позиционирование / z-stack для своих попапов.
38
37
 
39
- ## Структура приложения (panel)
38
+ ## Avatar
40
39
 
41
- ```
42
- src/
43
- App.tsx # Init: Alert, Modal, Notice, Form, LoadRoute
44
- pages/Auth.tsx Main.tsx
45
- modules/<Name>/
46
- index.tsx
47
- router.sections.ts
48
- sections/<Key>.tsx # export default
49
- modal/ # опционально
50
- component/ # локальные обёртки (Header приложения)
51
- style/theme.css theme-light.css theme-dark.css elcrm.css
52
- ```
40
+ - **Avatar** — фото / инициалы (`name` только для инициалов).
41
+ - **AvatarName** — `title` + `description` (+ `name` для инициалов).
42
+ - **AvatarGroup** — `items` с `value` / `title` / `name` / `src`.
43
+
44
+ ## Pricing
53
45
 
54
- - Секции: `createLazyResolver((key) => import(\`./sections/${key}.tsx\`))`.
55
- - Ключи роутера не смешивать (`section` vs `listTab` vs `accountTab`) — иначе lazy откроет не ту секцию.
56
- - Auth-экран: `<Section variant="auth">`, поля из `@elcrm/form`, submit через `@elcrm/button` `onSend`.
46
+ Один тип `PricingPlan` и один массив `plans` → и `PricingGroup`, и `PricingTable` (+ `sections` у Table).
57
47
 
58
48
  ## Можно
59
49
 
60
- Токены только в theme-файлах приложения. Импорт `@elcrm/components/style.css` — опционально (единый файл).
50
+ - Задать `--shell-color` / `--shell-padding` / `--shell-radius` один раз — большинство компонентов подхватят.
51
+ - `--popup-shadow` общий с `@elcrm/form`.
52
+ - `elcrm css` — недостающие `--shell-*`.
53
+ - Импорт `@elcrm/components/style.css` — опционально.
61
54
 
62
55
  ## Нельзя
63
56
 
64
- - `import "@elcrm/…/themes.css"` — пакеты больше не отдают темы.
65
- - Свои `<button>` / нативные инпуты вместо `@elcrm/button` / `@elcrm/form` в UI приложения.
66
- - `size="sm"|"md"` → `"s"|"m"` (`elcrm migrate size-sml`).
67
- - Дублировать toolbar-токены в модульных CSS — общий класс + theme.
68
- - Хардкодить цвета в компонентах модулей, если есть токен.
57
+ - `import "@elcrm/…/themes.css"`.
58
+ - `Block` / `Row` / `Column` — `Stack` (`elcrm migrate components`).
59
+ - `key` / `activeKey` / `defaultActiveKey` в группах — `value` / `defaultValue` (`elcrm migrate components`).
60
+ - Нативные `<button>` / инпуты вместо `@elcrm/button` / `@elcrm/form` в приложении.
61
+ - `size="sm"|"md"` → `"s"|"m"`.
62
+ - Хардкод цветов в модулях приложения, если есть токен.
63
+
64
+ ## Обновление проекта
65
+
66
+ ```bash
67
+ elcrm update --fix --test
68
+ elcrm css
69
+ elcrm docs
70
+ elcrm cursor
71
+ ```
72
+
73
+ Пилюля: `elcrm migrate components` (также внутри `migrate front` / `update --fix`).
@@ -6,11 +6,25 @@ import { Form } from "@elcrm/form";
6
6
  import { LoadRoute } from "@elcrm/router";
7
7
  import { createLazyResolver } from "./lazyRoute";
8
8
 
9
- /** Страницы: src/pages/Auth.tsx, Main.tsx */
10
9
  const resolvePage = createLazyResolver((segment) =>
11
10
  import(`./pages/${segment}.tsx`),
12
11
  );
13
12
 
13
+ /**
14
+ * Корень UI: глобальные Init пакетов + страница (`LoadRoute` variant=`page`).
15
+ *
16
+ * Init (по одному):
17
+ * - `Alert.Init` — диалоги
18
+ * - `Modal.Init` — очередь модалок (`Modal.Create` / Open)
19
+ * - `Notice.Init` — тосты (`position`, `durationMs`, `maxVisible`)
20
+ * - `Notifications.Init` — inbox (без autoSync в демо)
21
+ * - `Form.Init` — `onNotice` → `Notice.Remark`
22
+ *
23
+ * Страницы: `src/pages/{Auth|Main}.tsx`, default `Main`.
24
+ *
25
+ * @example
26
+ * <App />
27
+ */
14
28
  export default function App() {
15
29
  return (
16
30
  <>
@@ -13,7 +13,20 @@ import { Search } from "@elcrm/search";
13
13
  import useStateData from "../state";
14
14
  import { getTheme, toggleTheme, type ThemeMode } from "../lib/theme";
15
15
 
16
- /** Шапка panel на слотах @elcrm/components */
16
+ /**
17
+ * Шапка panel: слоты `@elcrm/components` `Header`.
18
+ *
19
+ * Слоты:
20
+ * - `brand` — `Brand`, клик → модуль `Home`
21
+ * - `nav` — `Menu` (`value` = `Router.module`: Home | List | Account | Settings)
22
+ * - `search` — `@elcrm/search`
23
+ * - `actions` — тема (`Button` ghost), `Avatar` → Account, выход (`Alert.Send`)
24
+ *
25
+ * Роутер: ключ `module`. Тема: `lib/theme` + localStorage.
26
+ *
27
+ * @example
28
+ * <Layout header={<Header />} />
29
+ */
17
30
  export default function Header() {
18
31
  const { user } = useStateData(["user"]);
19
32
  const module = Router.useRouter("module");
@@ -48,13 +61,13 @@ export default function Header() {
48
61
  }
49
62
  nav={
50
63
  <Menu
51
- activeKey={String(module || "Home")}
64
+ value={String(module || "Home")}
52
65
  onSelect={onTab}
53
66
  items={[
54
- { key: "Home", label: "Главная" },
55
- { key: "List", label: "Список" },
56
- { key: "Account", label: "Аккаунт" },
57
- { key: "Settings", label: "Настройки" },
67
+ { value: "Home", label: "Главная" },
68
+ { value: "List", label: "Список" },
69
+ { value: "Account", label: "Аккаунт" },
70
+ { value: "Settings", label: "Настройки" },
58
71
  ]}
59
72
  />
60
73
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Реэкспорт `createLazyResolver` — кэш `import()` для LoadRoute / NavSections / TabSections.
3
+ *
4
+ * @example
5
+ * const resolve = createLazyResolver((key) => import(`./sections/${key}.tsx`));
6
+ */
1
7
  export {
2
8
  createLazyResolver,
3
9
  type LazyRouteChunk,
@@ -1,4 +1,11 @@
1
- /** Ключ вкладки/секции только если он есть в списке модуля */
1
+ /**
2
+ * Ключ вкладки/секции из роутера, только если он есть в списке модуля.
3
+ * Иначе после смены модуля откроется чужой lazy-чанк.
4
+ *
5
+ * @param value - `Router.useRouter("section" | "listTab" | "accountTab")`
6
+ * @param keys - допустимые `value` из router.sections
7
+ * @param fallback - секция по умолчанию
8
+ */
2
9
  export function pickRouteKey(
3
10
  value: unknown,
4
11
  keys: readonly string[],
@@ -1,9 +1,14 @@
1
- /** Тема panel: localStorage + data-theme на html/body */
2
-
1
+ /**
2
+ * Тема panel: `data-theme` на html/body + localStorage `elcrm-panel-theme`.
3
+ * Цвета — `theme-light.css` / `theme-dark.css`.
4
+ */
3
5
  export type ThemeMode = "dark" | "light";
4
6
 
5
7
  const STORAGE_KEY = "elcrm-panel-theme";
6
8
 
9
+ /**
10
+ * Текущая тема (`dark`, если ключа нет).
11
+ */
7
12
  export function getTheme(): ThemeMode {
8
13
  try {
9
14
  const v = localStorage.getItem(STORAGE_KEY);
@@ -14,7 +19,11 @@ export function getTheme(): ThemeMode {
14
19
  return "dark";
15
20
  }
16
21
 
17
- /** Применить тему к документу */
22
+ /**
23
+ * Ставит `data-theme` и `color-scheme` на document.
24
+ *
25
+ * @param theme - `light` | `dark`, по умолчанию `getTheme()`
26
+ */
18
27
  export function applyTheme(theme: ThemeMode = getTheme()): void {
19
28
  const root = document.documentElement;
20
29
  const body = document.body;
@@ -23,6 +32,9 @@ export function applyTheme(theme: ThemeMode = getTheme()): void {
23
32
  root.style.colorScheme = theme;
24
33
  }
25
34
 
35
+ /**
36
+ * Сохранить тему и применить к DOM.
37
+ */
26
38
  export function setTheme(theme: ThemeMode): void {
27
39
  try {
28
40
  localStorage.setItem(STORAGE_KEY, theme);
@@ -32,6 +44,11 @@ export function setTheme(theme: ThemeMode): void {
32
44
  applyTheme(theme);
33
45
  }
34
46
 
47
+ /**
48
+ * Переключить light ↔ dark.
49
+ *
50
+ * @returns новая тема
51
+ */
35
52
  export function toggleTheme(): ThemeMode {
36
53
  const next: ThemeMode = getTheme() === "dark" ? "light" : "dark";
37
54
  setTheme(next);
@@ -8,17 +8,21 @@ import { Router } from "@elcrm/router";
8
8
  import { pickRouteKey } from "../../lib/routeKey";
9
9
  import { ACCOUNT_TAB_DEFAULT, ACCOUNT_TABS } from "./router.sections";
10
10
 
11
- /** Lazy: modules/Account/sections/{Key}.tsx */
12
- const resolveSection = createLazyResolver((key) =>
13
- import(`./sections/${key}.tsx`),
11
+ const resolveSection = createLazyResolver((value) =>
12
+ import(`./sections/${value}.tsx`),
14
13
  );
15
14
 
16
- const ACCOUNT_KEYS = ACCOUNT_TABS.map((t) => t.key);
15
+ const ACCOUNT_KEYS = ACCOUNT_TABS.map((t) => t.value);
17
16
 
18
- /** Модуль «Аккаунт»: Section (заголовок) + горизонтальные вкладки */
17
+ /**
18
+ * Аккаунт: `Section` + `TabSections`.
19
+ *
20
+ * Роутер: ключ `accountTab` (не смешивать с `section` / `listTab`).
21
+ * Секции: `sections/{Value}.tsx`.
22
+ */
19
23
  export default function Account() {
20
24
  const tab = Router.useRouter("accountTab");
21
- const activeKey = pickRouteKey(tab, ACCOUNT_KEYS, ACCOUNT_TAB_DEFAULT);
25
+ const value = pickRouteKey(tab, ACCOUNT_KEYS, ACCOUNT_TAB_DEFAULT);
22
26
 
23
27
  return (
24
28
  <Section
@@ -28,8 +32,8 @@ export default function Account() {
28
32
  >
29
33
  <TabSections
30
34
  items={ACCOUNT_TABS}
31
- activeKey={activeKey}
32
- onChange={(key) => Router.setRouter("accountTab", key)}
35
+ value={value}
36
+ onValueChange={(next) => Router.setRouter("accountTab", next)}
33
37
  resolveSection={resolveSection}
34
38
  loading={<Loading />}
35
39
  navLabel="Разделы аккаунта"
@@ -1,9 +1,11 @@
1
1
  import type { TabSectionsItem } from "@elcrm/components";
2
2
 
3
- /** Вкладки модуля Account (ключ = файл sections/{Key}.tsx) */
3
+ /**
4
+ * Вкладки Account. `value` = `sections/{Value}.tsx`.
5
+ */
4
6
  export const ACCOUNT_TABS: TabSectionsItem[] = [
5
- { key: "Profile", label: "Профиль" },
6
- { key: "Sessions", label: "Сессии", badge: 1 },
7
+ { value: "Profile", label: "Профиль" },
8
+ { value: "Sessions", label: "Сессии", badge: 1 },
7
9
  ];
8
10
 
9
11
  export const ACCOUNT_TAB_DEFAULT = "Profile";
@@ -1,12 +1,17 @@
1
1
  import React from "react";
2
- import { Card, Column } from "@elcrm/components";
2
+ import { Card, Stack } from "@elcrm/components";
3
3
  import { Api } from "@elcrm/api";
4
4
  import { Button } from "@elcrm/button";
5
5
  import { Notice } from "@elcrm/notice";
6
6
  import { DisplayField, EmailField, StringField, useForm } from "@elcrm/form";
7
7
  import useStateData from "../../../state";
8
8
 
9
- /** Вкладка: профиль */
9
+ /**
10
+ * Вкладка аккаунта: профиль.
11
+ *
12
+ * Поля: `DisplayField` login, `StringField` display_name, `EmailField` email.
13
+ * Сохранение: `Button` → `Api.query("auth/profile")` + `loadUser()`.
14
+ */
10
15
  export default function Profile() {
11
16
  const { user, loadUser } = useStateData(["user", "loadUser"]);
12
17
  const form = useForm({
@@ -45,7 +50,7 @@ export default function Profile() {
45
50
 
46
51
  return (
47
52
  <Card style={{ maxWidth: "28rem" }}>
48
- <Column gap="0.75rem">
53
+ <Stack gap="0.75rem">
49
54
  <DisplayField
50
55
  name="login"
51
56
  form={form}
@@ -69,7 +74,7 @@ export default function Profile() {
69
74
  disabled={busy}
70
75
  onClick={() => void onSave()}
71
76
  />
72
- </Column>
77
+ </Stack>
73
78
  </Card>
74
79
  );
75
80
  }