@elcrm/components 0.2.5 → 0.2.6

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.
package/README.md CHANGED
@@ -74,42 +74,42 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
74
74
 
75
75
  ## Компоненты
76
76
 
77
- | | Назначение | Ключевые props |
78
- | -------------------------------- | -------------------------------- | ---------------------------------------------------- |
79
- | **Layout** | Оболочка страницы | `sidebar`, `header`, `footer`, `fixed`, `children` |
80
- | **Header** | Шапка со слотами | `brand`, `nav`, `search`, `actions` |
81
- | **Footer** | Подвал | `children` |
82
- | **Brand** | Название / логотип | `as`, `children`, `onClick` |
83
- | **Avatar** | Фото или инициалы | `src`, `name`, `size`, `radius`, `status`, `as` |
84
- | **AvatarName** | Аватар + title + description | `title`, `description`, `radius`, `--avatar-name-*-color` |
85
- | **AvatarGroup** | Стопка аватаров + «+N» | `items` (`value`/`title`/`name`), `max`, `add` |
86
- | **Badge** | Метка / статус | `tone`, `size`, `dot`, `children` |
87
- | **RadioGroup** | Выбор одной опции-карточки | `items`, `value`, `indicator`, `indicatorSide` |
88
- | **PricingGroup** | Тарифные карточки в ряд | `items` (`PricingPlan`), `selectable`, `value` |
89
- | **PricingTable** | Сравнение тарифов (таблица) | те же `items` + `sections` |
90
- | **Menu** | Навигация в шапке (не popover) | `items` (`value`, `badge`: `0` скрыт), `value`, `onSelect`, `variant` (`plain`/`line`) |
91
- | **Dropdown** | Выпадающее меню (items, portal) | `label`/`trigger`, `items`/`groups`, `onSelect` |
92
- | **Popover** | Свой попап в portal | `trigger`, `children`, `align`, `open` |
93
- | **Breadcrumb** | Хлебные крошки | `items`, `separator`, `collapseAfter` |
94
- | **Sidebar** | Левое меню приложения | `groups`, `value`, `footer` + `footerItems`, `variant` |
95
- | **PageShell** | Каркас модуля (скролл + footer) | `wide`, `scroll`, `footer`, `children` |
96
- | **ChatList** | Колонка диалогов | `header`, `footer`, `children` (`List`/`Item`), `bodyRef` |
97
- | **ChatMessages** | Колонка переписки | `header`, `footer`, `children`, `bodyRef`, `onBodyScroll` |
98
- | **ChatSplit** | Список + лента | `list`, `messages` |
99
- | **PageHead** | Заголовок страницы + actions | `title`, `sup` (текст/Badge), `subtitle`, `actions`, `sticky`, `children` |
100
- | **Section** | Блок контента / auth / подраздел | `title`, `description`, `icon`, `actions`, `variant` |
101
- | **NavSections** | Сайдбар + lazy-панель | `groups` (`value`), `resolveSection`, `defaultValue` |
102
- | **TabSections** | Горизонтальные вкладки + lazy | `items` (`value` + `badge`), `resolveSection` |
103
- | **Card** | Карточка | `title`, `--card-pointer` |
104
- | **ActionGroup** | Тулбар действий | `label`, `items` и/или `children` |
105
- | **TextGroup** | Title + description | `title`, `description`, `size` |
106
- | **ItemColumn** | Иконка + столбик в Item | `icon`, `title`, `extra`, `aside`, `description`, `footer` / `meta`, `unread` |
107
- | **Stat** | Карточка-метрика | `label`, `value`, `tone`, `labelMuted` |
108
- | **List** / **Item** | Список и строка | List: `row`; Item: `as`, `active`, `variant` (`plain`/`card`) |
109
- | **Stack** | Ряд / колонка / сетка | `direction`, `columns` (N или `["1fr", "100px"]`), `gap` |
110
- | **Loading** | Плейсхолдер загрузки | `children` |
111
- | **EmptyState** | Заглушка «нет данных» | `title`, `description`, `icon`, `actions` |
112
- | **createLazyResolver** | Кэш `import()` секций | `(key) => import(…)` |
77
+ | | Назначение | Ключевые props |
78
+ | ---------------------- | -------------------------------- | -------------------------------------------------------------------------------------- |
79
+ | **Layout** | Оболочка страницы | `sidebar`, `header`, `footer`, `fixed`, `children` |
80
+ | **Header** | Шапка со слотами | `brand`, `nav`, `search`, `actions` |
81
+ | **Footer** | Подвал | `children` |
82
+ | **Brand** | Название / логотип | `as`, `children`, `onClick` |
83
+ | **Avatar** | Фото или инициалы | `src`, `name`, `size`, `radius`, `status`, `as` |
84
+ | **AvatarName** | Аватар + title + description | `title`, `description`, `radius`, `--avatar-name-*-color` |
85
+ | **AvatarGroup** | Стопка аватаров + «+N» | `items` (`value`/`title`/`name`), `max`, `add` |
86
+ | **Badge** | Метка / статус | `tone`, `size`, `dot`, `children` |
87
+ | **RadioGroup** | Выбор одной опции-карточки | `items`, `value`, `indicator`, `indicatorSide` |
88
+ | **PricingGroup** | Тарифные карточки в ряд | `items` (`PricingPlan`), `selectable`, `value` |
89
+ | **PricingTable** | Сравнение тарифов (таблица) | те же `items` + `sections` |
90
+ | **Menu** | Навигация в шапке (не popover) | `items` (`value`, `badge`: `0` скрыт), `value`, `onSelect`, `variant` (`plain`/`line`) |
91
+ | **Dropdown** | Выпадающее меню (items, portal) | `label`/`trigger`, `items`/`groups`, `onSelect` |
92
+ | **Popover** | Свой попап в portal | `trigger`, `children`, `align`, `open` |
93
+ | **Breadcrumb** | Хлебные крошки | `items`, `separator`, `collapseAfter` |
94
+ | **Sidebar** | Левое меню приложения | `groups`, `value`, `footer` + `footerItems`, `variant` |
95
+ | **PageShell** | Каркас модуля (скролл + footer) | `wide`, `scroll`, `footer`, `children` |
96
+ | **ChatList** | Колонка диалогов | `header`, `footer`, `children` (`List`/`Item`), `bodyRef` |
97
+ | **ChatMessages** | Колонка переписки | `header`, `footer`, `children`, `bodyRef`, `onBodyScroll` |
98
+ | **ChatSplit** | Список + лента | `list`, `messages` |
99
+ | **PageHead** | Заголовок страницы + actions | `title`, `sup` (текст/Badge), `subtitle`, `actions`, `sticky`, `children` |
100
+ | **Section** | Блок контента / auth / подраздел | `title`, `description`, `icon`, `actions`, `variant` |
101
+ | **NavSections** | Сайдбар + lazy-панель | `groups` (`value`), `resolveSection`, `defaultValue` |
102
+ | **TabSections** | Горизонтальные вкладки + lazy | `items` (`value` + `badge`), `resolveSection` |
103
+ | **Card** | Карточка | `title`, `--card-pointer` |
104
+ | **ActionGroup** | Тулбар действий | `label`, `items` и/или `children` |
105
+ | **TextGroup** | Title + description | `title`, `description`, `size` |
106
+ | **ItemColumn** | Иконка + столбик в Item | `icon`, `title`, `extra`, `aside`, `description`, `footer` / `meta`, `unread` |
107
+ | **Stat** | Карточка-метрика | `label`, `value`, `tone`, `labelMuted` |
108
+ | **List** / **Item** | Список и строка | List: `row`; Item: `as`, `active`, `variant` (`plain`/`card`) |
109
+ | **Stack** | Ряд / колонка / сетка | `direction`, `columns` (N или `["1fr", "100px"]`), `gap` |
110
+ | **Loading** | Плейсхолдер загрузки | `children` |
111
+ | **EmptyState** | Заглушка «нет данных» | `title`, `description`, `icon`, `actions` |
112
+ | **createLazyResolver** | Кэш `import()` секций | `(key) => import(…)` |
113
113
 
