@elcrm/components 0.0.3 → 0.1.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 (108) hide show
  1. package/README.md +450 -202
  2. package/dist/Avatar-Jzan87XD.js +101 -0
  3. package/dist/Avatar.css +1 -1
  4. package/dist/Avatar.d.ts +56 -5
  5. package/dist/Avatar.js +1 -1
  6. package/dist/AvatarGroup-DUe7-T3B.js +81 -0
  7. package/dist/AvatarGroup.css +1 -0
  8. package/dist/AvatarGroup.d.ts +77 -0
  9. package/dist/AvatarGroup.js +4 -0
  10. package/dist/AvatarName-KBbQrVta.js +56 -0
  11. package/dist/AvatarName.css +1 -0
  12. package/dist/AvatarName.d.ts +76 -0
  13. package/dist/AvatarName.js +4 -0
  14. package/dist/Badge.css +1 -1
  15. package/dist/Badge.d.ts +16 -1
  16. package/dist/Block-BAXh0PPp.js +8 -0
  17. package/dist/Block.d.ts +27 -6
  18. package/dist/Block.js +1 -1
  19. package/dist/Brand.css +1 -1
  20. package/dist/Brand.d.ts +20 -1
  21. package/dist/Breadcrumb-D0wXkE38.js +98 -0
  22. package/dist/Breadcrumb.css +1 -0
  23. package/dist/Breadcrumb.d.ts +80 -0
  24. package/dist/Breadcrumb.js +4 -0
  25. package/dist/Card.css +1 -1
  26. package/dist/Card.d.ts +22 -1
  27. package/dist/Column-DDaqX-A7.js +8 -0
  28. package/dist/Column.d.ts +23 -7
  29. package/dist/Column.js +1 -1
  30. package/dist/Dropdown-Cw5LKpcT.js +168 -0
  31. package/dist/Dropdown.css +1 -0
  32. package/dist/Dropdown.d.ts +124 -0
  33. package/dist/Dropdown.js +4 -0
  34. package/dist/EmptyState.css +1 -1
  35. package/dist/EmptyState.d.ts +24 -1
  36. package/dist/Footer.css +1 -1
  37. package/dist/Footer.d.ts +20 -1
  38. package/dist/Header.css +1 -1
  39. package/dist/Header.d.ts +25 -2
  40. package/dist/Item.css +1 -1
  41. package/dist/Item.d.ts +24 -1
  42. package/dist/Layout.css +1 -1
  43. package/dist/Layout.d.ts +23 -3
  44. package/dist/List.css +1 -1
  45. package/dist/List.d.ts +22 -1
  46. package/dist/Loading.css +1 -1
  47. package/dist/Loading.d.ts +19 -1
  48. package/dist/Menu-DRBHvSdJ.js +40 -0
  49. package/dist/Menu.css +1 -1
  50. package/dist/Menu.d.ts +64 -8
  51. package/dist/Menu.js +1 -1
  52. package/dist/{NavSections-CM-VPai2.js → NavSections-NDcIDD6H.js} +26 -26
  53. package/dist/NavSections.css +1 -1
  54. package/dist/NavSections.d.ts +79 -15
  55. package/dist/NavSections.js +1 -1
  56. package/dist/PageHead-Baz9BLuA.js +22 -0
  57. package/dist/PageHead.css +1 -0
  58. package/dist/PageHead.d.ts +36 -0
  59. package/dist/PageHead.js +4 -0
  60. package/dist/PageShell-CghThHS3.js +28 -0
  61. package/dist/PageShell.css +1 -0
  62. package/dist/PageShell.d.ts +33 -0
  63. package/dist/PageShell.js +4 -0
  64. package/dist/PricingGroup-CiqQtzjd.js +126 -0
  65. package/dist/PricingGroup.css +1 -0
  66. package/dist/PricingGroup.d.ts +130 -0
  67. package/dist/PricingGroup.js +4 -0
  68. package/dist/PricingTable-DYTJG4AU.js +158 -0
  69. package/dist/PricingTable.css +1 -0
  70. package/dist/PricingTable.d.ts +108 -0
  71. package/dist/PricingTable.js +4 -0
  72. package/dist/RadioGroup-DxoPHPIF.js +138 -0
  73. package/dist/RadioGroup.css +1 -0
  74. package/dist/RadioGroup.d.ts +111 -0
  75. package/dist/RadioGroup.js +4 -0
  76. package/dist/Row--HIe6vb9.js +8 -0
  77. package/dist/Row.d.ts +23 -7
  78. package/dist/Row.js +1 -1
  79. package/dist/Section.css +1 -1
  80. package/dist/Section.d.ts +23 -3
  81. package/dist/Stack-b0-XWwQ1.js +31 -0
  82. package/dist/Stack.css +1 -0
  83. package/dist/Stack.d.ts +34 -0
  84. package/dist/Stack.js +4 -0
  85. package/dist/TabSections-DL7DufKU.js +50 -0
  86. package/dist/TabSections.css +1 -1
  87. package/dist/TabSections.d.ts +69 -13
  88. package/dist/TabSections.js +1 -1
  89. package/dist/cx.d.ts +8 -1
  90. package/dist/dark.css +24 -0
  91. package/dist/index.css +32 -18
  92. package/dist/index.d.ts +21 -1
  93. package/dist/index.es.js +58 -38
  94. package/dist/lazyRoute.d.ts +19 -3
  95. package/dist/light.css +25 -0
  96. package/dist/stackGap.d.ts +13 -1
  97. package/dist/tokens.css +57 -0
  98. package/package.json +20 -4
  99. package/dist/Avatar-DHNPaIjZ.js +0 -64
  100. package/dist/Block-CVKukh1e.js +0 -28
  101. package/dist/Block.css +0 -1
  102. package/dist/Column-DlNrvFq1.js +0 -26
  103. package/dist/Column.css +0 -1
  104. package/dist/Menu-DS7zKQbY.js +0 -40
  105. package/dist/Row-CnD7Q7pD.js +0 -20
  106. package/dist/Row.css +0 -1
  107. package/dist/TabSections-oeEBx0nM.js +0 -50
  108. package/dist/stackGap-CDsjER0g.js +0 -7
