@elcrm/components 0.1.1 → 0.2.0

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 (135) hide show
  1. package/README.md +130 -16
  2. package/dist/ActionGroup-ChKFW0mt.js +27 -0
  3. package/dist/ActionGroup.css +1 -0
  4. package/dist/ActionGroup.d.ts +42 -0
  5. package/dist/ActionGroup.js +4 -0
  6. package/dist/Avatar-loGgFt72.js +113 -0
  7. package/dist/Avatar.d.ts +11 -2
  8. package/dist/Avatar.js +3 -2
  9. package/dist/{AvatarGroup-BAWas4pU.js → AvatarGroup-D1gnT3Od.js} +1 -1
  10. package/dist/AvatarGroup.d.ts +2 -1
  11. package/dist/AvatarGroup.js +1 -1
  12. package/dist/AvatarName-0BzyfpMq.js +68 -0
  13. package/dist/AvatarName.css +1 -1
  14. package/dist/AvatarName.d.ts +8 -2
  15. package/dist/AvatarName.js +1 -1
  16. package/dist/Badge-jp-9iNNL.js +28 -0
  17. package/dist/Badge.css +1 -1
  18. package/dist/Badge.d.ts +33 -17
  19. package/dist/Badge.js +1 -1
  20. package/dist/{Block-BAXh0PPp.js → Block-Bu7NEA0g.js} +1 -1
  21. package/dist/Block.js +1 -1
  22. package/dist/Card.css +1 -1
  23. package/dist/Card.d.ts +8 -1
  24. package/dist/ChatFrame-BW2TmCkZ.js +37 -0
  25. package/dist/ChatFrame.css +1 -0
  26. package/dist/ChatList.d.ts +9 -0
  27. package/dist/ChatList.js +7 -0
  28. package/dist/ChatMessages-B8rdXiK-.js +19 -0
  29. package/dist/ChatMessages.d.ts +9 -0
  30. package/dist/ChatMessages.js +4 -0
  31. package/dist/ChatSplit-DNnu3TNq.js +21 -0
  32. package/dist/CheckIcon-FdB8fMEF.js +17 -0
  33. package/dist/CheckIcon.d.ts +8 -0
  34. package/dist/{Column-DDaqX-A7.js → Column-BiogBDf8.js} +1 -1
  35. package/dist/Column.js +1 -1
  36. package/dist/Dropdown-4yUB2tk1.js +142 -0
  37. package/dist/Dropdown.css +1 -1
  38. package/dist/Dropdown.d.ts +3 -3
  39. package/dist/Dropdown.js +1 -1
  40. package/dist/Footer.css +1 -1
  41. package/dist/Item-D4okhsfW.js +46 -0
  42. package/dist/Item.css +1 -1
  43. package/dist/Item.d.ts +8 -1
  44. package/dist/Item.js +1 -1
  45. package/dist/ItemColumn-M7a9L8CI.js +41 -0
  46. package/dist/ItemColumn.css +1 -0
  47. package/dist/ItemColumn.d.ts +62 -0
  48. package/dist/ItemColumn.js +4 -0
  49. package/dist/Layout-mi6Ghayv.js +31 -0
  50. package/dist/Layout.css +1 -1
  51. package/dist/Layout.d.ts +28 -6
  52. package/dist/Layout.js +1 -1
  53. package/dist/Loading-DkkG7RcE.js +16 -0
  54. package/dist/Loading.css +1 -1
  55. package/dist/Loading.d.ts +1 -1
  56. package/dist/Loading.js +1 -1
  57. package/dist/Menu-DuXQv-zT.js +66 -0
  58. package/dist/Menu.css +1 -1
  59. package/dist/Menu.d.ts +15 -3
  60. package/dist/Menu.js +1 -1
  61. package/dist/NavSections-BpyZPimZ.js +79 -0
  62. package/dist/NavSections.css +1 -1
  63. package/dist/NavSections.js +1 -1
  64. package/dist/PageHead-DgaJ1mXY.js +31 -0
  65. package/dist/PageHead.css +1 -1
  66. package/dist/PageHead.d.ts +28 -6
  67. package/dist/PageHead.js +1 -1
  68. package/dist/{PageShell-CghThHS3.js → PageShell-D9EZiO8o.js} +13 -11
  69. package/dist/PageShell.css +1 -1
  70. package/dist/PageShell.d.ts +11 -6
  71. package/dist/PageShell.js +1 -1
  72. package/dist/Popover-D0m5JWIt.js +142 -0
  73. package/dist/Popover.css +1 -0
  74. package/dist/Popover.d.ts +54 -0
  75. package/dist/Popover.js +4 -0
  76. package/dist/PricingGroup-Czuhe81Z.js +120 -0
  77. package/dist/PricingGroup.css +1 -1
  78. package/dist/PricingGroup.js +1 -1
  79. package/dist/{PricingTable-DALddSwM.js → PricingTable-BuLC0opK.js} +69 -75
  80. package/dist/PricingTable.css +1 -1
  81. package/dist/PricingTable.js +1 -1
  82. package/dist/RadioGroup-fcXzhvMu.js +129 -0
  83. package/dist/RadioGroup.css +1 -1
  84. package/dist/RadioGroup.js +1 -1
  85. package/dist/{Row--HIe6vb9.js → Row-DsQOVn18.js} +1 -1
  86. package/dist/Row.js +1 -1
  87. package/dist/Sidebar-CBYq1ad6.js +107 -0
  88. package/dist/Sidebar.css +1 -0
  89. package/dist/Sidebar.d.ts +99 -0
  90. package/dist/Sidebar.js +4 -0
  91. package/dist/Stack-XkmPSswj.js +53 -0
  92. package/dist/Stack.css +1 -1
  93. package/dist/Stack.d.ts +16 -4
  94. package/dist/Stack.js +3 -2
  95. package/dist/Stat-C5iZyEoo.js +27 -0
  96. package/dist/Stat.css +1 -0
  97. package/dist/Stat.d.ts +39 -0
  98. package/dist/Stat.js +4 -0
  99. package/dist/TabSections-DN7yVJoF.js +69 -0
  100. package/dist/TabSections.css +1 -1
  101. package/dist/TabSections.js +1 -1
  102. package/dist/TextGroup-DmhuyMR1.js +23 -0
  103. package/dist/TextGroup.css +1 -0
  104. package/dist/TextGroup.d.ts +36 -0
  105. package/dist/TextGroup.js +4 -0
  106. package/dist/badgeText-CRzlIzi3.js +8 -0
  107. package/dist/badgeText.d.ts +4 -0
  108. package/dist/dark.css +13 -0
  109. package/dist/index.css +31 -17
  110. package/dist/index.d.ts +27 -4
  111. package/dist/index.es.d.ts +82 -0
  112. package/dist/index.es.js +83 -59
  113. package/dist/light.css +13 -0
  114. package/dist/overlayZ.d.ts +9 -0
  115. package/dist/rovingKeys-CGlfTtNQ.js +15 -0
  116. package/dist/rovingKeys.d.ts +13 -0
  117. package/dist/tokens.css +44 -4
  118. package/dist/types.d.ts +2 -0
  119. package/dist/useControllableValue-CWqH1kpM.js +10 -0
  120. package/dist/useControllableValue.d.ts +4 -0
  121. package/package.json +6 -2
  122. package/dist/Avatar-CyNXYvsa.js +0 -101
  123. package/dist/AvatarName-DhD3TytF.js +0 -56
  124. package/dist/Badge-l6nUkl-H.js +0 -22
  125. package/dist/Dropdown-DQlDiWCR.js +0 -168
  126. package/dist/Item-CaU4RW2G.js +0 -38
  127. package/dist/Layout-Xb3-xsmA.js +0 -18
  128. package/dist/Loading-ChGq9peU.js +0 -15
  129. package/dist/Menu-DRBHvSdJ.js +0 -40
  130. package/dist/NavSections-NDcIDD6H.js +0 -59
  131. package/dist/PageHead-Baz9BLuA.js +0 -22
  132. package/dist/PricingGroup-DgcdVSq8.js +0 -126
  133. package/dist/RadioGroup-D2UUOTkY.js +0 -138
  134. package/dist/Stack-b0-XWwQ1.js +0 -31
  135. package/dist/TabSections-DL7DufKU.js +0 -50
