@elcrm/components 0.2.9 → 0.2.13

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
@@ -49,6 +49,11 @@ import {
49
49
  RadioGroup,
50
50
  PricingGroup,
51
51
  PricingTable,
52
+ Table,
53
+ CodeBlock,
54
+ SplitPane,
55
+ Banner,
56
+ IconButton,
52
57
  Dropdown,
53
58
  Breadcrumb,
54
59
  PageHead,
@@ -88,18 +93,23 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
88
93
  | **RadioGroup** | Выбор одной опции-карточки | `items`, `value`, `indicator`, `indicatorSide` |
89
94
  | **PricingGroup** | Тарифные карточки в ряд | `items` (`PricingPlan`), `selectable`, `value` |
90
95
  | **PricingTable** | Сравнение тарифов (таблица) | те же `items` + `sections` |
96
+ | **Table** | Таблица данных со скроллом | `layout` (`auto`/`fixed`), стандартные props `<table>` |
97
+ | **CodeBlock** | Блок кода с bar и копированием | `title`, `code` / `children`, `onCopy`, `actions` |
98
+ | **SplitPane** | Две панели с разделителем | `direction`, `ratio`, `onRatioChange`, `first`, `second`, `minFirst` / `minSecond` |
99
+ | **Banner** | Статусная полоса | `tone` (`info`/`warning`/`danger`/`success`), не toast |
100
+ | **IconButton** | Иконка + tooltip | `name`, обязательный `label`, `tooltip` (`false` — без подсказки) |
91
101
  | **Menu** | Навигация в шапке (не popover) | `items` (`value`, `badge`: `0` скрыт), `value`, `onSelect`, `variant` (`plain`/`line`) |
92
102
  | **Dropdown** | Выпадающее меню (items, portal) | `label`/`trigger`, `items`/`groups`, `onSelect` |
93
103
  | **Popover** | Свой попап в portal | `trigger`, `children`, `align`, `open` |
94
104
  | **Breadcrumb** | Хлебные крошки | `items`, `separator`, `collapseAfter` |
95
- | **Sidebar** | Левое меню приложения | `groups`, `value`, `footer` + `footerItems`, `variant` |
105
+ | **Sidebar** | Левое меню приложения | `groups`, `value`, `item.items` + `defaultOpen`, `open` / `onOpenChange`, `footerItems` |
96
106
  | **PageShell** | Каркас модуля (скролл + footer) | `wide`, `scroll`, `footer`, `children` |
97
107
  | **ChatList** | Колонка диалогов | `header`, `footer`, `children` (`List`/`Item`), `bodyRef` |
98
108
  | **ChatMessages** | Колонка переписки | `header`, `footer`, `children`, `bodyRef`, `onBodyScroll` |
99
109
  | **ChatSplit** | Список + лента | `list`, `messages` |
100
110
  | **PageHead** | Заголовок страницы + actions | `title`, `sup` (текст/Badge), `subtitle`, `actions`, `sticky`, `children` |
101
111
  | **Section** | Блок контента / auth / подраздел | `title`, `description`, `icon`, `actions`, `variant` |
102
- | **NavSections** | Сайдбар + lazy-панель | `groups` (`value`), `resolveSection`, `defaultValue` |
112
+ | **NavSections** | Сайдбар + lazy-панель | `groups`, `item.items` + `defaultOpen`, `open` / `onOpenChange`, `resolveSection` |
103
113
  | **TabSections** | Горизонтальные вкладки + lazy | `items` (`value` + `badge`), `resolveSection` |
104
114
  | **Card** | Карточка | `title`, `--card-pointer` |
105
115
  | **ActionGroup** | Ряд иконок в карточке | `label`, `items` и/или `children` |
@@ -136,7 +146,17 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
136
146
  {
137
147
  value: "admin",
138
148
  title: "Админ",
139
- items: [{ value: "ops", label: "Операции" }],
149
+ items: [
150
+ {
151
+ value: "bridge",
152
+ label: "VK Bridge",
153
+ defaultOpen: true,
154
+ items: [
155
+ { value: "intro", label: "Введение" },
156
+ { value: "events", label: "События" },
157
+ ],
158
+ },
159
+ ],
140
160
  },
141
161
  ]}