@@ -0,0 +1,138 @@
1
+ import e from "react";
2
+ import { c as r } from "./cx-CcykAxZN.js";
3
+ import './RadioGroup.css';const F = "cqr", L = "cqi", M = "cqs", w = "cqp", B = "cqh", I = "cqn", j = "cqm", A = "cqmf", G = "cqc", S = "cqt", T = "cqe", U = "cqd", W = "cqx", $ = "cqy", C = "cqz", H = "cqk", J = "cqkr", K = "cqkc", O = "cqkv", c = {
4
+ r: F,
5
+ i: L,
6
+ s: M,
7
+ p: w,
8
+ h: B,
9
+ n: I,
10
+ m: j,
11
+ mf: A,
12
+ c: G,
13
+ t: S,
14
+ e: T,
15
+ d: U,
16
+ x: W,
17
+ y: $,
18
+ z: C,
19
+ k: H,
20
+ kr: J,
21
+ kc: K,
22
+ kv: O
23
+ };
24
+ function P() {
25
+ return /* @__PURE__ */ e.createElement("svg", { className: c.kv, viewBox: "0 0 12 12", "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(
26
+ "path",
27
+ {
28
+ d: "M2.5 6.2 4.8 8.5 9.5 3.5",
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ strokeWidth: "1.8",
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round"
34
+ }
35
+ ));
36
+ }
37
+ function E({
38
+ kind: t,
39
+ selected: l
40
+ }) {
41
+ const a = t === "radio";
42
+ return /* @__PURE__ */ e.createElement(
43
+ "span",
44
+ {
45
+ className: r(c.k, a ? c.kr : c.kc),
46
+ "data-on": l ? "true" : void 0,
47
+ "aria-hidden": !0
48
+ },
49
+ !a && l ? /* @__PURE__ */ e.createElement(P, null) : null
50
+ );
51
+ }
52
+ function i({
53
+ title: t,
54
+ meta: l,
55
+ price: a,
56
+ badge: s
57
+ }) {
58
+ return t == null && l == null && a == null && s == null ? null : /* @__PURE__ */ e.createElement("div", { className: c.t }, a != null ? /* @__PURE__ */ e.createElement("span", { className: c.z }, a) : null, t ?? null, l != null ? /* @__PURE__ */ e.createElement("span", { className: c.e }, l) : null, s ?? null);
59
+ }
60
+ function f({ children: t }) {
61
+ return /* @__PURE__ */ e.createElement(
62
+ "div",
63
+ {
64
+ className: c.x,
65
+ onClick: (l) => l.stopPropagation(),
66
+ onMouseDown: (l) => l.preventDefault()
67
+ },
68
+ t
69
+ );
70
+ }
71
+ function Y({
72
+ items: t,
73
+ value: l,
74
+ defaultValue: a,
75
+ name: s,
76
+ disabled: m = !1,
77
+ indicator: p = "check",
78
+ indicatorSide: q = "end",
79
+ onValueChange: u,
80
+ className: N,
81
+ ...h
82
+ }) {
83
+ const b = e.useId(), x = s ?? b, [y, g] = e.useState(a), k = l !== void 0, z = k ? l : y, R = (n) => {
84
+ k || g(n), u == null || u(n);
85
+ };
86
+ return /* @__PURE__ */ e.createElement(
87
+ "div",
88
+ {
89
+ className: r(c.r, N),
90
+ role: "radiogroup",
91
+ "aria-disabled": m || void 0,
92
+ ...h
93
+ },
94
+ t.map((n) => {
95
+ const o = z === n.value, D = m || !!n.disabled, d = n.variant === "split", v = n.iconFramed !== !1;
96
+ return /* @__PURE__ */ e.createElement(
97
+ "label",
98
+ {
99
+ key: n.value,
100
+ className: r(c.i, o && c.s, d && c.p),
101
+ "data-selected": o ? "true" : void 0,
102
+ "data-indicator": d ? void 0 : q
103
+ },
104
+ /* @__PURE__ */ e.createElement(
105
+ "input",
106
+ {
107
+ className: c.n,
108
+ type: "radio",
109
+ name: x,
110
+ value: n.value,
111
+ checked: o,
112
+ disabled: D,
113
+ onChange: () => R(n.value)
114
+ }
115
+ ),
116
+ d ? /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("div", { className: c.h }, n.icon != null ? /* @__PURE__ */ e.createElement("div", { className: r(c.m, v && c.mf) }, n.icon) : null, /* @__PURE__ */ e.createElement(i, { title: n.title }), /* @__PURE__ */ e.createElement(E, { kind: p, selected: o })), /* @__PURE__ */ e.createElement("div", { className: c.y }, /* @__PURE__ */ e.createElement(
117
+ i,
118
+ {
119
+ price: n.price,
120
+ meta: n.meta,
121
+ badge: n.badge
122
+ }
123
+ ), n.description != null ? /* @__PURE__ */ e.createElement("p", { className: c.d }, n.description) : null, n.actions != null ? /* @__PURE__ */ e.createElement(f, null, n.actions) : null)) : /* @__PURE__ */ e.createElement(e.Fragment, null, n.icon != null ? /* @__PURE__ */ e.createElement("div", { className: r(c.m, v && c.mf) }, n.icon) : null, /* @__PURE__ */ e.createElement("div", { className: c.c }, /* @__PURE__ */ e.createElement(
124
+ i,
125
+ {
126
+ title: n.title,
127
+ meta: n.meta,
128
+ price: n.price,
129
+ badge: n.badge
130
+ }
131
+ ), n.description != null ? /* @__PURE__ */ e.createElement("p", { className: c.d }, n.description) : null, n.actions != null ? /* @__PURE__ */ e.createElement(f, null, n.actions) : null), /* @__PURE__ */ e.createElement(E, { kind: p, selected: o }))
132
+ );
133
+ })
134
+ );
135
+ }
136
+ export {
137
+ Y as R
138
+ };
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.cqr{display:flex;flex-direction:column;gap:var(--shell-gap)}.cqi{display:flex;align-items:flex-start;gap:var(--shell-gap);width:100%;margin:0;padding:var(--shell-padding);box-sizing:border-box;text-align:left;font:inherit;color:var(--shell-color);background:var(--shell-background);border:1px solid var(--shell-border);border-radius:var(--shell-radius);cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}.cqi:hover:not(:disabled){background:var(--shell-background-hover)}.cqi:disabled{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.cqs,.cqi[data-selected=true]{border-color:var(--shell-color-accent);background:var(--shell-background-selected)}.cqp{flex-direction:column;align-items:stretch;padding:0;gap:0}.cqp.cqs .cqh,.cqp[data-selected=true] .cqh{border-bottom-color:var(--shell-color-accent)}.cqn{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.cqm{display:flex;flex-shrink:0;align-items:center;justify-content:center;overflow:hidden}.cqmf{width:var(--shell-icon-size);height:var(--shell-icon-size);padding:var(--shell-gap-sm);box-sizing:content-box;color:var(--shell-color-muted);background:var(--shell-background-muted);border:1px solid var(--shell-border);border-radius:var(--shell-radius)}.cqc{display:flex;flex:1;flex-direction:column;gap:var(--shell-gap-sm);min-width:0}.cqt{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--shell-gap-sm);font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);color:var(--shell-color);line-height:1.3}.cqe{font-size:var(--shell-font-size-sm);font-weight:400;color:var(--shell-color-muted)}.cqd{margin:0;font-size:var(--shell-font-size-sm);color:var(--shell-color-muted);line-height:1.45}.cqx{display:flex;flex-wrap:wrap;gap:var(--shell-gap-sm);margin-top:var(--shell-gap-sm)}.cqh{display:flex;align-items:center;gap:var(--shell-gap);padding:var(--shell-padding);border-bottom:1px solid var(--shell-border)}.cqh .cqt{flex:1;min-width:0}.cqy{display:flex;flex-direction:column;gap:var(--shell-gap-sm);padding:var(--shell-padding)}.cqz{font-size:var(--shell-font-size-lg);font-weight:700;color:var(--shell-color);line-height:1.2}.cqk{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:var(--shell-check-size);height:var(--shell-check-size);margin-left:auto;box-sizing:border-box;border:1px solid var(--shell-border);background:var(--shell-background);color:#fff}.cqi[data-indicator=start]>.cqk{margin-left:0;order:-1}.cqkr{border-radius:50%}.cqkr[data-on=true]{border-color:var(--shell-color-accent);background:var(--shell-color-accent);box-shadow:inset 0 0 0 3px var(--shell-background-selected)}.cqkc{border-radius:calc(var(--shell-radius) * .4)}.cqkc[data-on=true]{border-color:var(--shell-color-accent);background:var(--shell-color-accent)}.cqkv{width:60%;height:60%;display:block}}
@@ -0,0 +1,111 @@
1
+ import { default as React } from 'react';
2
+ /** Вид индикатора выбора: квадрат с галочкой или круг. */
3
+ export type RadioIndicator = "check" | "radio";
4
+ /** Сторона индикатора в row-варианте. */
5
+ export type RadioIndicatorSide = "start" | "end";
6
+ /**
7
+ * Опция группы выбора {@link RadioGroup}.
8
+ *
9
+ * Идентификатор — `value` (не `key`).
10
+ * Опциональные поля (`icon`, `title`, `description`, …): есть → рисуем, нет → пропускаем.
11
+ * Внешний вид (индикатор, split) — props item/группы, не отдельные compound-слоты.
12
+ */
13
+ export type RadioGroupItem = {
14
+ /** Значение опции (то, что уходит в value / onValueChange). Не `key`. */
15
+ value: string;
16
+ /** Заголовок опции. */
17
+ title?: React.ReactNode;
18
+ /** Описание под заголовком. */
19
+ description?: React.ReactNode;
20
+ /** Иконка / логотип / Avatar — рисуется только если передано. */
21
+ icon?: React.ReactNode;
22
+ /**
23
+ * Рамка вокруг icon (квадрат под иконку/лого).
24
+ * Для Avatar — `false`. По умолчанию `true`.
25
+ */
26
+ iconFramed?: boolean;
27
+ /** Вторичный текст: @handle, $10/month, per month. */
28
+ meta?: React.ReactNode;
29
+ /** Крупная цена (часто с `variant="split"`). */
30
+ price?: React.ReactNode;
31
+ /** Метка (Badge и т.п.). */
32
+ badge?: React.ReactNode;
33
+ /** Edit / Set as default — клик не меняет выбор. */
34
+ actions?: React.ReactNode;
35
+ /** Недоступная опция. */
36
+ disabled?: boolean;
37
+ /**
38
+ * `row` — одна строка (планы, люди, оплата).
39
+ * `split` — шапка (icon + title) / тело (price + meta + badge + description).
40
+ */
41
+ variant?: "row" | "split";
42
+ };
43
+ /**
44
+ * Props компонента {@link RadioGroup}.
45
+ *
46
+ * Групповой UI: `items` + `value` / `onValueChange`, без `RadioGroup.Item`.
47
+ */
48
+ export type RadioGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> & {
49
+ /** Декларативный список опций. */
50
+ items: RadioGroupItem[];
51
+ /** Выбранное значение (controlled). */
52
+ value?: string;
53
+ /** Начальное значение (uncontrolled). */
54
+ defaultValue?: string;
55
+ /** `name` для скрытых `input[type=radio]`. */
56
+ name?: string;
57
+ /** Заблокировать всю группу. */
58
+ disabled?: boolean;
59
+ /** `check` — квадрат с галочкой; `radio` — круг. */
60
+ indicator?: RadioIndicator;
61
+ /** Сторона индикатора в row-варианте. */
62
+ indicatorSide?: RadioIndicatorSide;
63
+ /** Смена выбора (`next` = `item.value`). */
64
+ onValueChange?: (value: string) => void;
65
+ };
66
+ /**
67
+ * Группа выбора одной опции из декларативного массива `items`.
68
+ *
69
+ * Когда использовать:
70
+ * - выбор тарифа, способа оплаты, человека, варианта настроек карточкой;
71
+ * - нужен check/radio-индикатор и опциональные icon / meta / actions.
72
+ *
73
+ * Связанные компоненты:
74
+ * - {@link PricingGroup} — тарифные карточки без radio-индикатора;
75
+ * - {@link AvatarName} — часто как `icon` / контент опции «человек».
76
+ *
77
+ * API: только `items` + value/onValueChange. Compound-слоты (`RadioGroup.Item`) не используются.
78
+ * Идентификатор опции — `value` (не `key`).
79
+ *
80
+ * CSS-токены: `--shell-*`.
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * const items = [
85
+ * {
86
+ * value: "basic",
87
+ * icon: <Icons.Line name="zap" />,
88
+ * title: "Basic",
89
+ * description: "Для старта",
90
+ * meta: "$9/мес",
91
+ * },
92
+ * {
93
+ * value: "pro",
94
+ * title: "Pro",
95
+ * description: "Для команд",
96
+ * price: "$29",
97
+ * badge: <Badge>Popular</Badge>,
98
+ * variant: "split",
99
+ * },
100
+ * ];
101
+ *
102
+ * <RadioGroup
103
+ * items={items}
104
+ * value={plan}
105
+ * onValueChange={setPlan}
106
+ * indicator="check"
107
+ * indicatorSide="end"
108
+ * />
109
+ * ```
110
+ */
111
+ export declare function RadioGroup({ items, value: valueProp, defaultValue, name: nameProp, disabled, indicator, indicatorSide, onValueChange, className, ...rest }: RadioGroupProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { R as a } from "./RadioGroup-DxoPHPIF.js";
2
+ export {
3
+ a as RadioGroup
4
+ };
@@ -0,0 +1,8 @@
1
+ import t from "react";
2
+ import { S as o } from "./Stack-b0-XWwQ1.js";
3
+ function c(r) {
4
+ return /* @__PURE__ */ t.createElement(o, { direction: "row", ...r });
5
+ }
6
+ export {
7
+ c as R
8
+ };
package/dist/Row.d.ts CHANGED
@@ -1,8 +1,24 @@
1
1
  import { default as React } from 'react';
2
- export type RowProps = React.HTMLAttributes<HTMLDivElement> & {
3
- /** Отступ между детьми: число (px) или CSS-строка */
4
- gap?: number | string;
5
- children?: React.ReactNode;
6
- };
7
- /** Горизонтальный ряд. Токены: --row-* */
8
- export declare function Row({ gap, children, className, style, ...rest }: RowProps): React.JSX.Element;
2
+ import { StackProps } from './Stack';
3
+ /**
4
+ * @deprecated Используйте `Stack direction="row"`.
5
+ * Миграция: `elcrm migrate components`.
6
+ *
7
+ * Горизонтальный стек (алиас Stack).
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * // Было:
12
+ * <Row gap={8}>…</Row>
13
+ * // Стало:
14
+ * <Stack direction="row" gap={8}>…</Stack>
15
+ * ```
16
+ */
17
+ export type RowProps = Omit<StackProps, "direction">;
18
+ /**
19
+ * @deprecated Используйте `Stack direction="row"`.
20
+ * Миграция: `elcrm migrate components`.
21
+ *
22
+ * @see Stack, Block, Column
23
+ */
24
+ export declare function Row(props: RowProps): React.JSX.Element;
package/dist/Row.js CHANGED
@@ -1,4 +1,4 @@
1
- import { R } from "./Row-CnD7Q7pD.js";
1
+ import { R } from "./Row--HIe6vb9.js";
2
2
  export {
3
3
  R as Row
4
4
  };
package/dist/Section.css CHANGED
@@ -1 +1 @@
1
- @layer elcrm-components{.cep{display:flex;flex-direction:column;gap:var(--section-gap);padding:var(--section-padding);flex:1;min-width:0}.ceu{box-sizing:border-box;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:var(--section-auth-padding)}.cek{box-sizing:border-box;width:100%;max-width:var(--section-auth-card-max-width);padding:var(--section-auth-card-padding);background:var(--section-auth-card-background);border:1px solid var(--section-auth-card-border);border-radius:var(--section-auth-card-radius);box-shadow:var(--section-auth-card-shadow);display:flex;flex-direction:column;gap:var(--section-gap)}.ceh{display:flex;flex-direction:column;gap:.35rem}.cec{text-align:center;align-items:center}.cei{display:flex;margin-bottom:var(--section-icon-gap)}.cec .cei{justify-content:center}.cet{margin:0;font-size:var(--section-title-size);font-weight:600;letter-spacing:-.02em;color:var(--section-title-color);line-height:1.25}.ced{font-size:var(--section-title-size-md)}.cee{margin:0;font-size:var(--section-description-size);color:var(--section-description-color);line-height:1.45}.ceb{display:flex;flex-direction:column;gap:var(--section-body-gap);width:100%;text-align:left}.ces{display:flex;flex-direction:column;gap:var(--section-actions-gap);width:100%}.cel{display:flex;flex-direction:column;gap:var(--section-gap);margin-bottom:var(--section-block-margin-bottom);padding-bottom:var(--section-block-padding-bottom);border-bottom:1px solid var(--section-block-border)}.cel:last-of-type{margin-bottom:0;padding-bottom:0;border-bottom:none}.cel .cet{font-size:.95rem;font-weight:600}}
1
+ @layer elcrm-components{.cep{display:flex;flex-direction:column;gap:var(--shell-gap);padding:var(--section-padding);flex:1;min-width:0}.ceu{box-sizing:border-box;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:var(--section-padding)}.cek{box-sizing:border-box;width:100%;max-width:var(--section-auth-card-max-width);padding:var(--shell-gap-lg);background:var(--shell-background);border:1px solid var(--shell-border);border-radius:var(--shell-radius);box-shadow:var(--shell-shadow);display:flex;flex-direction:column;gap:var(--shell-gap)}.ceh{display:flex;flex-direction:column;gap:var(--shell-gap-sm)}.cec{text-align:center;align-items:center}.cei{display:flex;margin-bottom:var(--shell-gap-sm)}.cec .cei{justify-content:center}.cet{margin:0;font-size:var(--section-title-size);font-weight:var(--shell-font-weight);letter-spacing:-.02em;color:var(--shell-color);line-height:1.25}.ced{font-size:var(--section-title-size-md)}.cee{margin:0;font-size:var(--shell-font-size);color:var(--shell-color-muted);line-height:1.45}.ceb{display:flex;flex-direction:column;gap:var(--shell-gap);width:100%;text-align:left}.ces{display:flex;flex-direction:column;gap:var(--shell-gap-sm);width:100%}.cel{display:flex;flex-direction:column;gap:var(--shell-gap);margin-bottom:var(--shell-gap-lg);padding-bottom:var(--shell-gap-lg);border-bottom:1px solid var(--shell-border)}.cel:last-of-type{margin-bottom:0;padding-bottom:0;border-bottom:none}.cel .cet{font-size:var(--shell-font-size);font-weight:var(--shell-font-weight)}}
package/dist/Section.d.ts CHANGED
@@ -1,11 +1,28 @@
1
1
  import { default as React } from 'react';
2
+ /**
3
+ * Единая секция страницы: заголовок, описание, иконка, контент, действия.
4
+ *
5
+ * Роли: Section = блок модуля / панель / auth-карточка;
6
+ * для строки «title + actions» на странице — PageHead;
7
+ * каркас со скроллом — PageShell.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <Section title="Настройки" description="Профиль и безопасность" variant="page">
12
+ * <Stack gap={16}>…</Stack>
13
+ * </Section>
14
+ * ```
15
+ *
16
+ * Токены: `--shell-*`, узкие `--section-padding`, `--section-title-size`, `--section-title-size-md`, `--section-auth-card-max-width`.
17
+ */
2
18
  export type SectionProps = {
3
- /** Заголовок */
19
+ /** Заголовок секции */
4
20
  title?: React.ReactNode;
5
21
  /** Подзаголовок / описание */
6
22
  description?: React.ReactNode;
7
23
  /** Иконка / логотип над или рядом с заголовком */
8
24
  icon?: React.ReactNode;
25
+ /** Основной контент секции */
9
26
  children?: React.ReactNode;
10
27
  /** Блок действий (кнопки под контентом или в шапке) */
11
28
  actions?: React.ReactNode;
@@ -15,15 +32,18 @@ export type SectionProps = {
15
32
  * `auth` — центрированная карточка входа.
16
33
  */
17
34
  variant?: "page" | "block" | "auth";
18
- /** Выравнивание шапки */
35
+ /** Выравнивание шапки (`start` по умолчанию, для `auth` — `center`) */
19
36
  align?: "start" | "center";
20
37
  /** Размер заголовка */
21
38
  titleSize?: "md" | "lg";
39
+ /** Доп. CSS-класс */
22
40
  className?: string;
41
+ /** HTML-тег корня (`main` / `section` / `div`) */
23
42
  as?: "section" | "main" | "div";
24
43
  };
25
44
  /**
26
45
  * Единая секция страницы: title + description + icon + children.
27
- * Токены: --section-*
46
+ *
47
+ * @see PageHead, PageShell, Stack, EmptyState
28
48
  */
29
49
  export declare function Section({ title, description, icon, children, actions, variant, align, titleSize, className, as, }: SectionProps): React.JSX.Element;
@@ -0,0 +1,31 @@
1
+ import m from "react";
2
+ import { c as u } from "./cx-CcykAxZN.js";
3
+ import './Stack.css';function a(t) {
4
+ if (t != null)
5
+ return { gap: typeof t == "number" ? `${t}px` : t };
6
+ }
7
+ const i = "c1r", l = "c1h", r = {
8
+ r: i,
9
+ h: l
10
+ };
11
+ function p({
12
+ direction: t = "column",
13
+ gap: c,
14
+ children: e,
15
+ className: n,
16
+ style: o,
17
+ ...s
18
+ }) {
19
+ return /* @__PURE__ */ m.createElement(
20
+ "div",
21
+ {
22
+ className: u(r.r, t === "row" && r.h, n),
23
+ style: { ...a(c), ...o },
24
+ ...s
25
+ },
26
+ e
27
+ );
28
+ }
29
+ export {
30
+ p as S
31
+ };
package/dist/Stack.css ADDED
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.c1r{display:flex;flex-direction:column;gap:var(--shell-gap)}.c1h{flex-direction:row;flex-wrap:wrap;align-items:center}}
@@ -0,0 +1,34 @@
1
+ import { default as React } from 'react';
2
+ /** Направление раскладки стека */
3
+ export type StackDirection = "row" | "column";
4
+ /**
5
+ * Универсальный стек (ряд / колонка) с отступами между детьми.
6
+ *
7
+ * Роли: Stack = базовая раскладка; заменяет устаревшие Block / Row / Column.
8
+ * Внутри Section, Card, форм и списков действий.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <Stack direction="row" gap={12}>
13
+ * <Button>Отмена</Button>
14
+ * <Button>Сохранить</Button>
15
+ * </Stack>
16
+ * ```
17
+ *
18
+ * Токены: `--shell-gap` (если `gap` не задан); при `gap` — inline-стиль.
19
+ */
20
+ export type StackProps = React.HTMLAttributes<HTMLDivElement> & {
21
+ /** Направление: по умолчанию `column` */
22
+ direction?: StackDirection;
23
+ /** Отступ между детьми: число (px) или CSS-строка */
24
+ gap?: number | string;
25
+ /** Дочерние элементы стека */
26
+ children?: React.ReactNode;
27
+ };
28
+ /**
29
+ * Универсальный стек (ряд / колонка).
30
+ * Вместо устаревших Block / Row / Column.
31
+ *
32
+ * @see Block, Row, Column
33
+ */
34
+ export declare function Stack({ direction, gap, children, className, style, ...rest }: StackProps): React.JSX.Element;
package/dist/Stack.js ADDED
@@ -0,0 +1,4 @@
1
+ import { S as r } from "./Stack-b0-XWwQ1.js";
2
+ export {
3
+ r as Stack
4
+ };
@@ -0,0 +1,50 @@
1
+ import t, { useMemo as S, useState as y, Suspense as T } from "react";
2
+ import { c as b } from "./cx-CcykAxZN.js";
3
+ import './TabSections.css';const x = "ctr", I = "ctb", M = "ctn", R = "ctt", $ = "cta", g = "cte", j = "cts", q = "ctm", a = {
4
+ r: x,
5
+ b: I,
6
+ n: M,
7
+ t: R,
8
+ a: $,
9
+ e: g,
10
+ s: j,
11
+ m: q
12
+ };
13
+ function A({
14
+ items: l,
15
+ resolveSection: d,
16
+ value: o,
17
+ defaultValue: n,
18
+ onValueChange: s,
19
+ loading: v = null,
20
+ actions: m,
21
+ className: i,
22
+ navLabel: E = "Вкладки"
23
+ }) {
24
+ const p = S(() => {
25
+ var e;
26
+ return n || (((e = l.find((c) => !c.disabled)) == null ? void 0 : e.value) ?? "");
27
+ }, [n, l]), [N, f] = y(p), r = o ?? N, k = (e) => {
28
+ o == null && f(e), s == null || s(e);
29
+ }, u = r ? d(r) : null;
30
+ return /* @__PURE__ */ t.createElement("div", { className: b(a.r, i) }, /* @__PURE__ */ t.createElement("div", { className: a.b }, /* @__PURE__ */ t.createElement("nav", { className: a.n, "aria-label": E }, l.map((e) => {
31
+ const c = e.value === r;
32
+ return /* @__PURE__ */ t.createElement(
33
+ "button",
34
+ {
35
+ key: e.value,
36
+ type: "button",
37
+ className: b(a.t, c && a.a),
38
+ "data-active": c ? "true" : void 0,
39
+ "aria-current": c ? "page" : void 0,
40
+ disabled: e.disabled,
41
+ onClick: () => k(e.value)
42
+ },
43
+ /* @__PURE__ */ t.createElement("span", null, e.label),
44
+ e.badge != null && e.badge !== "" ? /* @__PURE__ */ t.createElement("em", { className: a.e }, e.badge) : null
45
+ );
46
+ })), m != null ? /* @__PURE__ */ t.createElement("div", { className: a.s }, m) : null), /* @__PURE__ */ t.createElement("div", { className: a.m }, u ? /* @__PURE__ */ t.createElement(T, { fallback: v }, t.createElement(u)) : null));
47
+ }
48
+ export {
49
+ A as T
50
+ };
@@ -1 +1 @@
1
- @layer elcrm-components{.ctr{display:flex;flex-direction:column;gap:var(--tab-sections-gap);width:100%;min-width:0}.ctb{display:flex;align-items:flex-end;gap:1rem;border-bottom:1px solid var(--tab-sections-border);min-width:0}.ctn{display:flex;align-items:stretch;gap:var(--tab-sections-tab-gap);flex:1 1 auto;min-width:0;overflow-x:auto;scrollbar-width:thin}.ctt{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:inline-flex;align-items:center;gap:.4rem;flex:0 0 auto;margin:0 0 -1px;padding:var(--tab-sections-tab-padding);border:none;border-bottom:2px solid transparent;background:transparent;color:var(--tab-sections-color);font:inherit;font-size:.95rem;font-weight:500;white-space:nowrap;cursor:pointer;transition:color .12s ease,border-color .12s ease}.ctt:hover:not(:disabled){color:var(--tab-sections-color-active)}.cta{color:var(--tab-sections-color-active);border-bottom-color:var(--tab-sections-accent)}.ctt:disabled{opacity:.45;cursor:not-allowed}.cte{display:inline-flex;align-items:center;justify-content:center;min-width:1.15em;height:1.15em;padding:0 .35em;border-radius:999px;background:var(--tab-sections-badge-background);color:var(--tab-sections-badge-color);font-size:var(--tab-sections-badge-size);font-style:normal;font-weight:700;line-height:1}.cts{flex:0 0 auto;display:flex;align-items:center;padding-bottom:.35rem}.ctm{min-width:0;width:100%}}
1
+ @layer elcrm-components{.ctr{display:flex;flex-direction:column;gap:var(--shell-gap);width:100%;min-width:0}.ctb{display:flex;align-items:flex-end;gap:var(--shell-gap);border-bottom:1px solid var(--shell-border);min-width:0}.ctn{display:flex;align-items:stretch;gap:var(--shell-gap-lg);flex:1 1 auto;min-width:0;overflow-x:auto;scrollbar-width:thin}.ctt{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:inline-flex;align-items:center;gap:var(--shell-gap-sm);flex:0 0 auto;margin:0 0 -1px;padding:var(--shell-padding-block) .15rem;border:none;border-bottom:2px solid transparent;background:transparent;color:var(--shell-color-muted);font:inherit;font-size:var(--shell-font-size);font-weight:500;white-space:nowrap;cursor:pointer}.ctt:hover:not(:disabled){color:var(--shell-color)}.cta{color:var(--shell-color);border-bottom-color:var(--shell-color-accent)}.ctt:disabled{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.cte{display:inline-flex;align-items:center;justify-content:center;min-width:1.15em;height:1.15em;padding:0 .35em;border-radius:999px;background:var(--shell-color-danger);color:#fff;font-size:11px;font-style:normal;font-weight:700;line-height:1}.cts{flex:0 0 auto;display:flex;align-items:center;padding-bottom:var(--shell-gap-sm)}.ctm{min-width:0;width:100%}}
@@ -1,31 +1,87 @@
1
1
  import { default as React } from 'react';
2
2
  import { LazyRouteChunk } from './lazyRoute';
3
+ /**
4
+ * Пункт горизонтальных вкладок {@link TabSections}.
5
+ *
6
+ * Идентификатор — `value` (часто имя файла секции). Не путать с React `key`.
7
+ */
3
8
  export type TabSectionsItem = {
4
- key: string;
9
+ /** Уникальный идентификатор вкладки (не `key`). */
10
+ value: string;
11
+ /** Подпись вкладки. */
5
12
  label: React.ReactNode;
6
- /** Бейдж справа от подписи (число / текст) */
13
+ /** Бейдж справа от подписи (число / текст). Нет — не рисуем. */
7
14
  badge?: React.ReactNode;
15
+ /** Недоступная вкладка. */
8
16
  disabled?: boolean;
9
17
  };
18
+ /**
19
+ * Props компонента {@link TabSections}.
20
+ *
21
+ * Групповой UI: декларативный массив `items` + `value` / `onValueChange`.
22
+ */
10
23
  export type TabSectionsProps = {
11
- /** Горизонтальные вкладки (ключ = файл секции) */
24
+ /**
25
+ * Горизонтальные вкладки.
26
+ * `value` пункта обычно = имя файла секции для `resolveSection`.
27
+ */
12
28
  items: TabSectionsItem[];
13
29
  /**
14
- * Резолвер: обычно `createLazyResolver((k) => import(\`./sections/${k}.tsx\`))`.
30
+ * Резолвер панели по `value`.
31
+ * Обычно: `createLazyResolver((v) => import(\`./sections/${v}.tsx\`))`.
15
32
  */
16
- resolveSection: (key: string) => LazyRouteChunk | React.ComponentType;
17
- activeKey?: string;
18
- defaultActiveKey?: string;
19
- onChange?: (key: string) => void;
33
+ resolveSection: (value: string) => LazyRouteChunk | React.ComponentType;
34
+ /** Контролируемый активный `value`. */
35
+ value?: string;
36
+ /** Начальный `value` (неконтролируемый режим). */
37
+ defaultValue?: string;
38
+ /** Смена вкладки (`next` = `item.value`). */
39
+ onValueChange?: (value: string) => void;
40
+ /** Fallback для `<Suspense>`. */
20
41
  loading?: React.ReactNode;
21
- /** Справа в ряду вкладок (фильтр, сортировка…) */
42
+ /** Справа в ряду вкладок (фильтр, сортировка…). */
22
43
  actions?: React.ReactNode;
44
+ /** CSS-класс корня. */
23
45
  className?: string;
46
+ /** `aria-label` для `<nav>` вкладок. */
24
47
  navLabel?: string;
25
48
  };
26
49
  /**
27
- * Как NavSections, но вкладки горизонтально. Без своего заголовка —
28
- * оборачивайте в Section.
29
- * Токены: --tab-sections-*
50
+ * Как {@link NavSections}, но вкладки горизонтально; без собственного заголовка —
51
+ * обычно оборачивают в `Section` / `PageHead`.
52
+ *
53
+ * Когда использовать:
54
+ * - несколько панелей на одной странице с горизонтальным переключением;
55
+ * - контент грузится lazy по `value`.
56
+ *
57
+ * Связанные компоненты:
58
+ * - {@link NavSections} — сайдбар + секции;
59
+ * - {@link createLazyResolver} — кэш lazy-импортов;
60
+ * - `Section` — обёртка с заголовком страницы.
61
+ *
62
+ * API: декларативные `items`. Идентификатор — `value` (не `key`).
63
+ * Compound-слоты не используются.
64
+ *
65
+ * CSS-токены: `--shell-*`.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * const items = [
70
+ * { value: "members", label: "Участники", badge: 12 },
71
+ * { value: "invites", label: "Приглашения" },
72
+ * ];
73
+ *
74
+ * const resolveSection = createLazyResolver(
75
+ * (v) => import(`./sections/${v}.tsx`),
76
+ * );
77
+ *
78
+ * <TabSections
79
+ * items={items}
80
+ * resolveSection={resolveSection}
81
+ * value={tab}
82
+ * onValueChange={setTab}
83
+ * actions={<Button size="s">Фильтр</Button>}
84
+ * />
85
+ * ```
30
86
  */
31
- export declare function TabSections({ items, resolveSection, activeKey: activeKeyProp, defaultActiveKey, onChange, loading, actions, className, navLabel, }: TabSectionsProps): React.JSX.Element;
87
+ export declare function TabSections({ items, resolveSection, value: valueProp, defaultValue, onValueChange, loading, actions, className, navLabel, }: TabSectionsProps): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- import { T as e } from "./TabSections-oeEBx0nM.js";
1
+ import { T as e } from "./TabSections-DL7DufKU.js";
2
2
  export {
3
3
  e as TabSections
4
4
  };
package/dist/cx.d.ts CHANGED
@@ -1,2 +1,9 @@
1
- /** Склейка className без лишних пробелов */
1
+ /**
2
+ * Склейка CSS className без лишних пробелов и falsy-значений.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * cx(s.r, active && s.a, className) // → "cr c a my"
7
+ * ```
8
+ */
2
9
  export declare function cx(...parts: Array<string | false | null | undefined>): string;
package/dist/dark.css ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Цвета shell (@elcrm/components) для тёмной темы — `elcrm css`.
3
+ */
4
+ html[data-theme="dark"],
5
+ body[data-theme="dark"] {
6
+ --shell-color: var(--text);
7
+ --shell-color-muted: var(--muted);
8
+ --shell-color-accent: var(--accent);
9
+ --shell-color-danger: var(--danger);
10
+ --shell-color-success: var(--success);
11
+ --shell-color-warning: var(--warning);
12
+ --shell-border: var(--line);
13
+ --shell-background: var(--panel);
14
+ --shell-background-muted: var(--bg);
15
+ --shell-background-hover: color-mix(in srgb, var(--text) 8%, transparent);
16
+ --shell-background-selected: color-mix(in srgb, var(--accent) 14%, transparent);
17
+ --shell-shadow: var(--shadow);
18
+
19
+ --avatar-background: var(--shell-color-accent);
20
+ --avatar-color: var(--button-primary-fg);
21
+ --avatar-border: 1px solid var(--shell-border);
22
+ --avatar-outline-active: 2px solid
23
+ color-mix(in srgb, var(--accent) 70%, white);
24
+ }