package/README.md CHANGED
@@ -4,6 +4,17 @@ Shell UI для elCRM: `Layout`, `Header`, `Section`, `Stack`, `NavSections`, `T
4
4
 
5
5
  Стили — CSS Modules с короткими уникальными классами; значения токенов задаёт приложение (`theme-dark.css` / `theme-light.css`). Fallback в пакете нет.
6
6
 
7
+ ## Визуальный стенд
8
+
9
+ Как у `@elcrm/form` — страница с галереей компонентов (`@elcrm/storybook`):
10
+
11
+ ```bash
12
+ bun i
13
+ bun run dev
14
+ ```
15
+
16
+ Откроется `http://localhost:3001`. В сайдбаре стенда: `section` (компонент / `all`), `size`, тон Badge, вариант Menu.
17
+
7
18
  ## Установка
8
19
 
9
20
  ```bash
@@ -17,7 +28,11 @@ import {
17
28
  Footer,
18
29
  List,
19
30
  Item,
31
+ ItemColumn,
20
32
  Card,
33
+ ActionGroup,
34
+ TextGroup,
35
+ Stat,
21
36
  Stack,
22
37
  Menu,
23
38
  Loading,
@@ -37,6 +52,11 @@ import {
37
52
  Breadcrumb,
38
53
  PageHead,
39
54
  PageShell,
55
+ ChatList,
56
+ ChatMessages,
57
+ ChatSplit,
58
+ Sidebar,
59
+ Popover,
40
60
  createLazyResolver,
41
61
  } from "@elcrm/components";
42
62
  import type { PricingPlan } from "@elcrm/components";
@@ -56,28 +76,37 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
56
76
 
57
77
  | | Назначение | Ключевые props |
58
78
  | -------------------------------- | -------------------------------- | ---------------------------------------------------- |
59
- | **Layout** | Оболочка страницы | `header`, `footer`, `children` |
79
+ | **Layout** | Оболочка страницы | `sidebar`, `header`, `footer`, `fixed`, `children` |
60
80
  | **Header** | Шапка со слотами | `brand`, `nav`, `search`, `actions` |
61
81
  | **Footer** | Подвал | `children` |
62
82
  | **Brand** | Название / логотип | `as`, `children`, `onClick` |
63
- | **Avatar** | Фото или инициалы | `src`, `name` (инициалы), `size`, `status`, `as` |
64
- | **AvatarName** | Аватар + title + description | `title`, `description`, `name?`, `size`, `status` |
83
+ | **Avatar** | Фото или инициалы | `src`, `name`, `size`, `radius`, `status`, `as` |
84
+ | **AvatarName** | Аватар + title + description | `title`, `description`, `radius`, `--avatar-name-*-color` |
65
85
  | **AvatarGroup** | Стопка аватаров + «+N» | `items` (`value`/`title`/`name`), `max`, `add` |
66
- | **Badge** | Метка / статус | `tone`, `children` |
86
+ | **Badge** | Метка / статус | `tone`, `size`, `dot`, `children` |
67
87
  | **RadioGroup** | Выбор одной опции-карточки | `items`, `value`, `indicator`, `indicatorSide` |
68
88
  | **PricingGroup** | Тарифные карточки в ряд | `items` (`PricingPlan`), `selectable`, `value` |
69
89
  | **PricingTable** | Сравнение тарифов (таблица) | те же `items` + `sections` |
70
- | **Menu** | Навигация в шапке (не popover) | `items` (`value`), `value`, `onSelect` |
71
- | **Dropdown** | Выпадающее меню (items) | `label`/`trigger`, `items`/`groups`, `onSelect` |
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` |
72
93
  | **Breadcrumb** | Хлебные крошки | `items`, `separator`, `collapseAfter` |