142
162
  footer={<AvatarName title="Паша" description="mail@…" />}
@@ -150,7 +170,7 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
150
170
  />
151
171
  ```
152
172
 
153
- Токены: `--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` в теме приложения.
173
+ Токены: `--sidebar-width`, `--sidebar-item-font-size`, `--sidebar-item-padding`, `--sidebar-icon-size`, `--sidebar-group-font-size`, `--sidebar-indent`, `--sidebar-item-active`. Для orange active в `plain` переопределите `--sidebar-item-active` в теме приложения.
154
174
 
155
175
  ### Stack
156
176
 
@@ -656,6 +676,15 @@ const resolve = createLazyResolver(
656
676
  label: "Личное",
657
677
  items: [
658
678
  { value: "Profile", label: "Профиль" },
679
+ {
680
+ value: "bridge",
681
+ label: "VK Bridge",
682
+ defaultOpen: true,
683
+ items: [
684
+ { value: "Intro", label: "Введение" },
685
+ { value: "Events", label: "События" },
686
+ ],
687
+ },
659
688
  { value: "Appearance", label: "Оформление" },
660
689
  ],
661
690
  },
@@ -741,6 +770,10 @@ const resolve = createLazyResolver(
741
770
  --stack-grid-min
742
771
  --toolbar-gap
743
772
  --healthmap-cell / --healthmap-gap / --healthmap-l0…l4
773
+ --table-font-size / --table-cell-padding / --table-head-font-size / --table-code-font-size
774
+ --code-block-bg / --code-block-padding / --code-block-font-size
775
+ --split-handle-size / --split-handle-bg / --split-handle-bg-hover
776
+ --banner-padding / --banner-bg / --banner-border
744
777
  --panel-info-width / --panel-info-width-min / --panel-info-overlay-max / --panel-info-backdrop
745
778
  --row-gap / --column-gap
746
779
  --popup-shadow
@@ -0,0 +1,34 @@
1
+ import e from "react";
2
+ import { c as a } from "./cx-CcykAxZN.js";
3
+ import './Banner.css';const i = "cLr", d = "cLi", m = "cLw", f = "cLd", w = "cLs", s = {
4
+ r: i,
5
+ i: d,
6
+ w: m,
7
+ d: f,
8
+ s: w
9
+ }, L = {
10
+ info: s.i,
11
+ warning: s.w,
12
+ danger: s.d,
13
+ success: s.s
14
+ };
15
+ function p({
16
+ tone: c = "info",
17
+ className: n,
18
+ role: t = "status",
19
+ children: o,
20
+ ...r
21
+ }) {
22
+ return /* @__PURE__ */ e.createElement(
23
+ "div",
24
+ {
25
+ className: a(s.r, L[c], n),
26
+ role: t,
27
+ ...r
28
+ },
29
+ o
30
+ );
31
+ }
32
+ export {
33
+ p as B
34
+ };
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.cLr{margin:0;padding:var(--banner-padding);border:1px solid var(--banner-border);border-radius:var(--shell-radius);background:var(--banner-bg);font-size:var(--shell-font-size-sm);color:var(--shell-color);line-height:1.45}.cLi{--banner-bg: color-mix(in srgb, var(--shell-color-accent) 8%, var(--shell-bg));--banner-border: color-mix(in srgb, var(--shell-color-accent) 35%, var(--shell-border))}.cLw{--banner-bg: color-mix(in srgb, var(--shell-color-warning) 10%, var(--shell-bg));--banner-border: color-mix(in srgb, var(--shell-color-warning) 40%, var(--shell-border))}.cLd{--banner-bg: color-mix(in srgb, var(--shell-color-danger) 10%, var(--shell-bg));--banner-border: color-mix(in srgb, var(--shell-color-danger) 40%, var(--shell-border))}.cLs{--banner-bg: color-mix(in srgb, var(--shell-color-success) 10%, var(--shell-bg));--banner-border: color-mix(in srgb, var(--shell-color-success) 40%, var(--shell-border))}.cLr code{font-size:.92em}}
@@ -0,0 +1,19 @@
1
+ import { default as React } from 'react';
2
+ export type BannerTone = "info" | "warning" | "danger" | "success";
3
+ export type BannerProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ /** Визуальный акцент */
5
+ tone?: BannerTone;
6
+ };
7
+ /**
8
+ * Статусная полоса (offline, maintenance). Не toast — не путать с Notice.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <Banner tone="info" role="status">
13
+ * Каталог из снимка <code>kit-catalog.json</code>
14
+ * </Banner>
15
+ * ```
16
+ *
17
+ * Токены: `--banner-*`, `--shell-bg`.
18
+ */
19
+ export declare function Banner({ tone, className, role, children, ...rest }: BannerProps): React.JSX.Element;
package/dist/Banner.js ADDED
@@ -0,0 +1,4 @@
1
+ import { B as e } from "./Banner-B72lcEMo.js";
2
+ export {
3
+ e as Banner
4
+ };
@@ -0,0 +1,25 @@
1
+ import e from "react";
2
+ import { c as u } from "./cx-CcykAxZN.js";
3
+ import './CodeBlock.css';const p = "cEr", b = "cEb", d = "cEp", i = "cEa", N = "cEc", c = {
4
+ r: p,
5
+ b,
6
+ p: d,
7
+ a: i,
8
+ c: N
9
+ };
10
+ function k({
11
+ title: l,
12
+ code: n,
13
+ children: s,
14
+ onCopy: t,
15
+ copyLabel: r = "Копировать",
16
+ actions: a,
17
+ className: m,
18
+ ...o
19
+ }) {
20
+ const E = s ?? (n != null ? /* @__PURE__ */ e.createElement("code", null, n) : null);
21
+ return /* @__PURE__ */ e.createElement("div", { className: u(c.r, m), ...o }, l != null || t || a ? /* @__PURE__ */ e.createElement("div", { className: c.b }, /* @__PURE__ */ e.createElement("span", null, l ?? "Код"), /* @__PURE__ */ e.createElement("div", { className: c.a }, a, t ? /* @__PURE__ */ e.createElement("button", { type: "button", className: c.c, onClick: t }, r) : null)) : null, /* @__PURE__ */ e.createElement("pre", { className: c.p }, E));
22
+ }
23
+ export {
24
+ k as C
25
+ };
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.cEr{border:1px solid var(--shell-border);border-radius:var(--shell-radius);background:var(--code-block-bg);overflow:hidden}.cEb{display:flex;justify-content:space-between;align-items:center;gap:var(--shell-gap-sm);padding:var(--code-block-bar-padding);border-bottom:1px solid var(--shell-border);font-size:var(--shell-font-size-sm);color:var(--shell-color-muted)}.cEp{margin:0;padding:var(--code-block-padding);overflow:auto;font-family:var(--code-block-font-family);font-size:var(--code-block-font-size);line-height:var(--code-block-line-height);color:var(--shell-color);white-space:pre}.cEa{display:inline-flex;align-items:center;gap:.35rem;flex-shrink:0}.cEc{border:0;background:transparent;color:var(--shell-color-accent);cursor:pointer;font:inherit;padding:0}.cEc:hover,.cEc:focus-visible{text-decoration:underline}}
@@ -0,0 +1,26 @@
1
+ import { default as React } from 'react';
2
+ export type CodeBlockProps = React.HTMLAttributes<HTMLDivElement> & {
3
+ /** Заголовок в полосе над кодом */
4
+ title?: React.ReactNode;
5
+ /** Текст без подсветки (если нет children) */
6
+ code?: string;
7
+ /** Подсветка / разметка — свой рендер в pre */
8
+ children?: React.ReactNode;
9
+ /** Кнопка «Копировать»; без onCopy не показывается */
10
+ onCopy?: () => void;
11
+ /** Подпись кнопки копирования */
12
+ copyLabel?: string;
13
+ /** Доп. действия справа в bar */
14
+ actions?: React.ReactNode;
15
+ };
16
+ /**
17
+ * Блок кода: bar + pre. Подсветку передаёт приложение через `children`.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <CodeBlock title="Пример" code={snippet} onCopy={() => copy(snippet)} />
22
+ * ```
23
+ *
24
+ * Токены: `--code-block-*`, `--shell-border`.
25
+ */
26
+ export declare function CodeBlock({ title, code, children, onCopy, copyLabel, actions, className, ...rest }: CodeBlockProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { C as r } from "./CodeBlock-CRzVopIv.js";
2
+ export {
3
+ r as CodeBlock
4
+ };
@@ -0,0 +1,32 @@
1
+ import t from "react";
2
+ import { IconButton as f } from "@elcrm/button/IconButton";
3
+ import { Icons as p } from "@elcrm/icons";
4
+ import { Tooltip as u } from "@elcrm/tooltip";
5
+ const I = {
6
+ s: 16,
7
+ m: 18,
8
+ l: 20
9
+ };
10
+ function C({
11
+ name: c,
12
+ label: n,
13
+ tooltip: o,
14
+ iconSize: m,
15
+ size: e = "m",
16
+ tooltipPlacement: i,
17
+ ...s
18
+ }) {
19
+ const a = m ?? I[e], r = /* @__PURE__ */ t.createElement(
20
+ f,
21
+ {
22
+ label: n,
23
+ size: e,
24
+ icon: /* @__PURE__ */ t.createElement(p.Line, { name: c, size: a }),
25
+ ...s
26
+ }
27
+ );
28
+ return o === !1 ? r : /* @__PURE__ */ t.createElement(u, { content: o ?? n, placement: i }, r);
29
+ }
30
+ export {
31
+ C as I
32
+ };
@@ -0,0 +1,27 @@
1
+ import { default as React } from 'react';
2
+ import { TIconButton } from '@elcrm/button/IconButton';
3
+ import { TButtonSize } from '@elcrm/button';
4
+ import { TooltipPlacement } from '@elcrm/tooltip';
5
+ export type IconButtonProps = Omit<TIconButton, "icon" | "label" | "size"> & {
6
+ /** Имя из `icons update` / `Icons.Line` (в приложении — `IconLineName`) */
7
+ name: string;
8
+ /** Подпись: `aria-label` и текст tooltip по умолчанию */
9
+ label: string;
10
+ size?: TButtonSize;
11
+ /** Подсказка; `false` — без Tooltip (например, внутри toolbar с aria-label) */
12
+ tooltip?: React.ReactNode | false;
13
+ /** Размер SVG; по умолчанию от `size` кнопки */
14
+ iconSize?: number;
15
+ /** Сторона tooltip */
16
+ tooltipPlacement?: TooltipPlacement;
17
+ };
18
+ /**
19
+ * Иконка-кнопка: `Icons.Line` + {@link ShellIconButton} + {@link Tooltip}.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <IconButton name="power" label="Выход" onClick={logout} />
24
+ * <IconButton name="copy_01" label="Копировать" tooltip="Скопировать CSS" onClick={copy} />
25
+ * ```
26
+ */
27
+ export declare function IconButton({ name, label, tooltip, iconSize, size, tooltipPlacement, ...rest }: IconButtonProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { I as n } from "./IconButton-DPjpBKJn.js";
2
+ export {
3
+ n as IconButton
4
+ };
@@ -0,0 +1,142 @@
1
+ import e, { useMemo as B, Suspense as D } from "react";
2
+ import { c as K } from "./cx-CcykAxZN.js";
3
+ import { I as F } from "./Item-D4okhsfW.js";
4
+ import { f as I, c as M, a as O, b as P, i as V } from "./navTree-BRyaILN9.js";
5
+ import { h as j } from "./rovingKeys-CGlfTtNQ.js";
6
+ import { u as y } from "./useControllableValue-CWqH1kpM.js";
7
+ import './NavSections.css';const A = "cvr", W = "cvn", $ = "cvg", q = "cvl", z = "cvi", G = "cva", H = "cvc", J = "cvt", Q = "cve", T = "cvs", U = "cvf", X = "cvm", c = {
8
+ r: A,
9
+ n: W,
10
+ g: $,
11
+ l: q,
12
+ i: z,
13
+ a: G,
14
+ c: H,
15
+ t: J,
16
+ e: Q,
17
+ s: T,
18
+ f: U,
19
+ m: X
20
+ };
21
+ function Y({ className: a }) {
22
+ return /* @__PURE__ */ e.createElement("svg", { className: a, viewBox: "0 0 12 12", "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(
23
+ "path",
24
+ {
25
+ d: "M2.5 4.5 6 8l3.5-3.5",
26
+ fill: "none",
27
+ stroke: "currentColor",
28
+ strokeWidth: "1.5",
29
+ strokeLinecap: "round",
30
+ strokeLinejoin: "round"
31
+ }
32
+ ));
33
+ }
34
+ function C({
35
+ items: a,
36
+ value: d,
37
+ open: f,
38
+ onSelect: m,
39
+ onToggle: E
40
+ }) {
41
+ return /* @__PURE__ */ e.createElement(e.Fragment, null, a.map((t) => {
42
+ const v = t.items, s = v != null && v.length > 0, u = s && f.includes(t.value), r = !s && t.value === d;
43
+ return /* @__PURE__ */ e.createElement(e.Fragment, { key: t.value }, /* @__PURE__ */ e.createElement(
44
+ F,
45
+ {
46
+ as: "button",
47
+ active: r,
48
+ className: K(c.i, r && c.a),
49
+ "data-active": r ? "true" : void 0,
50
+ "data-open": u ? "true" : void 0,
51
+ "aria-current": r ? "page" : void 0,
52
+ "aria-expanded": s ? u : void 0,
53
+ disabled: t.disabled,
54
+ onClick: () => {
55
+ s ? E(t.value) : m(t.value);
56
+ }
57
+ },
58
+ t.icon != null ? /* @__PURE__ */ e.createElement("span", { className: c.c, "aria-hidden": !0 }, t.icon) : null,
59
+ /* @__PURE__ */ e.createElement("span", { className: c.t }, t.label),
60
+ s ? /* @__PURE__ */ e.createElement(Y, { className: c.e }) : null
61
+ ), s && u ? /* @__PURE__ */ e.createElement("div", { className: c.s }, /* @__PURE__ */ e.createElement(
62
+ C,
63
+ {
64
+ items: v,
65
+ value: d,
66
+ open: f,
67
+ onSelect: m,
68
+ onToggle: E
69
+ }
70
+ )) : null);
71
+ }));
72
+ }
73
+ function le({
74
+ groups: a,
75
+ resolveSection: d,
76
+ value: f,
77
+ defaultValue: m,
78
+ onValueChange: E,
79
+ open: t,
80
+ defaultOpen: v,
81
+ onOpenChange: s,
82
+ loading: u = null,
83
+ footer: r,
84
+ className: R,
85
+ navLabel: S = "Разделы"
86
+ }) {
87
+ const g = B(
88
+ () => m || I(a),
89
+ [m, a]
90
+ ), [l, b] = y(
91
+ f,
92
+ g,
93
+ E
94
+ ), [o = [], N] = y(
95
+ t,
96
+ M(a, v, l),
97
+ s
98
+ ), x = O(a).map((n) => n.value), p = e.useRef(l);
99
+ e.useEffect(() => {
100
+ if (l == null || l === p.current) {
101
+ p.current = l;
102
+ return;
103
+ }
104
+ p.current = l;
105
+ const i = P(a, l).filter((w) => !o.includes(w));
106
+ i.length && N([...o, ...i]);
107
+ }, [l, a, o, N]);
108
+ const h = (n) => {
109
+ N(
110
+ o.includes(n) ? o.filter((i) => i !== n) : [...o, n]
111
+ );
112
+ }, L = (n) => {
113
+ V(a, n) ? h(n) : b(n);
114
+ }, k = l ? d(l) : null;
115
+ return /* @__PURE__ */ e.createElement("div", { className: K(c.r, R) }, /* @__PURE__ */ e.createElement(
116
+ "nav",
117
+ {
118
+ className: c.n,
119
+ "aria-label": S,
120
+ onKeyDown: (n) => j(n, {
121
+ values: x,
122
+ value: l,
123
+ onSelect: L,
124
+ vertical: !0
125
+ })
126
+ },
127
+ a.map((n, i) => /* @__PURE__ */ e.createElement("div", { key: i, className: c.g }, n.label != null ? /* @__PURE__ */ e.createElement("div", { className: c.l }, n.label) : null, /* @__PURE__ */ e.createElement(
128
+ C,
129
+ {
130
+ items: n.items,
131
+ value: l,
132
+ open: o,
133
+ onSelect: b,
134
+ onToggle: h
135
+ }
136
+ ))),
137
+ r != null ? /* @__PURE__ */ e.createElement("div", { className: c.f }, r) : null
138
+ ), /* @__PURE__ */ e.createElement("div", { className: c.m }, k ? /* @__PURE__ */ e.createElement(D, { fallback: u }, e.createElement(k)) : null));
139
+ }
140
+ export {
141
+ le as N
142
+ };
@@ -1 +1 @@
1
- @layer elcrm-components{.cvr{display:flex;align-items:stretch;gap:var(--shell-gap-lg);width:100%;flex:1 1 auto;min-height:0;box-sizing:border-box;padding:var(--nav-sections-padding)}.cvn{display:flex;flex-direction:column;gap:var(--shell-gap);flex:0 0 var(--nav-sections-nav-width);width:var(--nav-sections-nav-width);min-width:var(--nav-sections-nav-width);box-sizing:border-box}.cvg{display:flex;flex-direction:column;gap:2px;padding-bottom:var(--shell-gap);border-bottom:1px solid var(--shell-border)}.cvg:last-of-type{border-bottom:none;padding-bottom:0}.cvl{font-size:var(--sidebar-group-font-size);font-weight:var(--shell-font-weight);letter-spacing:.04em;text-transform:uppercase;color:var(--shell-color-muted);padding:0 var(--shell-padding-inline) var(--shell-gap-sm)}.cvi:is(button){position:relative;font-size:var(--shell-font-size-sm)}.cva:before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:0 2px 2px 0;background:var(--shell-color-accent)}.cvi:disabled{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.cvc{display:inline-flex;flex:0 0 auto;color:var(--shell-color-muted);line-height:0}.cva .cvc{color:var(--shell-color)}.cvt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cvf{margin-top:auto;padding-top:var(--shell-gap);border-top:1px solid var(--shell-border)}.cvm{flex:1 1 auto;min-width:0;max-width:var(--nav-sections-main-max-width);width:100%;min-height:0;overflow:auto}@media (max-width: 720px){.cvr{flex-direction:column}.cvn{flex:none;width:100%;min-width:0;max-width:none}}}
1
+ @layer elcrm-components{.cvr{display:flex;align-items:stretch;gap:var(--shell-gap-lg);width:100%;flex:1 1 auto;min-height:0;box-sizing:border-box;padding:var(--nav-sections-padding)}.cvn{display:flex;flex-direction:column;gap:var(--shell-gap);flex:0 0 var(--nav-sections-nav-width);width:var(--nav-sections-nav-width);min-width:var(--nav-sections-nav-width);box-sizing:border-box}.cvg{display:flex;flex-direction:column;gap:2px;padding-bottom:var(--shell-gap);border-bottom:1px solid var(--shell-border)}.cvg:last-of-type{border-bottom:none;padding-bottom:0}.cvl{font-size:var(--sidebar-group-font-size);font-weight:var(--shell-font-weight);letter-spacing:.04em;text-transform:uppercase;color:var(--shell-color-muted);padding:0 var(--shell-padding-inline) var(--shell-gap-sm)}.cvi:is(button){position:relative;display:flex;width:100%;align-items:center;font-size:var(--shell-font-size-sm)}.cva:before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:0 2px 2px 0;background:var(--shell-color-accent)}.cvi:disabled{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.cvc{display:inline-flex;flex:0 0 auto;color:var(--shell-color-muted);line-height:0}.cva .cvc{color:var(--shell-color)}.cvt{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cve{flex-shrink:0;width:12px;height:12px;margin-left:auto;color:var(--shell-color-muted);transition:transform .15s ease}.cvi:is(button)[data-open=true] .cve{transform:rotate(180deg);color:inherit}.cvs{display:flex;flex-direction:column;gap:2px;padding-inline-start:var(--sidebar-indent)}.cvf{margin-top:auto;padding-top:var(--shell-gap);border-top:1px solid var(--shell-border)}.cvm{flex:1 1 auto;min-width:0;max-width:var(--nav-sections-main-max-width);width:100%;min-height:0;overflow:auto}@media (max-width: 720px){.cvr{flex-direction:column}.cvn{flex:none;width:100%;min-width:0;max-width:none}}}
@@ -15,6 +15,17 @@ export type NavSectionsItem = {
15
15
  icon?: React.ReactNode;
16
16
  /** Недоступный пункт. */
17
17
  disabled?: boolean;
18
+ /**
19
+ * Вложенные пункты — есть → шеврон и раскрытие.
20
+ * Клик по родителю только открывает/закрывает, панель не меняем.
21
+ */
22
+ items?: NavSectionsItem[];
23
+ /**
24
+ * Подменю открыто при первом рендере.
25
+ * Если выбран потомок — ветка откроется сама.
26
+ * @default false
27
+ */
28
+ defaultOpen?: boolean;
18
29
  };
19
30
  /**
20
31
  * Группа пунктов сайдбара (опциональный заголовок + `items`).
@@ -46,6 +57,17 @@ export type NavSectionsProps = {
46
57
  defaultValue?: string;
47
58
  /** Смена активного раздела (`next` = `item.value`). */
48
59
  onValueChange?: (value: string) => void;
60
+ /**
61
+ * Раскрытые родители (их `value`). Контролируемый режим.
62
+ * Вместе с `onOpenChange`.
63
+ */
64
+ open?: string[];
65
+ /**
66
+ * Начально раскрытые (плюс `item.defaultOpen` и предки выбранного).
67
+ */
68
+ defaultOpen?: string[];
69
+ /** Смена набора раскрытых родителей */
70
+ onOpenChange?: (open: string[]) => void;
49
71
  /** Fallback для `<Suspense>` при ленивой загрузке секции. */
50
72
  loading?: React.ReactNode;
51
73
  /** Низ сайдбара (например кнопка «Назад»). */
@@ -68,34 +90,40 @@ export type NavSectionsProps = {
68
90
  * - {@link Menu} — простая навигация без панелей.
69
91
  *
70
92
  * API: декларативные `groups` / `items`. Идентификатор раздела — `value` (не `key`).
93
+ * У пункта `items` + `defaultOpen` — раскрываемое подменю (как у Sidebar).
71
94
  * Compound-слоты (`NavSections.Item`) не используются.
72
95
  *
73
96
  * CSS-токены: `--shell-*`, `--nav-sections-padding`, `--nav-sections-nav-width`,
74
- * `--nav-sections-main-max-width`.
97
+ * `--nav-sections-main-max-width`, `--sidebar-indent`.
75
98
  *
76
99
  * @example
77
100
  * ```tsx
78
101
  * const groups = [
79
102
  * {
80
- * label: "Личное",
103
+ * label: "Документация",
81
104
  * items: [
82
- * { value: "profile", label: "Профиль", icon: <Icons.Line name="user" /> },
83
- * { value: "security", label: "Безопасность" },
105
+ * {
106
+ * value: "bridge",
107
+ * label: "VK Bridge",
108
+ * defaultOpen: true,
109
+ * items: [
110
+ * { value: "intro", label: "Введение" },
111
+ * { value: "events", label: "События", items: [
112
+ * { value: "evt-init", label: "VKWebAppInit" },
113
+ * ] },
114
+ * ],
115
+ * },
116
+ * { value: "auth", label: "Авторизация VK" },
84
117
  * ],
85
118
  * },
86
119
  * ];
87
120
  *
88
- * const resolveSection = createLazyResolver(
89
- * (v) => import(`./sections/${v}.tsx`),
90
- * );
91
- *
92
121
  * <NavSections
93
122
  * groups={groups}
94
123
  * resolveSection={resolveSection}
95
124
  * value={section}
96
125
  * onValueChange={setSection}
97
- * footer={<Button onSend={() => navigate(-1)}>Назад</Button>}
98
126
  * />
99
127
  * ```
100
128
  */
101
- export declare function NavSections({ groups, resolveSection, value: valueProp, defaultValue, onValueChange, loading, footer, className, navLabel, }: NavSectionsProps): React.JSX.Element;
129
+ export declare function NavSections({ groups, resolveSection, value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen, onOpenChange, loading, footer, className, navLabel, }: NavSectionsProps): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- import { N as e } from "./NavSections-BpyZPimZ.js";
1
+ import { N as e } from "./NavSections-D6rDEtT1.js";
2
2
  export {
3
3
  e as NavSections
4
4
  };