114
114
  Слоты `Header` заполняет приложение: поиск — `@elcrm/search`, кнопки — `@elcrm/button`, и т.д.
115
115
 
@@ -127,8 +127,15 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
127
127
  brand={<Brand as="strong">App</Brand>}
128
128
  extra={<Notifications.Bell />}
129
129
  groups={[
130
- { value: "main", items: [{ value: "home", label: "Обзор", icon: <Icon /> }] },
131
- { value: "admin", title: "Админ", items: [{ value: "ops", label: "Операции" }] },
130
+ {
131
+ value: "main",
132
+ items: [{ value: "home", label: "Обзор", icon: <Icon /> }],
133
+ },
134
+ {
135
+ value: "admin",
136
+ title: "Админ",
137
+ items: [{ value: "ops", label: "Операции" }],
138
+ },
132
139
  ]}
133
140
  footer={<AvatarName title="Паша" description="mail@…" />}
134
141
  footerItems={[
@@ -218,16 +225,18 @@ const items = [
218
225
  value={plan}
219
226
  onValueChange={setPlan}
220
227
  indicator="check"
221
- />
228
+ />;
222
229
 
223
- {/* Круглый radio слева */}
230
+ {
231
+ /* Круглый radio слева */
232
+ }
224
233
  <RadioGroup
225
234
  items={items}
226
235
  value={plan}
227
236
  onValueChange={setPlan}
228
237
  indicator="radio"
229
238
  indicatorSide="start"
230
- />
239
+ />;
231
240
  ```
232
241
 
233
242
  ### PricingGroup / PricingTable
@@ -322,15 +331,21 @@ const sections = [
322
331
 
323
332
  Три разных роли — не путать:
324
333
 
325
- | | Роль |
326
- | --- | --- |
327
- | **PageShell** | Каркас модуля: скролл в body (`scroll`, по умолчанию да), опциональный footer снизу |
328
- | **PageHead** | Строка title (+ `sup` надстрочно, subtitle) слева и actions справа; `sticky` — прилипает к верху скролла |
329
- | **Section** | Блок контента / auth-карточка / подраздел с title+description |
334
+ | | Роль |
335
+ | ------------- | -------------------------------------------------------------------------------------------------------- |
336
+ | **PageShell** | Каркас модуля: скролл в body (`scroll`, по умолчанию да), опциональный footer снизу |
337
+ | **PageHead** | Строка title (+ `sup` надстрочно, subtitle) слева и actions справа; `sticky` — прилипает к верху скролла |
338
+ | **Section** | Блок контента / auth-карточка / подраздел с title+description |
330
339
 
331
340
  ```tsx
332
341
  <PageShell scroll footer={<button type="button">Сохранить</button>}>
333
- <PageHead sticky title="Товары" sup={12} subtitle="Каталог" actions={<button>Добавить</button>} />
342
+ <PageHead
343
+ sticky
344
+ title="Товары"
345
+ sup={12}
346
+ subtitle="Каталог"
347
+ actions={<button>Добавить</button>}
348
+ />
334
349
  <Section title="Фильтры">{/* … */}</Section>
335
350
  </PageShell>
336
351
  ```
@@ -339,11 +354,11 @@ const sections = [
339
354
 
340
355
  Каркас мессенджера, не пузыри и не композер. Строки диалогов — `List` + `Item` (+ `ItemColumn` / `Avatar`). Поиск и ввод — слоты `header` / `footer` приложения.
341
356
 
342
- | | Роль |
343
- | --- | --- |
344
- | **ChatList** | Колонка диалогов: `header` (поиск), `children` (список), `footer` |
357
+ | | Роль |
358
+ | ---------------- | ----------------------------------------------------------------------------- |
359
+ | **ChatList** | Колонка диалогов: `header` (поиск), `children` (список), `footer` |
345
360
  | **ChatMessages** | Колонка переписки: `header` (собеседник), `children` (лента), `footer` (ввод) |
346
- | **ChatSplit** | Ряд `list` + `messages` (`--chat-split-gap`) |
361
+ | **ChatSplit** | Ряд `list` + `messages` (`--chat-split-gap`) |
347
362
 
348
363
  Токены: `--chat-list-width`, `--chat-split-gap`, `--chat-radius`, `--chat-background`, `--chat-border` (рамка и разделители header/footer), `--chat-header-padding` / `--chat-body-padding` / `--chat-footer-padding`, узкие `--chat-messages-*-padding`. Узкие `--chat-*` по умолчанию = `--shell-*`.
349
364
 
@@ -354,7 +369,11 @@ const sections = [
354
369
  <List>
355
370
  <li>
356
371
  <Item as="button" active>
357
- <ItemColumn icon={<Avatar name="Иван" />} title="Иван" description="Привет" />
372
+ <ItemColumn
373
+ icon={<Avatar name="Иван" />}
374
+ title="Иван"
375
+ description="Привет"
376
+ />
358
377
  </Item>
359
378
  </li>
360
379
  </List>
@@ -363,8 +382,7 @@ const sections = [
363
382
  messages={
364
383
  <ChatMessages
365
384
  header={<AvatarName title="Иван" description="в сети" />}
366
- footer={<TextareaField name="body" placeholder="Сообщение" />}
367
- >
385
+ footer={<TextareaField name="body" placeholder="Сообщение" />}>
368
386
  {bubbles}
369
387
  </ChatMessages>
370
388
  }
@@ -448,15 +466,15 @@ const people = [
448
466
  ],
449
467
  },
450
468
  {
451
- items: [
452
- { value: "details", label: "View details", submenu: true },
453
- ],
469
+ items: [{ value: "details", label: "View details", submenu: true }],
454
470
  },
455
471
  ]}
456
472
  onSelect={(value) => console.log(value)}
457
- />
473
+ />;
458
474
 
459
- {/* Выбор с галочкой */}
475
+ {
476
+ /* Выбор с галочкой */
477
+ }
460
478
  <Dropdown
461
479
  label="Can edit"
462
480
  items={[
@@ -464,16 +482,23 @@ const people = [
464
482
  { value: "edit", label: "Can edit", checked: true },
465
483
  { value: "view", label: "Can view" },
466
484
  ]}
467
- />
485
+ />;
468
486
 
469
- {/* Иконки, статус, submenu */}
487
+ {
488
+ /* Иконки, статус, submenu */
489
+ }
470
490
  <Dropdown
471
491
  label="Actions"
472
492
  groups={[
473
493
  {
474
494
  items: [
475
495
  { value: "back", label: "Back", icon: <BackIcon /> },
476
- { value: "reload", label: "Reload", icon: <ReloadIcon />, shortcut: "⌘R" },
496
+ {
497
+ value: "reload",
498
+ label: "Reload",
499
+ icon: <ReloadIcon />,
500
+ shortcut: "⌘R",
501
+ },
477
502
  ],
478
503
  },
479
504
  {
@@ -490,24 +515,37 @@ const people = [
490
515
  },
491
516
  {
492
517
  items: [
493
- { value: "more", label: "More tools", icon: <CubeIcon />, submenu: true },
518
+ {
519
+ value: "more",
520
+ label: "More tools",
521
+ icon: <CubeIcon />,
522
+ submenu: true,
523
+ },
494
524
  ],
495
525
  },
496
526
  ]}
497
- />
527
+ />;
498
528
 
499
- {/* Колонки: checkbox, title, не закрывать при клике */}
529
+ {
530
+ /* Колонки: checkbox, title, не закрывать при клике */
531
+ }
500
532
  <Dropdown
501
533
  trigger={<button type="button">⚙</button>}
502
534
  title="Колонки в списке"
503
535
  closeOnSelect={false}
504
536
  items={[
505
537
  { value: "photo", label: "Фото", checkbox: true, checked: true },
506
- { value: "sku", label: "SKU", checkbox: true, checked: true, disabled: true },
538
+ {
539
+ value: "sku",
540
+ label: "SKU",
541
+ checkbox: true,
542
+ checked: true,
543
+ disabled: true,
544
+ },
507
545
  { value: "brand", label: "Бренд", checkbox: true, checked: false },
508
546
  ]}
509
547
  onSelect={(value) => toggleColumn(value)}
510
- />
548
+ />;
511
549
  ```
512
550
 
513
551
  `Menu` — навигация в шапке (inline, не popover).
@@ -524,9 +562,11 @@ const people = [
524
562
  { value: "settings", label: "Settings", href: "/settings" },
525
563
  { value: "team", label: "Team" }, // текущий — последний
526
564
  ]}
527
- />
565
+ />;
528
566
 
529
- {/* Иконка + avatar + dropdown */}
567
+ {
568
+ /* Иконка + avatar + dropdown */
569
+ }
530
570
  <Breadcrumb
531
571
  items={[
532
572
  { value: "org", icon: <Logo />, label: "Warpspeed", href: "/" },
@@ -539,15 +579,17 @@ const people = [
539
579
  },
540
580
  { value: "settings", label: "Settings" },
541
581
  ]}
542
- />
582
+ />;
543
583
 
544
- {/* Длинный путь → первые 2, …, последний */}
584
+ {
585
+ /* Длинный путь → первые 2, …, последний */
586
+ }
545
587
  <Breadcrumb
546
588
  collapseAfter={2}
547
589
  separator="slash"
548
590
  items={longPath}
549
591
  onEllipsisClick={() => {}}
550
- />
592
+ />;
551
593
  ```
552
594
 
553
595
  ## Пример
@@ -580,9 +622,11 @@ const people = [
580
622
  </Section>
581
623
  </Layout>;
582
624
 
583
- {/* `fixed` по умолчанию = header + footer + sidebar. Скролл только у children.
625
+ {
626
+ /* `fixed` по умолчанию = header + footer + sidebar. Скролл только у children.
584
627
  Высота каркаса: `--layout-height` (100dvh). Вложенно: 100% + явная высота родителя.
585
- `fixed={[]}` — обычный поток страницы. */}
628
+ `fixed={[]}` — обычный поток страницы. */
629
+ }
586
630
 
587
631
  {
588
632
  /* Экран входа */
@@ -598,7 +642,9 @@ const people = [
598
642
  {
599
643
  /* Настройки: меню слева + sections/*.tsx справа */
600
644
  }
601
- const resolve = createLazyResolver((value) => import(`./sections/${value}.tsx`));
645
+ const resolve = createLazyResolver(
646
+ (value) => import(`./sections/${value}.tsx`),
647
+ );
602
648
 
603
649
  <NavSections
604
650
  defaultValue="Profile"
@@ -636,13 +682,13 @@ const resolve = createLazyResolver((value) => import(`./sections/${value}.tsx`))
636
682
 
637
683
  Как в `@elcrm/form`: сначала **общие**, потом узкие.
638
684
 
639
- | Слой | Где | Примеры |
640
- | ---- | --- | ------- |
641
- | Палитра | theme-light / dark | `--text`, `--muted`, `--line`, `--panel`, `--accent`, `--shadow` |
642
- | Control | theme.css | `--control-height`, `--control-radius`, `--control-font-size` |
643
- | **Shell** | theme.css + light/dark | `--shell-*` — общие для Dropdown, RadioGroup, Pricing*, Breadcrumb, AvatarName… |
644
- | Popover | light/dark | `--popup-shadow` (общий с form) |
645
- | Узкие | theme.css | `--header-height`, `--avatar-size-*`, `--dropdown-min-width`, `--pricing-card-min-width`… |
685
+ | Слой | Где | Примеры |
686
+ | --------- | ---------------------- | ----------------------------------------------------------------------------------------- |
687
+ | Палитра | theme-light / dark | `--text`, `--muted`, `--line`, `--panel`, `--accent`, `--shadow` |
688
+ | Control | theme.css | `--control-height`, `--control-radius`, `--control-font-size` |
689
+ | **Shell** | theme.css + light/dark | `--shell-*` — общие для Dropdown, RadioGroup, Pricing\*, Breadcrumb, AvatarName… |
690
+ | Popover | light/dark | `--popup-shadow` (общий с form) |
691
+ | Узкие | theme.css | `--header-height`, `--avatar-size-*`, `--dropdown-min-width`, `--pricing-card-min-width`… |
646
692
 
647
693
  Задай `--shell-*` один раз — большинство новых компонентов подхватят.
648
694
 
@@ -682,7 +728,8 @@ const resolve = createLazyResolver((value) => import(`./sections/${value}.tsx`))
682
728
  **Узкие (только если нужно иначе)**
683
729
 
684
730
  ```
685
- --header-height / --header-z / --header-margin / --header-shadow / --header-radius / --header-border
731
+ --layout-height / --layout-padding / --layout-margin / --layout-max-width
732
+ --header-height / --header-z / --header-margin / --header-shadow / --header-radius / --header-border / --header-nav-justify
686
733
  --section-padding / --section-title-size / --section-title-size-md / --section-auth-card-max-width
687
734
  --nav-sections-padding / --nav-sections-nav-width / --nav-sections-main-max-width
688
735
  --avatar-size-s|m|l / --avatar-group-overlap
package/dist/Header.css CHANGED
@@ -1 +1 @@
1
- @layer elcrm-components{.chr{display:flex;align-items:center;gap:var(--shell-gap);flex-shrink:0;min-height:var(--header-height);padding:var(--shell-padding);color:var(--shell-color);background:var(--shell-background);border:var(--header-border);border-radius:var(--header-radius);box-shadow:var(--header-shadow);margin:var(--header-margin);position:relative;z-index:var(--header-z)}.chn{display:flex;flex:1;align-items:center;min-width:0}.cha{display:flex;align-items:center;gap:var(--shell-gap-sm);flex-shrink:0;margin:auto}}
1
+ @layer elcrm-components{.chr{display:flex;align-items:center;gap:var(--shell-gap);flex-shrink:0;min-height:var(--header-height);padding:var(--shell-padding);color:var(--shell-color);background:var(--shell-background);border:var(--header-border);border-radius:var(--header-radius);box-shadow:var(--header-shadow);margin:var(--header-margin);position:relative;z-index:var(--header-z)}.chn{display:flex;flex:1;align-items:center;min-width:0;justify-content:var(--header-nav-justify)}.cha{display:flex;align-items:center;gap:var(--shell-gap-sm);flex-shrink:0;margin:auto}}
package/dist/Header.d.ts CHANGED
@@ -15,7 +15,7 @@ import { default as React } from 'react';
15
15
  * ```
16
16
  *
17
17
  * Токены: `--shell-*`, узкие `--header-height`, `--header-z`, `--header-margin`,
18
- * `--header-shadow`, `--header-radius`, `--header-border` (shorthand рамки).
18
+ * `--header-shadow`, `--header-radius`, `--header-border`, `--header-nav-justify`.
19
19
  */
20
20
  export type HeaderProps = React.HTMLAttributes<HTMLElement> & {
21
21
  /** Логотип / название (часто `<Brand />`) */
package/dist/Layout.css CHANGED
@@ -1 +1 @@
1
- @layer elcrm-components{.clr{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box;background:var(--shell-background-muted);gap:0}.clx{height:var(--layout-height, 100dvh);max-height:var(--layout-height, 100dvh);overflow:hidden}.clm{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain}.clr:not(.clx){height:auto;max-height:none;overflow:visible}.clr:not(.clx) .clm{overflow:visible;flex:0 1 auto;min-height:auto}.cls{flex-direction:row;align-items:stretch}.clb{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.clr:not(.clx) .clb{overflow:visible}.cla{display:flex;flex-direction:column;flex:0 0 var(--sidebar-width, 248px);width:var(--sidebar-width, 248px);min-width:0;min-height:0;align-self:stretch}.clk{overflow:hidden}.cla:not(.clk){overflow:auto}.cla>*{flex:1 1 auto;min-height:0;width:100%;height:100%;max-height:100%}@media (max-width: 900px){.cla{flex-basis:72px;width:72px}}.clh,.clf{flex:0 0 auto;min-width:0}}
1
+ @layer elcrm-components{.clr{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box;background:var(--shell-background-muted);margin:var(--layout-margin);padding:var(--layout-padding);max-width:var(--layout-max-width);gap:0}.clx{height:var(--layout-height, 100dvh);max-height:var(--layout-height, 100dvh);overflow:hidden}.clm{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain}.clr:not(.clx){height:auto;max-height:none;overflow:visible}.clr:not(.clx) .clm{overflow:visible;flex:0 1 auto;min-height:auto}.cls{flex-direction:row;align-items:stretch}.clb{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.clr:not(.clx) .clb{overflow:visible}.cla{display:flex;flex-direction:column;flex:0 0 var(--sidebar-width, 248px);width:var(--sidebar-width, 248px);min-width:0;min-height:0;align-self:stretch}.clk{overflow:hidden}.cla:not(.clk){overflow:auto}.cla>*{flex:1 1 auto;min-height:0;width:100%;height:100%;max-height:100%}@media (max-width: 900px){.cla{flex-basis:72px;width:72px}}.clh,.clf{flex:0 0 auto;min-width:0}}
package/dist/Layout.d.ts CHANGED
@@ -26,7 +26,7 @@ export type LayoutFixedSlot = "header" | "footer" | "sidebar";
26
26
  * </Layout>
27
27
  * ```
28
28
  *
29
- * Токены: `--layout-height`, `--shell-background-muted`, прочие `--shell-*`.
29
+ * Токены: `--layout-height`, `--layout-padding`, `--layout-margin`, `--layout-max-width`, `--shell-*`.
30
30
  */
31
31
  export type LayoutProps = React.HTMLAttributes<HTMLDivElement> & {
32
32
  /** Левая колонка (обычно `<Sidebar />`) — ряд: сайдбар + контент */
package/dist/index.css CHANGED
@@ -22,13 +22,13 @@
22
22
 
23
23
  @layer elcrm-components{.cfr{flex-shrink:0;padding:var(--shell-padding);color:var(--shell-color-muted);background:transparent;border-top:1px solid var(--shell-border);font-size:var(--shell-font-size-sm)}}
24
24
 
25
- @layer elcrm-components{.chr{display:flex;align-items:center;gap:var(--shell-gap);flex-shrink:0;min-height:var(--header-height);padding:var(--shell-padding);color:var(--shell-color);background:var(--shell-background);border:var(--header-border);border-radius:var(--header-radius);box-shadow:var(--header-shadow);margin:var(--header-margin);position:relative;z-index:var(--header-z)}.chn{display:flex;flex:1;align-items:center;min-width:0}.cha{display:flex;align-items:center;gap:var(--shell-gap-sm);flex-shrink:0;margin:auto}}
25
+ @layer elcrm-components{.chr{display:flex;align-items:center;gap:var(--shell-gap);flex-shrink:0;min-height:var(--header-height);padding:var(--shell-padding);color:var(--shell-color);background:var(--shell-background);border:var(--header-border);border-radius:var(--header-radius);box-shadow:var(--header-shadow);margin:var(--header-margin);position:relative;z-index:var(--header-z)}.chn{display:flex;flex:1;align-items:center;min-width:0;justify-content:var(--header-nav-justify)}.cha{display:flex;align-items:center;gap:var(--shell-gap-sm);flex-shrink:0;margin:auto}}
26
26
 
27
27
  @layer elcrm-components{.cir{display:flex;align-items:center;gap:var(--shell-gap-sm);padding:var(--shell-padding);color:var(--shell-color);background:transparent;border:1px solid transparent;border-radius:var(--shell-radius);cursor:default}.cir:is(button,a){-webkit-appearance:none;-moz-appearance:none;appearance:none;font:inherit;text-align:left;width:100%;cursor:pointer}.cir:hover{background:var(--shell-background-hover)}.cia,.cir[aria-current=true],.cir[data-active=true]{background:var(--shell-background-selected)}.cic{align-items:flex-start;gap:var(--shell-gap);padding:var(--shell-padding);border:1px solid var(--shell-border);border-radius:var(--shell-radius);background:var(--shell-background-muted);flex-shrink:0}.cic:hover,.cic.cia,.cic[data-active=true]{background:color-mix(in srgb,var(--shell-color-accent) 12%,var(--shell-background-muted));border-color:color-mix(in srgb,var(--shell-color-accent) 35%,transparent)}}
28
28
 
29
29
  @layer elcrm-components{.c7r{display:flex;align-items:flex-start;gap:var(--item-column-gap);min-width:0;width:100%;text-align:left}.c7i{flex-shrink:0;box-sizing:border-box;width:var(--item-column-icon-size);height:var(--item-column-icon-size);border-radius:var(--item-column-icon-radius);display:inline-flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--shell-color-accent) 14%,transparent);color:var(--shell-color-accent)}.c7b{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.c7h{display:flex;align-items:center;gap:8px;min-width:0}.c7t{flex:1;min-width:0;margin:0;font-size:var(--item-column-title-size);font-weight:var(--shell-font-weight);color:var(--shell-color);line-height:var(--item-column-title-line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c7x{flex-shrink:0;display:inline-flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:6px}.c7d{margin:0;font-size:var(--shell-font-size-sm);font-weight:400;color:var(--shell-color);line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.c7f{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 6px;font-size:12px;color:var(--shell-color-muted);line-height:1.35}.c7p{opacity:.5;-webkit-user-select:none;user-select:none}.c7a{flex-shrink:0;display:inline-flex;align-items:flex-start}.c7s .c7t{font-size:var(--shell-font-size-sm)}.c7s .c7d{font-size:12px}.c7l .c7t{font-size:var(--shell-font-size-lg)}.c7l .c7d{font-size:var(--shell-font-size)}.c7k .c7t{font-weight:700}}
30
30
 
31
- @layer elcrm-components{.clr{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box;background:var(--shell-background-muted);gap:0}.clx{height:var(--layout-height, 100dvh);max-height:var(--layout-height, 100dvh);overflow:hidden}.clm{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain}.clr:not(.clx){height:auto;max-height:none;overflow:visible}.clr:not(.clx) .clm{overflow:visible;flex:0 1 auto;min-height:auto}.cls{flex-direction:row;align-items:stretch}.clb{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.clr:not(.clx) .clb{overflow:visible}.cla{display:flex;flex-direction:column;flex:0 0 var(--sidebar-width, 248px);width:var(--sidebar-width, 248px);min-width:0;min-height:0;align-self:stretch}.clk{overflow:hidden}.cla:not(.clk){overflow:auto}.cla>*{flex:1 1 auto;min-height:0;width:100%;height:100%;max-height:100%}@media (max-width: 900px){.cla{flex-basis:72px;width:72px}}.clh,.clf{flex:0 0 auto;min-width:0}}
31
+ @layer elcrm-components{.clr{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;box-sizing:border-box;background:var(--shell-background-muted);margin:var(--layout-margin);padding:var(--layout-padding);max-width:var(--layout-max-width);gap:0}.clx{height:var(--layout-height, 100dvh);max-height:var(--layout-height, 100dvh);overflow:hidden}.clm{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain}.clr:not(.clx){height:auto;max-height:none;overflow:visible}.clr:not(.clx) .clm{overflow:visible;flex:0 1 auto;min-height:auto}.cls{flex-direction:row;align-items:stretch}.clb{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.clr:not(.clx) .clb{overflow:visible}.cla{display:flex;flex-direction:column;flex:0 0 var(--sidebar-width, 248px);width:var(--sidebar-width, 248px);min-width:0;min-height:0;align-self:stretch}.clk{overflow:hidden}.cla:not(.clk){overflow:auto}.cla>*{flex:1 1 auto;min-height:0;width:100%;height:100%;max-height:100%}@media (max-width: 900px){.cla{flex-basis:72px;width:72px}}.clh,.clf{flex:0 0 auto;min-width:0}}
32
32
 
33
33
  @layer elcrm-components{.csr{display:flex;flex-direction:column;gap:var(--shell-gap-sm);padding:0;margin:0;list-style:none}.csh{flex-direction:row;flex-wrap:wrap}}
34
34
 
package/dist/tokens.css CHANGED
@@ -25,6 +25,11 @@
25
25
  --shell-disabled-opacity: 0.55;
26
26
  --shell-z: var(--elcrm-z-popover, 1000);
27
27
  --layout-height: 100dvh;
28
+ /* отступ каркаса; 0 — на всю ширину вьюпорта */
29
+ --layout-padding: 0;
30
+ --layout-margin: 0;
31
+ /* none — без ограничения; иначе например 1440px */
32
+ --layout-max-width: none;
28
33
 
29
34
  /* —— header —— */
30
35
  --header-radius: var(--shell-radius);
@@ -34,6 +39,8 @@
34
39
  --header-z: 20;
35
40
  /* shorthand: толщина + стиль + цвет, все стороны. Только снизу — `none` и линия через --header-shadow */
36
41
  --header-border: 1px solid var(--shell-border);
42
+ /* выравнивание nav в оставшейся ширине шапки: flex-start | center | flex-end */
43
+ --header-nav-justify: flex-start;
37
44
 
38
45
  /* —— section / nav-sections —— */
39
46
  --section-padding: 1.5rem 1.75rem 2.5rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elcrm/components",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Shell UI elCRM: Layout, Header, Sidebar, Popover, PageHead, PageShell, Section, Stack, Stat, ActionGroup, TextGroup, ItemColumn и др.",
5
5
  "type": "module",
6
6
  "author": "MaSkal <dev@elcrm.online>",