73
- | **PageShell** | Каркас модуля (скролл + footer) | `wide`, `footer`, `children` |
74
- | **PageHead** | Заголовок страницы + actions | `title`, `subtitle`, `actions` |
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` |
75
100
  | **Section** | Блок контента / auth / подраздел | `title`, `description`, `icon`, `actions`, `variant` |
76
101
  | **NavSections** | Сайдбар + lazy-панель | `groups` (`value`), `resolveSection`, `defaultValue` |
77
102
  | **TabSections** | Горизонтальные вкладки + lazy | `items` (`value` + `badge`), `resolveSection` |
78
- | **Card** | Карточка | `title`, `children` |
79
- | **List** / **Item** | Список и строка | List: `row`; Item: `as`, `active` |
80
- | **Stack** | Ряд / колонка | `direction`, `gap` |
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` |
81
110
  | **Loading** | Плейсхолдер загрузки | `children` |
82
111
  | **EmptyState** | Заглушка «нет данных» | `title`, `description`, `icon`, `actions` |
83
112
  | **createLazyResolver** | Кэш `import()` секций | `(key) => import(…)` |
@@ -86,11 +115,51 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
86
115
 
87
116
  `Block` / `Row` / `Column` — deprecated-обёртки над `Stack`. В проектах: `elcrm migrate components`.
88
117
 
118
+ ### Sidebar
119
+
120
+ Левое меню приложения. `variant="cards"` — секции-карточки; `variant="plain"` — плоский список (backoffice). Иконки, колокольчик, тема, карточка пользователя — слоты приложения. Роутер не подключаем.
121
+
122
+ ```tsx
123
+ <Sidebar
124
+ variant="cards"
125
+ value={page}
126
+ onSelect={setPage}
127
+ brand={<Brand as="strong">App</Brand>}
128
+ extra={<Notifications.Bell />}
129
+ groups={[
130
+ { value: "main", items: [{ value: "home", label: "Обзор", icon: <Icon /> }] },
131
+ { value: "admin", title: "Админ", items: [{ value: "ops", label: "Операции" }] },
132
+ ]}
133
+ footer={<AvatarName title="Паша" description="mail@…" />}
134
+ footerItems={[
135
+ { value: "docs", label: "Документация", icon: <Icon /> },
136
+ { value: "theme", label: "Тема", submenu: true },
137
+ { value: "logout", label: "Выйти", danger: true },
138
+ ]}
139
+ onFooterSelect={(value) => console.log(value)}
140
+ version="v 0.2.26"
141
+ />
142
+ ```
143
+
144
+ Токены: `--sidebar-width`, `--sidebar-item-font-size`, `--sidebar-item-padding`, `--sidebar-icon-size`, `--sidebar-group-font-size`, `--sidebar-item-active`. Для orange active в `plain` переопределите `--sidebar-item-active` в теме приложения.
145
+
89
146
  ### Stack
90
147
 
91
148
  ```tsx
92
149
  <Stack gap="0.75rem">{/* колонка */}</Stack>
93
150
  <Stack direction="row" gap={8}>{/* ряд */}</Stack>
151
+ <Stack direction="grid" gap={16}>{/* auto-fit, мин. ширина --stack-grid-min */}</Stack>
152
+ <Stack direction="grid" columns={3} gap={12}>{/* stretch; не height:100% у детей */}</Stack>
153
+ ```
154
+
155
+ ### Card
156
+
157
+ Курсор задаёт приложение токеном `--card-pointer` (`auto` по умолчанию):
158
+
159
+ ```tsx
160
+ <Card title="Тенанты" style={{ ["--card-pointer" as string]: "pointer" }}>
161
+ 12
162
+ </Card>
94
163
  ```
95
164
 
96
165
  ### RadioGroup
@@ -255,17 +324,53 @@ const sections = [
255
324
 
256
325
  | | Роль |
257
326
  | --- | --- |
258
- | **PageShell** | Каркас модуля: скролл в body, опциональный footer снизу |
259
- | **PageHead** | Строка title (+ subtitle) слева и actions справа |
327
+ | **PageShell** | Каркас модуля: скролл в body (`scroll`, по умолчанию да), опциональный footer снизу |
328
+ | **PageHead** | Строка title (+ `sup` надстрочно, subtitle) слева и actions справа; `sticky` — прилипает к верху скролла |
260
329
  | **Section** | Блок контента / auth-карточка / подраздел с title+description |
261
330
 
262
331
  ```tsx
263
- <PageShell footer={<button type="button">Сохранить</button>}>
264
- <PageHead title="Товары" subtitle="Каталог" actions={<button>Добавить</button>} />
332
+ <PageShell scroll footer={<button type="button">Сохранить</button>}>
333
+ <PageHead sticky title="Товары" sup={12} subtitle="Каталог" actions={<button>Добавить</button>} />
265
334
  <Section title="Фильтры">{/* … */}</Section>
266
335
  </PageShell>
267
336
  ```
268
337
 
338
+ ### ChatList / ChatMessages / ChatSplit
339
+
340
+ Каркас мессенджера, не пузыри и не композер. Строки диалогов — `List` + `Item` (+ `ItemColumn` / `Avatar`). Поиск и ввод — слоты `header` / `footer` приложения.
341
+
342
+ | | Роль |
343
+ | --- | --- |
344
+ | **ChatList** | Колонка диалогов: `header` (поиск), `children` (список), `footer` |
345
+ | **ChatMessages** | Колонка переписки: `header` (собеседник), `children` (лента), `footer` (ввод) |
346
+ | **ChatSplit** | Ряд `list` + `messages` (`--chat-split-gap`) |
347
+
348
+ Токены: `--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
+
350
+ ```tsx
351
+ <ChatSplit
352
+ list={
353
+ <ChatList header={<SearchField name="q" placeholder="Найти" />}>
354
+ <List>
355
+ <li>
356
+ <Item as="button" active>
357
+ <ItemColumn icon={<Avatar name="Иван" />} title="Иван" description="Привет" />
358
+ </Item>
359
+ </li>
360
+ </List>
361
+ </ChatList>
362
+ }
363
+ messages={
364
+ <ChatMessages
365
+ header={<AvatarName title="Иван" description="в сети" />}
366
+ footer={<TextareaField name="body" placeholder="Сообщение" />}
367
+ >
368
+ {bubbles}
369
+ </ChatMessages>
370
+ }
371
+ />
372
+ ```
373
+
269
374
  ### AvatarGroup
270
375
 
271
376
  Стопка аватаров через `items`:
@@ -407,7 +512,7 @@ const people = [
407
512
 
408
513
  `Menu` — навигация в шапке (inline, не popover).
409
514
 
410
- **Dropdown vs `@elcrm/overlay`:** в пакете `Dropdown` — готовое меню по `items`/`groups` (контент + поведение выбора). `@elcrm/overlay` portal, позиционирование и z-index стек, когда нужен свой попап или общий слой поверх страницы. Не смешивать: для типового меню пунктов `Dropdown`; для кастомного портала — overlay.
515
+ **Dropdown vs Popover:** `Dropdown` — пункты `items`/`groups`. `Popover` свой `children` в portal (тот же z-стек, что у `@elcrm/form`). `@elcrm/overlay` `Menu` устаревший алиас `Popover`. Select`@elcrm/form`, не overlay.
411
516
 
412
517
  ### Breadcrumb
413
518
 
@@ -449,6 +554,8 @@ const people = [
449
554
 
450
555
  ```tsx
451
556
  <Layout
557
+ fixed={["header", "footer", "sidebar"]}
558
+ sidebar={<Sidebar groups={groups} value={page} onSelect={setPage} />}
452
559
  header={
453
560
  <Header
454
561
  brand={<Brand onClick={() => {}}>App</Brand>}
@@ -473,6 +580,10 @@ const people = [
473
580
  </Section>
474
581
  </Layout>;
475
582
 
583
+ {/* `fixed` по умолчанию = header + footer + sidebar. Скролл только у children.
584
+ Высота каркаса: `--layout-height` (100dvh). Вложенно: 100% + явная высота родителя.
585
+ `fixed={[]}` — обычный поток страницы. */}
586
+
476
587
  {
477
588
  /* Экран входа */
478
589
  }
@@ -563,6 +674,7 @@ const resolve = createLazyResolver((value) => import(`./sections/${value}.tsx`))
563
674
  --shell-color-warning
564
675
  --shell-border (= --line, только цвет)
565
676
  --shell-background (= --panel)
677
+ --shell-bg фон страницы (sticky PageHead)
566
678
  --shell-background-muted / -hover / -selected
567
679
  --shell-shadow
568
680
  ```
@@ -576,6 +688,8 @@ const resolve = createLazyResolver((value) => import(`./sections/${value}.tsx`))
576
688
  --avatar-size-s|m|l / --avatar-group-overlap
577
689
  --dropdown-min-width / --dropdown-max-width / --dropdown-max-height
578
690
  --pricing-card-min-width / --pricing-table-label-width
691
+ --card-pointer
692
+ --stack-grid-min
579
693
  --row-gap / --column-gap
580
694
  --popup-shadow
581
695
  ```
@@ -0,0 +1,27 @@
1
+ import e from "react";
2
+ import { c as n } from "./cx-CcykAxZN.js";
3
+ import './ActionGroup.css';const s = "c5r", i = {
4
+ r: s
5
+ };
6
+ function p({
7
+ label: r,
8
+ items: o,
9
+ children: t,
10
+ className: a,
11
+ ...c
12
+ }) {
13
+ return /* @__PURE__ */ e.createElement(
14
+ "div",
15
+ {
16
+ className: n(i.r, a),
17
+ role: "toolbar",
18
+ "aria-label": r,
19
+ ...c
20
+ },
21
+ o,
22
+ t
23
+ );
24
+ }
25
+ export {
26
+ p as A
27
+ };
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.c5r{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--action-group-gap);flex:0 0 auto}}
@@ -0,0 +1,42 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * Ряд действий в строке списка / карточке (`role="toolbar"`).
4
+ *
5
+ * Роли: тулбар иконок справа в Card. Кнопки — `@elcrm/button` / свои слоты,
6
+ * не нативный `<button>` в приложении.
7
+ *
8
+ * @param label подпись для `aria-label`
9
+ * @param items массив узлов (если нет — только `children`)
10
+ * @param children слот кнопок; можно вместе с `items` (сначала items)
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <ActionGroup
15
+ * label="Действия: Иван"
16
+ * items={[
17
+ * <Button key="edit" variant="ghost" onClick={edit}>Изменить</Button>,
18
+ * <Button key="del" variant="ghost" onClick={remove}>Удалить</Button>,
19
+ * ]}
20
+ * />
21
+ *
22
+ * <ActionGroup label="Ещё">
23
+ * <Button variant="ghost">Копировать</Button>
24
+ * </ActionGroup>
25
+ * ```
26
+ *
27
+ * Токены: `--action-group-gap` (иначе `--shell-gap-sm`).
28
+ */
29
+ export type ActionGroupProps = React.HTMLAttributes<HTMLDivElement> & {
30
+ /** Подпись тулбара (`aria-label`) */
31
+ label: string;
32
+ /** Кнопки массивом узлов */
33
+ items?: React.ReactNode[];
34
+ /** Свои кнопки / слоты */
35
+ children?: React.ReactNode;
36
+ };
37
+ /**
38
+ * Группа действий: `items` и/или `children`.
39
+ *
40
+ * @see Card, Stack, Item
41
+ */
42
+ export declare function ActionGroup({ label, items, children, className, ...rest }: ActionGroupProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { A as p } from "./ActionGroup-ChKFW0mt.js";
2
+ export {
3
+ p as ActionGroup
4
+ };
@@ -0,0 +1,113 @@
1
+ import a from "react";
2
+ import { c as p } from "./cx-CcykAxZN.js";
3
+ import './Avatar.css';const h = "car", x = "cav", A = "cas", C = "cam", S = "cal", z = "caa", B = "cai", F = "cab", R = "cao", U = "caf", e = {
4
+ r: h,
5
+ v: x,
6
+ s: A,
7
+ m: C,
8
+ l: S,
9
+ a: z,
10
+ i: B,
11
+ b: F,
12
+ o: R,
13
+ f: U
14
+ };
15
+ function $(t) {
16
+ const n = t.trim();
17
+ if (!n) return "?";
18
+ const s = n.split(/\s+/).filter(Boolean);
19
+ return s.length >= 2 ? (s[0][0] + s[1][0]).toUpperCase() : n.slice(0, 2).toUpperCase();
20
+ }
21
+ function D(t) {
22
+ if (!(t == null || t === ""))
23
+ return {
24
+ "--avatar-radius": typeof t == "number" ? `${t}px` : t
25
+ };
26
+ }
27
+ function j({ status: t }) {
28
+ return /* @__PURE__ */ a.createElement(
29
+ "span",
30
+ {
31
+ className: p(e.b, t === "online" ? e.o : e.f),
32
+ "data-status": t,
33
+ "aria-hidden": !0
34
+ }
35
+ );
36
+ }
37
+ function k({
38
+ src: t,
39
+ label: n,
40
+ children: s,
41
+ name: c,
42
+ status: o
43
+ }) {
44
+ const l = t != null && t !== "" ? /* @__PURE__ */ a.createElement("img", { className: e.i, src: t, alt: n ?? "", draggable: !1 }) : s ?? (c != null ? $(c) : "?");
45
+ return /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement("span", { className: e.v }, l), o != null ? /* @__PURE__ */ a.createElement(j, { status: o }) : null);
46
+ }
47
+ function G(t) {
48
+ const {
49
+ src: n,
50
+ alt: s,
51
+ name: c,
52
+ size: o = "m",
53
+ status: l,
54
+ active: i,
55
+ children: b,
56
+ className: y,
57
+ radius: E,
58
+ as: f = "span",
59
+ style: N,
60
+ ...m
61
+ } = t, g = o === "s" ? e.s : o === "l" ? e.l : e.m, u = p(e.r, g, i && e.a, y), r = s ?? c, d = { ...D(E), ...N }, v = /* @__PURE__ */ a.createElement(
62
+ k,
63
+ {
64
+ src: n,
65
+ label: r,
66
+ name: c,
67
+ status: l,
68
+ children: b
69
+ }
70
+ );
71
+ return f === "button" ? /* @__PURE__ */ a.createElement(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ className: u,
76
+ "aria-label": r,
77
+ "data-active": i ? "true" : void 0,
78
+ "data-status": l,
79
+ style: d,
80
+ ...m
81
+ },
82
+ v
83
+ ) : f === "div" ? /* @__PURE__ */ a.createElement(
84
+ "div",
85
+ {
86
+ className: u,
87
+ role: r ? "img" : void 0,
88
+ "aria-label": r,
89
+ "data-active": i ? "true" : void 0,
90
+ "data-status": l,
91
+ style: d,
92
+ ...m
93
+ },
94
+ v
95
+ ) : /* @__PURE__ */ a.createElement(
96
+ "span",
97
+ {
98
+ className: u,
99
+ role: r ? "img" : void 0,
100
+ "aria-label": r,
101
+ "data-active": i ? "true" : void 0,
102
+ "data-status": l,
103
+ style: d,
104
+ ...m
105
+ },
106
+ v
107
+ );
108
+ }
109
+ export {
110
+ G as A,
111
+ D as a,
112
+ $ as i
113
+ };
package/dist/Avatar.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { UiSize } from './types';
2
3
  /**
3
4
  * Инициалы из имени: «Иван Петров» → «ИП», «admin» → «AD».
4
5
  *
@@ -25,16 +26,23 @@ type Common = {
25
26
  */
26
27
  name?: string;
27
28
  /** Размер: `s` | `m` | `l`. */
28
- size?: "s" | "m" | "l";
29
+ size?: UiSize;
29
30
  /** Точка статуса (online / offline). */
30
31
  status?: AvatarStatus;
31
32
  /** Подсветка (например активный пункт шапки). */
32
33
  active?: boolean;
34
+ /**
35
+ * Скругление. Число — пиксели (`10` → `10px`), строка — любой CSS.
36
+ * Пишет `--avatar-radius` на корне (дефолт токена — круг, `999px`).
37
+ */
38
+ radius?: number | string;
33
39
  /** Кастомное содержимое вместо инициалов (если нет `src`). */
34
40
  children?: React.ReactNode;
35
41
  /** CSS-класс корня. */
36
42
  className?: string;
37
43
  };
44
+ /** Стиль `--avatar-radius` из пропа `radius`. */
45
+ export declare function avatarRadiusStyle(radius?: number | string): React.CSSProperties | undefined;
38
46
  /**
39
47
  * Props {@link Avatar}: корень как `button` | `span` | `div`.
40
48
  *
@@ -59,7 +67,8 @@ export type AvatarProps = (Common & React.ButtonHTMLAttributes<HTMLButtonElement
59
67
  * (`name` у Avatar остаётся только для инициалов);
60
68
  * - {@link AvatarGroup} — стопка аватаров из `items`.
61
69
  *
62
- * CSS-токены: `--avatar-*` (в т.ч. `--avatar-size-s|m|l`).
70
+ * CSS-токены: `--avatar-*` (в т.ч. `--avatar-size-s|m|l`, `--avatar-radius`).
71
+ * Скругление: `radius={10}` или `style={{ ["--avatar-radius"]: "10px" }}`.
63
72
  *
64
73
  * @example
65
74
  * ```tsx
package/dist/Avatar.js CHANGED
@@ -1,5 +1,6 @@
1
- import { A as r, i as m } from "./Avatar-CyNXYvsa.js";
1
+ import { A as r, a as s, i as t } from "./Avatar-loGgFt72.js";
2
2
  export {
3
3
  r as Avatar,
4
- m as initialsFromName
4
+ s as avatarRadiusStyle,
5
+ t as initialsFromName
5
6
  };
@@ -1,5 +1,5 @@
1
1
  import n from "react";
2
- import { A as f } from "./Avatar-CyNXYvsa.js";
2
+ import { A as f } from "./Avatar-loGgFt72.js";
3
3
  import { c as u } from "./cx-CcykAxZN.js";
4
4
  import './AvatarGroup.css';const G = "cyr", I = "cyi", M = "cyo", R = "cyd", S = "cys", j = "cym", q = "cyl", l = {
5
5
  r: G,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { UiSize } from './types';
2
3
  /**
3
4
  * Элемент стопки {@link AvatarGroup}.
4
5
  *
@@ -30,7 +31,7 @@ export type AvatarGroupProps = React.HTMLAttributes<HTMLDivElement> & {
30
31
  /** Сколько аватаров показать до «+N». По умолчанию все. */
31
32
  max?: number;
32
33
  /** Размер аватаров: `s` | `m` | `l`. */
33
- size?: "s" | "m" | "l";
34
+ size?: UiSize;
34
35
  /** Кнопка «+» справа. */
35
36
  add?: boolean;
36
37
  /** Подпись для кнопки добавления (`aria-label`). */
@@ -1,4 +1,4 @@
1
- import { A as o } from "./AvatarGroup-BAWas4pU.js";
1
+ import { A as o } from "./AvatarGroup-D1gnT3Od.js";
2
2
  export {
3
3
  o as AvatarGroup
4
4
  };
@@ -0,0 +1,68 @@
1
+ import t from "react";
2
+ import { A as b } from "./Avatar-loGgFt72.js";
3
+ import { c as g } from "./cx-CcykAxZN.js";
4
+ import './AvatarName.css';const y = "czr", A = "czc", h = "czx", C = "czt", F = "czd", I = "czs", R = "czm", S = "czl", e = {
5
+ r: y,
6
+ c: A,
7
+ x: h,
8
+ t: C,
9
+ d: F,
10
+ s: I,
11
+ m: R,
12
+ l: S
13
+ };
14
+ function $(s, n) {
15
+ if (s != null && s !== "") return s;
16
+ if (typeof n == "string") return n;
17
+ }
18
+ function w(s) {
19
+ const {
20
+ src: n,
21
+ name: c,
22
+ title: m,
23
+ description: i,
24
+ size: a = "m",
25
+ radius: p,
26
+ status: z,
27
+ className: E,
28
+ children: N,
29
+ extra: u,
30
+ as: d = "div",
31
+ ...r
32
+ } = s, x = a === "s" ? e.s : a === "l" ? e.l : e.m, l = g(e.r, x, E), f = m ?? (typeof c == "string" ? c : void 0), v = $(c, m), o = /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement(
33
+ b,
34
+ {
35
+ src: n,
36
+ name: v,
37
+ size: a,
38
+ radius: p,
39
+ status: z
40
+ }
41
+ ), /* @__PURE__ */ t.createElement("span", { className: e.c }, f != null ? /* @__PURE__ */ t.createElement("span", { className: e.t }, f) : null, i != null ? /* @__PURE__ */ t.createElement("span", { className: e.d }, i) : null, N), u != null ? /* @__PURE__ */ t.createElement("span", { className: e.x }, u) : null);
42
+ return d === "button" ? /* @__PURE__ */ t.createElement(
43
+ "button",
44
+ {
45
+ type: "button",
46
+ className: l,
47
+ ...r
48
+ },
49
+ o
50
+ ) : d === "a" ? /* @__PURE__ */ t.createElement(
51
+ "a",
52
+ {
53
+ className: l,
54
+ ...r
55
+ },
56
+ o
57
+ ) : /* @__PURE__ */ t.createElement(
58
+ "div",
59
+ {
60
+ className: l,
61
+ ...r
62
+ },
63
+ o
64
+ );
65
+ }
66
+ export {
67
+ w as A
68
+ };
@@ -1 +1 @@
1
- @layer elcrm-components{.czr{display:inline-flex;align-items:center;gap:var(--shell-gap);max-width:100%;text-align:left;vertical-align:middle}.czr:is(button,a){-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:0;padding:0;border:none;background:transparent;font:inherit;color:inherit;cursor:pointer;text-decoration:none}.czc{display:flex;flex-direction:column;gap:2px;min-width:0}.czt{margin:0;font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);color:var(--shell-color);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.czd{margin:0;font-size:var(--shell-font-size-sm);font-weight:400;color:var(--shell-color-muted);line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.czs{gap:var(--shell-gap-sm)}.czs .czt{font-size:var(--shell-font-size-sm)}.czm{gap:var(--shell-gap)}.czl{gap:var(--shell-gap-lg)}.czl .czt{font-size:var(--shell-font-size-lg)}.czl .czd{font-size:var(--shell-font-size)}}
1
+ @layer elcrm-components{.czr{display:inline-flex;align-items:center;gap:var(--shell-gap);max-width:100%;text-align:left;vertical-align:middle}.czr:is(button,a){-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:0;padding:0;border:none;background:transparent;font:inherit;color:inherit;cursor:pointer;text-decoration:none}.czc{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.czx{display:inline-flex;flex-shrink:0;align-items:center;margin-left:auto;color:var(--shell-color-muted);line-height:0}.czt{margin:0;font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);color:var(--avatar-name-title-color);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.czd{margin:0;font-size:var(--shell-font-size-sm);font-weight:400;color:var(--avatar-name-description-color);line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.czs{gap:var(--shell-gap-sm)}.czs .czt{font-size:var(--shell-font-size-sm)}.czm{gap:var(--shell-gap)}.czl{gap:var(--shell-gap-lg)}.czl .czt{font-size:var(--shell-font-size-lg)}.czl .czd{font-size:var(--shell-font-size)}}
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { AvatarStatus } from './Avatar';
3
+ import { UiSize } from './types';
3
4
  /**
4
5
  * Общие поля блока «аватар + текст».
5
6
  *
@@ -19,13 +20,17 @@ type Common = {
19
20
  /** Подпись: email, роль… */
20
21
  description?: React.ReactNode;
21
22
  /** Размер: `s` | `m` | `l`. */
22
- size?: "s" | "m" | "l";
23
+ size?: UiSize;
24
+ /** Скругление аватара (`10` → `10px`). Токен `--avatar-radius`. */
25
+ radius?: number | string;
23
26
  /** Статус у аватара. */
24
27
  status?: AvatarStatus;
25
28
  /** CSS-класс корня. */
26
29
  className?: string;
27
30
  /** Доп. контент в текстовой колонке. */
28
31
  children?: React.ReactNode;
32
+ /** Справа: ⋮, шеврон, бейдж. Нет — не рисуем. */
33
+ extra?: React.ReactNode;
29
34
  };
30
35
  /**
31
36
  * Props {@link AvatarName}: корень как `button` | `a` | `div`.
@@ -50,7 +55,8 @@ export type AvatarNameProps = (Common & React.ButtonHTMLAttributes<HTMLButtonEle
50
55
  *
51
56
  * Канон: `title` / `description` для UI-текста; `name` — только инициалы.
52
57
  *
53
- * CSS-токены: `--shell-*` (типографика строки), аватар — `--avatar-*`.
58
+ * CSS-токены: `--avatar-name-title-color`, `--avatar-name-description-color`,
59
+ * аватар — `--avatar-*`.
54
60
  *
55
61
  * @example
56
62
  * ```tsx
@@ -1,4 +1,4 @@
1
- import { A as e } from "./AvatarName-DhD3TytF.js";
1
+ import { A as e } from "./AvatarName-0BzyfpMq.js";
2
2
  export {
3
3
  e as AvatarName
4
4
  };