@elcrm/components 0.2.6 → 0.2.8

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
@@ -31,6 +31,7 @@ import {
31
31
  ItemColumn,
32
32
  Card,
33
33
  ActionGroup,
34
+ Toolbar,
34
35
  TextGroup,
35
36
  Stat,
36
37
  Stack,
@@ -101,7 +102,8 @@ import { RadioGroup } from "@elcrm/components/RadioGroup";
101
102
  | **NavSections** | Сайдбар + lazy-панель | `groups` (`value`), `resolveSection`, `defaultValue` |
102
103
  | **TabSections** | Горизонтальные вкладки + lazy | `items` (`value` + `badge`), `resolveSection` |
103
104
  | **Card** | Карточка | `title`, `--card-pointer` |
104
- | **ActionGroup** | Тулбар действий | `label`, `items` и/или `children` |
105
+ | **ActionGroup** | Ряд иконок в карточке | `label`, `items` и/или `children` |
106
+ | **Toolbar** | Фильтры одной высоты | `size`, `width` (`l` = 100%, `s` = по контенту), `align`, `label`, `children` |
105
107
  | **TextGroup** | Title + description | `title`, `description`, `size` |
106
108
  | **ItemColumn** | Иконка + столбик в Item | `icon`, `title`, `extra`, `aside`, `description`, `footer` / `meta`, `unread` |
107
109
  | **Stat** | Карточка-метрика | `label`, `value`, `tone`, `labelMuted` |
@@ -737,6 +739,9 @@ const resolve = createLazyResolver(
737
739
  --pricing-card-min-width / --pricing-table-label-width
738
740
  --card-pointer
739
741
  --stack-grid-min
742
+ --toolbar-gap
743
+ --healthmap-cell / --healthmap-gap / --healthmap-l0…l4
744
+ --panel-info-width / --panel-info-width-min / --panel-info-overlay-max / --panel-info-backdrop
740
745
  --row-gap / --column-gap
741
746
  --popup-shadow
742
747
  ```
@@ -1,30 +1,31 @@
1
1
  import e from "react";
2
- import { C as f } from "./CheckIcon-FdB8fMEF.js";
3
- import { c as m } from "./cx-CcykAxZN.js";
4
- import { P as j } from "./Popover-D5RymtcJ.js";
5
- import { u as I } from "./useControllableValue-CWqH1kpM.js";
6
- import './Dropdown.css';const M = "crr", P = "crt", R = "crc", W = "crp", z = "crh", F = "crg", G = "crd", T = "cri", V = "crn", q = "crm", A = "crk", H = "crl", J = "cru", K = "crb", Q = "cro", U = "crf", X = "crx", Y = "cry", Z = "crv", n = {
7
- r: M,
8
- t: P,
9
- c: R,
10
- p: W,
11
- h: z,
12
- g: F,
13
- d: G,
14
- i: T,
15
- n: V,
16
- m: q,
17
- k: A,
18
- l: H,
19
- u: J,
20
- b: K,
21
- o: Q,
22
- f: U,
23
- x: X,
24
- y: Y,
25
- v: Z
2
+ import { C as E } from "./CheckIcon-FdB8fMEF.js";
3
+ import { c as p } from "./cx-CcykAxZN.js";
4
+ import { P as M } from "./Popover-DWdvZTuA.js";
5
+ import { u as P } from "./Toolbar-C1zAk9ak.js";
6
+ import { u as R } from "./useControllableValue-CWqH1kpM.js";
7
+ import './Dropdown.css';const T = "crr", W = "crt", F = "crc", G = "crp", V = "crh", q = "crg", A = "crd", H = "cri", J = "crn", K = "crm", Q = "crk", U = "crl", X = "cru", Y = "crb", Z = "cro", _ = "crf", $ = "crx", S = "cry", ee = "crv", n = {
8
+ r: T,
9
+ t: W,
10
+ c: F,
11
+ p: G,
12
+ h: V,
13
+ g: q,
14
+ d: A,
15
+ i: H,
16
+ n: J,
17
+ m: K,
18
+ k: Q,
19
+ l: U,
20
+ u: X,
21
+ b: Y,
22
+ o: Z,
23
+ f: _,
24
+ x: $,
25
+ y: S,
26
+ v: ee
26
27
  };
27
- function _() {
28
+ function ne() {
28
29
  return /* @__PURE__ */ e.createElement("svg", { className: n.c, viewBox: "0 0 12 12", "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(
29
30
  "path",
30
31
  {
@@ -37,7 +38,7 @@ function _() {
37
38
  }
38
39
  ));
39
40
  }
40
- function $() {
41
+ function ce() {
41
42
  return /* @__PURE__ */ e.createElement("svg", { className: n.m, viewBox: "0 0 12 12", "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(
42
43
  "path",
43
44
  {
@@ -50,74 +51,76 @@ function $() {
50
51
  }
51
52
  ));
52
53
  }
53
- function S({ item: a }) {
54
- return a.checkbox ? /* @__PURE__ */ e.createElement("span", { className: m(n.x, a.checked && n.y), "aria-hidden": !0 }, a.checked ? /* @__PURE__ */ e.createElement(f, { className: n.v }) : null) : a.checked ? /* @__PURE__ */ e.createElement("span", { className: n.k, "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(f, { className: n.v })) : a.status != null ? /* @__PURE__ */ e.createElement(
54
+ function re({ item: a }) {
55
+ return a.checkbox ? /* @__PURE__ */ e.createElement("span", { className: p(n.x, a.checked && n.y), "aria-hidden": !0 }, a.checked ? /* @__PURE__ */ e.createElement(E, { className: n.v }) : null) : a.checked ? /* @__PURE__ */ e.createElement("span", { className: n.k, "aria-hidden": !0 }, /* @__PURE__ */ e.createElement(E, { className: n.v })) : a.status != null ? /* @__PURE__ */ e.createElement(
55
56
  "span",
56
57
  {
57
- className: m(n.b, a.status === "online" ? n.o : n.f),
58
+ className: p(n.b, a.status === "online" ? n.o : n.f),
58
59
  "aria-hidden": !0
59
60
  }
60
61
  ) : a.icon != null ? /* @__PURE__ */ e.createElement("span", { className: n.n }, a.icon) : null;
61
62
  }
62
- function ee(a, t, l) {
63
+ function ae(a, t, l) {
63
64
  return t != null && t.length > 0 ? l != null && t[0] && t[0].title == null ? [{ ...t[0], title: l }, ...t.slice(1)] : t : a != null && a.length > 0 ? [{ title: l, items: a }] : l != null ? [{ title: l, items: [] }] : [];
64
65
  }
65
- function le({
66
+ function ie({
66
67
  items: a,
67
68
  groups: t,
68
69
  trigger: l,
69
70
  label: o,
70
- hideChevron: E = !1,
71
- title: v,
71
+ hideChevron: v = !1,
72
+ title: b,
72
73
  align: k = "end",
73
- placement: b = "auto",
74
- open: N,
75
- defaultOpen: x = !1,
76
- onOpenChange: C,
77
- closeOnSelect: y = !0,
74
+ placement: N = "auto",
75
+ open: x,
76
+ defaultOpen: C = !1,
77
+ onOpenChange: y,
78
+ closeOnSelect: g = !0,
78
79
  disabled: u = !1,
80
+ size: L,
79
81
  onSelect: d,
80
- className: g
82
+ className: z
81
83
  }) {
82
- const [i = !1, p] = I(
83
- N,
84
+ const i = P(), w = L ?? (i == null ? void 0 : i.size), [h = !1, f] = R(
84
85
  x,
85
- C
86
- ), L = ee(a, t, v), w = (r) => {
86
+ C,
87
+ y
88
+ ), O = ae(a, t, b), B = (r) => {
87
89
  var c;
88
90
  if (r.disabled) return;
89
- (c = r.onClick) == null || c.call(r), d == null || d(r.value, r), r.checkbox || r.submenu || !y || p(!1);
90
- }, O = l ?? /* @__PURE__ */ e.createElement(
91
+ (c = r.onClick) == null || c.call(r), d == null || d(r.value, r), r.checkbox || r.submenu || !g || f(!1);
92
+ }, D = l ?? /* @__PURE__ */ e.createElement(
91
93
  "button",
92
94
  {
93
95
  type: "button",
94
96
  className: n.t,
95
- "data-open": i ? "true" : void 0,
97
+ "data-size": w,
98
+ "data-open": h ? "true" : void 0,
96
99
  disabled: u,
97
100
  "aria-haspopup": "menu",
98
- "aria-expanded": i
101
+ "aria-expanded": h
99
102
  },
100
103
  o,
101
- E ? null : /* @__PURE__ */ e.createElement(_, null)
104
+ v ? null : /* @__PURE__ */ e.createElement(ne, null)
102
105
  );
103
106
  return /* @__PURE__ */ e.createElement(
104
- j,
107
+ M,
105
108
  {
106
- className: m(n.r, g),
107
- open: i,
109
+ className: p(n.r, z),
110
+ open: h,
108
111
  onOpenChange: (r) => {
109
- u || p(r);
112
+ u || f(r);
110
113
  },
111
114
  align: k,
112
- placement: b,
113
- trigger: O,
115
+ placement: N,
116
+ trigger: D,
114
117
  label: typeof o == "string" ? o : void 0,
115
118
  disabled: u
116
119
  },
117
- /* @__PURE__ */ e.createElement("div", { className: n.p, "data-align": k }, L.map((r, h) => /* @__PURE__ */ e.createElement(e.Fragment, { key: h }, h > 0 ? /* @__PURE__ */ e.createElement("hr", { className: n.d }) : null, /* @__PURE__ */ e.createElement("div", { className: n.g, role: "group" }, r.title != null ? /* @__PURE__ */ e.createElement("p", { className: n.h }, r.title) : null, r.items.map((c) => {
118
- const B = r.items.some(
120
+ /* @__PURE__ */ e.createElement("div", { className: n.p, "data-align": k }, O.map((r, m) => /* @__PURE__ */ e.createElement(e.Fragment, { key: m }, m > 0 ? /* @__PURE__ */ e.createElement("hr", { className: n.d }) : null, /* @__PURE__ */ e.createElement("div", { className: n.g, role: "group" }, r.title != null ? /* @__PURE__ */ e.createElement("p", { className: n.h }, r.title) : null, r.items.map((c) => {
121
+ const j = r.items.some(
119
122
  (s) => s.checked || s.checkbox || s.status != null || s.icon != null
120
- ), D = /* @__PURE__ */ e.createElement(S, { item: c });
123
+ ), I = /* @__PURE__ */ e.createElement(re, { item: c });
121
124
  return /* @__PURE__ */ e.createElement(
122
125
  "button",
123
126
  {
@@ -129,16 +132,16 @@ function le({
129
132
  "data-danger": c.danger ? "true" : void 0,
130
133
  disabled: c.disabled,
131
134
  "aria-checked": c.checked != null || c.checkbox ? !!c.checked : void 0,
132
- onClick: () => w(c)
135
+ onClick: () => B(c)
133
136
  },
134
- B ? D ?? /* @__PURE__ */ e.createElement("span", { className: n.n, "aria-hidden": !0 }) : null,
137
+ j ? I ?? /* @__PURE__ */ e.createElement("span", { className: n.n, "aria-hidden": !0 }) : null,
135
138
  c.label != null ? /* @__PURE__ */ e.createElement("span", { className: n.l }, c.label) : null,
136
139
  c.shortcut != null ? /* @__PURE__ */ e.createElement("span", { className: n.u }, c.shortcut) : null,
137
- c.submenu ? /* @__PURE__ */ e.createElement($, null) : null
140
+ c.submenu ? /* @__PURE__ */ e.createElement(ce, null) : null
138
141
  );
139
142
  })))))
140
143
  );
141
144
  }
142
145
  export {
143
- le as D
146
+ ie as D
144
147
  };
package/dist/Dropdown.css CHANGED
@@ -1 +1 @@
1
- @layer elcrm-components{.crr{position:relative;display:inline-flex;vertical-align:middle}.crt{display:inline-flex;align-items:center;gap:var(--shell-gap-sm);margin:0;padding:var(--shell-padding);box-sizing:border-box;font:inherit;font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);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;line-height:1.2}.crt:hover{background:var(--shell-background-hover)}.crt[data-open=true]{border-color:var(--shell-color-accent);color:var(--shell-color-accent)}.crc{display:inline-flex;width:var(--shell-icon-size);height:var(--shell-icon-size);flex-shrink:0;color:var(--shell-color-muted)}.crt[data-open=true] .crc{color:var(--shell-color-accent)}.crp{min-width:0}.crh{margin:0;padding:var(--shell-padding);font-size:var(--shell-font-size-sm);font-weight:var(--shell-font-weight);color:var(--shell-color-muted);line-height:1.3}.crg{display:flex;flex-direction:column;gap:2px}.crd{height:0;margin:var(--shell-gap-sm) 0;border:0;border-top:1px solid var(--shell-border)}.cri{display:flex;align-items:center;gap:var(--shell-gap-sm);width:100%;margin:0;padding:var(--shell-padding);box-sizing:border-box;text-align:left;font:inherit;font-size:var(--shell-font-size);font-weight:500;color:var(--shell-color);background:transparent;border:none;border-radius:calc(var(--shell-radius) * .7);cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;line-height:1.35}.cri:hover:not(:disabled),.cri:focus-visible:not(:disabled){background:var(--shell-background-hover);outline:none}.cri[data-checked=true]{background:var(--shell-background-selected)}.cri[data-danger=true]{color:var(--shell-color-danger)}.cri[data-danger=true] .crn,.cri[data-danger=true] .crm{color:inherit}.cri:disabled,.cri[aria-disabled=true]{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.crn,.crk{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:var(--shell-icon-size);height:var(--shell-icon-size);color:var(--shell-color-muted)}.crk{color:var(--shell-color-accent)}.crl{flex:1;min-width:0}.cru{flex-shrink:0;margin-left:auto;font-size:var(--shell-font-size-sm);color:var(--shell-color-muted)}.crm{display:inline-flex;flex-shrink:0;margin-left:auto;width:var(--shell-icon-size);height:var(--shell-icon-size);color:var(--shell-color-muted)}.crb{display:inline-flex;flex-shrink:0;width:var(--shell-status-size);height:var(--shell-status-size);border-radius:50%}.cro{background:var(--shell-color-success)}.crf{background:var(--shell-color-muted)}.crx{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;box-sizing:border-box;width:var(--shell-check-size);height:var(--shell-check-size);border:1px solid var(--shell-border);border-radius:calc(var(--shell-radius) * .4);background:var(--shell-background);color:var(--avatar-color)}.cry{border-color:var(--shell-color-accent);background:var(--shell-color-accent)}.crv{width:70%;height:70%;display:block}}
1
+ @layer elcrm-components{.crr{position:relative;display:inline-flex;vertical-align:middle}.crt{display:inline-flex;align-items:center;gap:var(--shell-gap-sm);margin:0;padding:var(--shell-padding);box-sizing:border-box;font:inherit;font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);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;line-height:1.2}.crt[data-size]{height:var(--control-height);min-height:var(--control-height);padding-block:0;box-sizing:border-box}.crt:hover{background:var(--shell-background-hover)}.crt[data-open=true]{border-color:var(--shell-color-accent);color:var(--shell-color-accent)}.crc{display:inline-flex;width:var(--shell-icon-size);height:var(--shell-icon-size);flex-shrink:0;color:var(--shell-color-muted)}.crt[data-open=true] .crc{color:var(--shell-color-accent)}.crp{min-width:0}.crh{margin:0;padding:var(--shell-padding);font-size:var(--shell-font-size-sm);font-weight:var(--shell-font-weight);color:var(--shell-color-muted);line-height:1.3}.crg{display:flex;flex-direction:column;gap:2px}.crd{height:0;margin:var(--shell-gap-sm) 0;border:0;border-top:1px solid var(--shell-border)}.cri{display:flex;align-items:center;gap:var(--shell-gap-sm);width:100%;margin:0;padding:var(--shell-padding);box-sizing:border-box;text-align:left;font:inherit;font-size:var(--shell-font-size);font-weight:500;color:var(--shell-color);background:transparent;border:none;border-radius:calc(var(--shell-radius) * .7);cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;line-height:1.35}.cri:hover:not(:disabled),.cri:focus-visible:not(:disabled){background:var(--shell-background-hover);outline:none}.cri[data-checked=true]{background:var(--shell-background-selected)}.cri[data-danger=true]{color:var(--shell-color-danger)}.cri[data-danger=true] .crn,.cri[data-danger=true] .crm{color:inherit}.cri:disabled,.cri[aria-disabled=true]{opacity:var(--shell-disabled-opacity);cursor:not-allowed}.crn,.crk{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;width:var(--shell-icon-size);height:var(--shell-icon-size);color:var(--shell-color-muted)}.crk{color:var(--shell-color-accent)}.crl{flex:1;min-width:0}.cru{flex-shrink:0;margin-left:auto;font-size:var(--shell-font-size-sm);color:var(--shell-color-muted)}.crm{display:inline-flex;flex-shrink:0;margin-left:auto;width:var(--shell-icon-size);height:var(--shell-icon-size);color:var(--shell-color-muted)}.crb{display:inline-flex;flex-shrink:0;width:var(--shell-status-size);height:var(--shell-status-size);border-radius:50%}.cro{background:var(--shell-color-success)}.crf{background:var(--shell-color-muted)}.crx{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;box-sizing:border-box;width:var(--shell-check-size);height:var(--shell-check-size);border:1px solid var(--shell-border);border-radius:calc(var(--shell-radius) * .4);background:var(--shell-background);color:var(--avatar-color)}.cry{border-color:var(--shell-color-accent);background:var(--shell-color-accent)}.crv{width:70%;height:70%;display:block}}
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { UiSize } from './types';
2
3
  /**
3
4
  * Пункт выпадающего меню {@link Dropdown}.
4
5
  *
@@ -82,6 +83,11 @@ export type DropdownProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onChange
82
83
  closeOnSelect?: boolean;
83
84
  /** Заблокировать триггер. */
84
85
  disabled?: boolean;
86
+ /**
87
+ * Высота встроенного триггера (`--control-height`).
88
+ * Внутри {@link Toolbar} берётся из тулбара, если не задан.
89
+ */
90
+ size?: UiSize;
85
91
  /** Выбор пункта: `value` + сам item. */
86
92
  onSelect?: (value: string, item: DropdownItem) => void;
87
93
  };
@@ -127,4 +133,4 @@ export type DropdownProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onChange
127
133
  * />
128
134
  * ```
129
135
  */
130
- export declare function Dropdown({ items, groups, trigger, label, hideChevron, title, align, placement, open: openProp, defaultOpen, onOpenChange, closeOnSelect, disabled, onSelect, className, }: DropdownProps): React.JSX.Element;
136
+ export declare function Dropdown({ items, groups, trigger, label, hideChevron, title, align, placement, open: openProp, defaultOpen, onOpenChange, closeOnSelect, disabled, size: sizeProp, onSelect, className, }: DropdownProps): React.JSX.Element;
package/dist/Dropdown.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as p } from "./Dropdown-B9AodUFf.js";
1
+ import { D as p } from "./Dropdown-DosvDR13.js";
2
2
  export {
3
3
  p as Dropdown
4
4
  };
@@ -0,0 +1,243 @@
1
+ import e, { useState as L, useMemo as Y, useEffect as X } from "react";
2
+ import { createPortal as ee } from "react-dom";
3
+ import { c as j } from "./cx-CcykAxZN.js";
4
+ import './Healthmap.css';const te = "hAr", ne = "hAc", se = "hAa", ae = "hAh", le = "hAt", ce = "hAm", re = "hAo", oe = "hAn", me = "hAd", ie = "hAw", ue = "hAk", he = "hAe", pe = "hAf", Ee = "hAi", fe = "hAg", ge = "hAb", ye = "hAp", be = "hAq", ve = "hAv", Ne = "hAl", de = "hAz", Te = "hAj", Ae = "hAu", ke = "hAx", we = "hAs", De = "hAy", n = {
5
+ r: te,
6
+ c: ne,
7
+ a: se,
8
+ h: ae,
9
+ t: le,
10
+ m: ce,
11
+ o: re,
12
+ n: oe,
13
+ d: me,
14
+ w: ie,
15
+ k: ue,
16
+ e: he,
17
+ f: pe,
18
+ i: Ee,
19
+ g: fe,
20
+ b: ge,
21
+ p: ye,
22
+ q: be,
23
+ v: ve,
24
+ l: Ne,
25
+ z: de,
26
+ j: Te,
27
+ u: Ae,
28
+ x: ke,
29
+ s: we,
30
+ y: De
31
+ }, f = 5, _ = 12, B = 3, z = _ + B, Ue = [
32
+ "янв",
33
+ "фев",
34
+ "мар",
35
+ "апр",
36
+ "май",
37
+ "июн",
38
+ "июл",
39
+ "авг",
40
+ "сен",
41
+ "окт",
42
+ "ноя",
43
+ "дек"
44
+ ], xe = {
45
+ one: "действие",
46
+ few: "действия",
47
+ many: "действий"
48
+ };
49
+ function x(t) {
50
+ const [l, a, m] = t.split("-").map(Number);
51
+ return new Date(Date.UTC(l, a - 1, m));
52
+ }
53
+ function Ce(t, l) {
54
+ if (t <= 0) return 0;
55
+ if (l <= 1) return 4;
56
+ const a = t / l;
57
+ return a > 0.75 ? 4 : a > 0.5 ? 3 : a > 0.25 ? 2 : 1;
58
+ }
59
+ function T(t, l) {
60
+ const a = t % 10, m = t % 100;
61
+ return a === 1 && m !== 11 ? l.one : a >= 2 && a <= 4 && (m < 10 || m >= 20) ? l.few : l.many;
62
+ }
63
+ function $e(t) {
64
+ return T(t, { one: "день", few: "дня", many: "дней" });
65
+ }
66
+ function H(t) {
67
+ return x(t).toLocaleDateString("ru-RU", {
68
+ weekday: "long",
69
+ day: "numeric",
70
+ month: "long",
71
+ year: "numeric",
72
+ timeZone: "UTC"
73
+ });
74
+ }
75
+ function Me(t) {
76
+ let l = 0;
77
+ for (let a = t.length - 1; a >= 0; a--) {
78
+ if (t[a].count <= 0) {
79
+ if (l === 0) continue;
80
+ break;
81
+ }
82
+ l++;
83
+ }
84
+ return l;
85
+ }
86
+ function Re({
87
+ data: t,
88
+ loading: l = !1,
89
+ year: a = null,
90
+ onYearChange: m,
91
+ hint: G,
92
+ brandName: O = "Активность",
93
+ brandSub: I = "сводка",
94
+ unit: A = xe,
95
+ className: Z
96
+ }) {
97
+ const [g, k] = L(0), [p, w] = L(null), i = Y(() => {
98
+ var P;
99
+ if (!((P = t == null ? void 0 : t.days) != null && P.length))
100
+ return {
101
+ weeks: [],
102
+ months: [],
103
+ streak: 0,
104
+ activeDays: 0,
105
+ bestDay: 0
106
+ };
107
+ const s = Math.max(1, ...t.days.map((o) => o.count)), h = new Map(t.days.map((o) => [o.date, o.count])), c = x(t.from), u = x(t.to), M = c.getUTCDay(), Q = M === 0 ? -6 : 1 - M, r = new Date(c);
108
+ r.setUTCDate(r.getUTCDate() + Q);
109
+ const E = [], S = [];
110
+ let F = -1;
111
+ for (; r.getTime() <= u.getTime() || E.length === 0; ) {
112
+ const o = [];
113
+ for (let U = 0; U < 7; U++) {
114
+ const V = r.getUTCFullYear(), N = r.getUTCMonth(), W = r.getUTCDate(), R = `${V}-${String(N + 1).padStart(2, "0")}-${String(W).padStart(2, "0")}`, d = r.getTime() >= c.getTime() && r.getTime() <= u.getTime(), q = d ? h.get(R) ?? 0 : 0;
115
+ U === 0 && d && N !== F && (S.push({ label: Ue[N], col: E.length }), F = N), o.push({
116
+ date: R,
117
+ count: d ? q : 0,
118
+ level: d ? Ce(q, s) : 0
119
+ }), r.setUTCDate(r.getUTCDate() + 1);
120
+ }
121
+ if (E.push(o), r.getTime() > u.getTime() && E.length >= 52 || E.length > 60) break;
122
+ }
123
+ return {
124
+ weeks: E,
125
+ months: S,
126
+ streak: Me(t.days),
127
+ activeDays: t.days.filter((o) => o.count > 0).length,
128
+ bestDay: Math.max(0, ...t.days.map((o) => o.count))
129
+ };
130
+ }, [t]), y = Y(() => {
131
+ var h;
132
+ const s = (h = t == null ? void 0 : t.years) != null && h.length ? [...t.years] : [(/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCFullYear() - 1];
133
+ return [...new Set(s)].sort((c, u) => u - c);
134
+ }, [t == null ? void 0 : t.years]), D = (/* @__PURE__ */ new Date()).getUTCFullYear(), b = a ?? (t == null ? void 0 : t.year) ?? D, C = (t == null ? void 0 : t.total) ?? 0, v = Math.max(1, Math.ceil(y.length / f));
135
+ X(() => {
136
+ const s = y.indexOf(b);
137
+ s < 0 || k(Math.floor(s / f));
138
+ }, [b, y]);
139
+ const J = y.slice(
140
+ g * f,
141
+ g * f + f
142
+ );
143
+ function $(s, h) {
144
+ const c = h.getBoundingClientRect();
145
+ w({
146
+ date: s.date,
147
+ count: s.count,
148
+ x: c.left + c.width / 2,
149
+ y: c.top
150
+ });
151
+ }
152
+ const K = l ? "Загрузка…" : `${C} ${T(C, A)} за ${a == null || a === D ? "последний год" : `${a} год`}`;
153
+ return /* @__PURE__ */ e.createElement("div", { className: j(n.r, Z) }, /* @__PURE__ */ e.createElement("section", { className: n.c, "aria-label": "Календарь активности" }, /* @__PURE__ */ e.createElement("div", { className: n.h }, /* @__PURE__ */ e.createElement("h3", { className: n.t }, K)), /* @__PURE__ */ e.createElement(
154
+ "div",
155
+ {
156
+ className: n.m,
157
+ "aria-hidden": !0,
158
+ style: {
159
+ width: i.weeks.length ? i.weeks.length * z - B : void 0
160
+ }
161
+ },
162
+ i.months.map((s) => /* @__PURE__ */ e.createElement(
163
+ "span",
164
+ {
165
+ key: `${s.label}-${s.col}`,
166
+ className: n.o,
167
+ style: { left: s.col * z }
168
+ },
169
+ s.label
170
+ ))
171
+ ), /* @__PURE__ */ e.createElement("div", { className: n.n }, /* @__PURE__ */ e.createElement("div", { className: n.d, "aria-hidden": !0 }, /* @__PURE__ */ e.createElement("span", null, "Пн"), /* @__PURE__ */ e.createElement("span", null), /* @__PURE__ */ e.createElement("span", null, "Ср"), /* @__PURE__ */ e.createElement("span", null), /* @__PURE__ */ e.createElement("span", null, "Пт"), /* @__PURE__ */ e.createElement("span", null), /* @__PURE__ */ e.createElement("span", null)), /* @__PURE__ */ e.createElement(
172
+ "div",
173
+ {
174
+ className: n.w,
175
+ style: {
176
+ gridTemplateColumns: `repeat(${i.weeks.length}, ${_}px)`
177
+ },
178
+ role: "grid",
179
+ "aria-label": "График активности"
180
+ },
181
+ i.weeks.map((s, h) => /* @__PURE__ */ e.createElement("div", { key: h, className: n.k, role: "row" }, s.map((c) => /* @__PURE__ */ e.createElement(
182
+ "button",
183
+ {
184
+ key: c.date,
185
+ type: "button",
186
+ className: n.e,
187
+ "data-level": c.level,
188
+ role: "gridcell",
189
+ "aria-label": `${H(c.date)}: ${c.count} ${T(c.count, A)}`,
190
+ onMouseEnter: (u) => $(c, u.currentTarget),
191
+ onMouseLeave: () => w(null),
192
+ onFocus: (u) => $(c, u.currentTarget),
193
+ onBlur: () => w(null)
194
+ }
195
+ ))))
196
+ )), /* @__PURE__ */ e.createElement("div", { className: n.f }, /* @__PURE__ */ e.createElement("span", { className: n.i }, G ?? ""), /* @__PURE__ */ e.createElement("nav", { className: n.z, "aria-label": "Год" }, v > 1 ? /* @__PURE__ */ e.createElement(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ className: n.u,
201
+ "aria-label": "Предыдущие годы",
202
+ disabled: g <= 0,
203
+ onClick: () => k((s) => Math.max(0, s - 1))
204
+ },
205
+ "‹"
206
+ ) : null, /* @__PURE__ */ e.createElement("div", { className: n.j, role: "tablist" }, J.map((s) => /* @__PURE__ */ e.createElement(
207
+ "button",
208
+ {
209
+ key: s,
210
+ type: "button",
211
+ role: "tab",
212
+ "aria-selected": s === b,
213
+ className: j(n.x, s === b && n.s),
214
+ onClick: () => m == null ? void 0 : m(s === D ? null : s)
215
+ },
216
+ s
217
+ ))), v > 1 ? /* @__PURE__ */ e.createElement(
218
+ "button",
219
+ {
220
+ type: "button",
221
+ className: n.u,
222
+ "aria-label": "Следующие годы",
223
+ disabled: g >= v - 1,
224
+ onClick: () => k((s) => Math.min(v - 1, s + 1))
225
+ },
226
+ "›"
227
+ ) : null), /* @__PURE__ */ e.createElement("div", { className: n.g, "aria-hidden": !0 }, /* @__PURE__ */ e.createElement("span", null, "Меньше"), [0, 1, 2, 3, 4].map((s) => /* @__PURE__ */ e.createElement("span", { key: s, className: n.e, "data-level": s })), /* @__PURE__ */ e.createElement("span", null, "Больше")))), /* @__PURE__ */ e.createElement("aside", { className: n.a, "aria-label": "Сводка" }, /* @__PURE__ */ e.createElement("div", { className: n.b }, /* @__PURE__ */ e.createElement("span", null, O), /* @__PURE__ */ e.createElement("span", null, I)), /* @__PURE__ */ e.createElement("div", { className: n.p }, /* @__PURE__ */ e.createElement("div", { className: n.q }, /* @__PURE__ */ e.createElement("span", { className: n.v }, l ? "—" : i.streak), /* @__PURE__ */ e.createElement("span", { className: n.l }, "серия, ", $e(i.streak))), /* @__PURE__ */ e.createElement("div", { className: n.q }, /* @__PURE__ */ e.createElement("span", { className: n.v }, l ? "—" : i.activeDays), /* @__PURE__ */ e.createElement("span", { className: n.l }, "активных дней")), /* @__PURE__ */ e.createElement("div", { className: n.q }, /* @__PURE__ */ e.createElement("span", { className: n.v }, l ? "—" : i.bestDay), /* @__PURE__ */ e.createElement("span", { className: n.l }, "макс. за день")))), p ? ee(
228
+ /* @__PURE__ */ e.createElement(
229
+ "div",
230
+ {
231
+ className: n.y,
232
+ style: { left: p.x, top: p.y },
233
+ role: "tooltip"
234
+ },
235
+ /* @__PURE__ */ e.createElement("div", null, H(p.date)),
236
+ /* @__PURE__ */ e.createElement("div", null, p.count, " ", T(p.count, A))
237
+ ),
238
+ document.body
239
+ ) : null);
240
+ }
241
+ export {
242
+ Re as H
243
+ };
@@ -0,0 +1 @@
1
+ @layer elcrm-components{.hAr{display:flex;gap:var(--shell-gap);align-items:stretch;width:100%}.hAc,.hAa{border:1px solid var(--shell-border);border-radius:var(--shell-radius);background:var(--shell-background);box-sizing:border-box}.hAc{flex:1;min-width:0;display:flex;flex-direction:column;padding:var(--shell-gap-lg) var(--shell-padding-inline);overflow-x:auto}.hAh{margin-bottom:var(--shell-gap-sm)}.hAt{margin:0;font-size:var(--shell-font-size);font-weight:var(--shell-font-weight);color:var(--shell-color)}.hAm{position:relative;margin:0 0 4px 26px;height:16px;font-size:11px;color:var(--shell-color-muted)}.hAo{position:absolute;top:0;white-space:nowrap}.hAn{display:flex;gap:4px;align-items:flex-start;flex:1}.hAd{display:grid;grid-template-rows:repeat(7,var(--healthmap-cell));gap:var(--healthmap-gap);width:22px;font-size:10px;line-height:var(--healthmap-cell);color:var(--shell-color-muted);flex-shrink:0;align-content:center}.hAw{display:grid;gap:var(--healthmap-gap);align-content:center}.hAk{display:grid;grid-template-rows:repeat(7,var(--healthmap-cell));gap:var(--healthmap-gap)}.hAe{width:var(--healthmap-cell);height:var(--healthmap-cell);padding:0;border:0;border-radius:2px;background:var(--healthmap-l0);box-sizing:border-box;cursor:default;display:block}.hAe:focus-visible{outline:2px solid var(--shell-color-accent);outline-offset:1px}.hAe[data-level="1"]{background:var(--healthmap-l1)}.hAe[data-level="2"]{background:var(--healthmap-l2)}.hAe[data-level="3"]{background:var(--healthmap-l3)}.hAe[data-level="4"]{background:var(--healthmap-l4)}.hAf{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:10px;margin-top:auto;padding-top:10px;margin-left:26px;font-size:11px;color:var(--shell-color-muted)}.hAi{min-width:0}.hAg{display:flex;align-items:center;gap:4px;justify-self:end}.hAa{flex:0 0 180px;display:flex;flex-direction:column;gap:var(--shell-gap-lg);padding:var(--shell-gap-lg) 18px}.hAb{display:flex;flex-direction:column;gap:2px;line-height:1.2}.hAb>span:first-child{font-size:var(--shell-font-size-lg);font-weight:700;letter-spacing:-.02em;color:var(--shell-color)}.hAb>span:last-child{font-size:11px;color:var(--shell-color-muted);text-transform:uppercase;letter-spacing:.04em}.hAp{display:flex;flex-direction:column;gap:var(--shell-gap-sm);flex:1;justify-content:center}.hAq{display:flex;flex-direction:column;gap:2px}.hAv{font-size:20px;font-weight:650;font-variant-numeric:tabular-nums;letter-spacing:-.03em;color:var(--shell-color);line-height:1.1}.hAl{font-size:var(--shell-font-size-sm);color:var(--shell-color-muted)}.hAz{display:flex;align-items:center;justify-content:center;gap:4px;justify-self:center}.hAj{display:flex;align-items:center;gap:4px}.hAu{width:28px;height:28px;border:0;border-radius:var(--shell-radius);background:transparent;color:var(--shell-color-muted);font-size:18px;line-height:1;cursor:pointer}.hAu:hover:not(:disabled){color:var(--shell-color);background:var(--shell-background-muted)}.hAu:disabled{opacity:var(--shell-disabled-opacity);cursor:default}.hAx{border:0;background:transparent;color:var(--shell-color-muted);font-size:var(--shell-font-size-sm);padding:5px 11px;border-radius:999px;cursor:pointer}.hAx:hover{color:var(--shell-color)}.hAs{background:var(--shell-color-accent);color:var(--shell-background)}.hAy{position:fixed;z-index:calc(var(--shell-z) + 50);transform:translate(-50%,calc(-100% - 10px));min-width:160px;max-width:280px;padding:var(--shell-gap-sm) var(--shell-gap);border-radius:var(--shell-radius);background:var(--shell-background);border:1px solid var(--shell-border);box-shadow:var(--popup-shadow);pointer-events:none;color:var(--shell-color)}.hAy>:first-child{font-size:var(--shell-font-size-sm);color:var(--shell-color-muted)}.hAy>:last-child{font-size:var(--shell-font-size);font-weight:600}@media (max-width: 720px){.hAr{flex-direction:column}.hAa{flex:none;width:100%}.hAf{grid-template-columns:1fr;margin-left:0;justify-items:start}.hAg,.hAz{justify-self:start}}}
@@ -0,0 +1,69 @@
1
+ import { default as React } from 'react';
2
+ /** День теплокарты: дата `yyyy-mm-dd` и счётчик. */
3
+ export type HealthmapDay = {
4
+ date: string;
5
+ count: number;
6
+ };
7
+ /**
8
+ * Данные теплокарты (как ответ heatmap-RPC).
9
+ * Компонент не ходит в API — данные даёт приложение.
10
+ */
11
+ export type HealthmapData = {
12
+ total: number;
13
+ from: string;
14
+ to: string;
15
+ year: number;
16
+ years: number[];
17
+ days: HealthmapDay[];
18
+ };
19
+ /** Склонение единиц («действие» / «событие»). */
20
+ export type HealthmapUnit = {
21
+ one: string;
22
+ few: string;
23
+ many: string;
24
+ };
25
+ export type HealthmapProps = {
26
+ /** Данные года; `null` — пустая сетка. */
27
+ data: HealthmapData | null;
28
+ /** Показать «Загрузка…» в заголовке. */
29
+ loading?: boolean;
30
+ /**
31
+ * Выбранный год. `null` — «последний год» (как в ответе сервера).
32
+ * Контролируемый режим: вместе с `onYearChange`.
33
+ */
34
+ year?: number | null;
35
+ /** Смена года (клик по вкладке). */
36
+ onYearChange?: (year: number | null) => void;
37
+ /** Подсказка под сеткой. */
38
+ hint?: string;
39
+ /** Бренд в боковой колонке KPI. */
40
+ brandName?: string;
41
+ /** Подпись под брендом. */
42
+ brandSub?: string;
43
+ /** Единицы для склонения счётчиков. */
44
+ unit?: HealthmapUnit;
45
+ /** Доп. CSS-класс. */
46
+ className?: string;
47
+ };
48
+ /**
49
+ * Теплокарта активности по дням (как GitHub contribution graph).
50
+ *
51
+ * Роли: Healthmap = визуализация `days[]`; данные грузит приложение
52
+ * (`Api.query` / свой RPC), не сам компонент.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * <Healthmap
57
+ * data={heatmap}
58
+ * loading={pending}
59
+ * year={year}
60
+ * onYearChange={setYear}
61
+ * brandName="eKivi"
62
+ * brandSub="клиенты"
63
+ * unit={{ one: "событие", few: "события", many: "событий" }}
64
+ * />
65
+ * ```
66
+ *
67
+ * Токены: `--shell-*`, `--healthmap-cell`, `--healthmap-gap`, `--healthmap-l0`…`--healthmap-l4`.
68
+ */
69
+ export declare function Healthmap({ data, loading, year, onYearChange, hint, brandName, brandSub, unit, className, }: HealthmapProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { H as m } from "./Healthmap-B6cdiLUl.js";
2
+ export {
3
+ m as Healthmap
4
+ };
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);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}}
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;gap:var(--layout-gap, 0)}.clb{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden;background:var(--layout-main-background, transparent);border-radius:var(--layout-main-radius, 0);box-shadow:var(--layout-main-shadow, none);border:var(--layout-main-border, none)}.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/Loading.css CHANGED
@@ -1 +1 @@
1
- @layer elcrm-components{.cgr{display:flex;align-items:center;justify-content:center;gap:var(--shell-gap-sm);padding:var(--shell-gap-lg);color:var(--shell-color-muted)}.cgi{box-sizing:border-box;width:var(--shell-icon-size);height:var(--shell-icon-size);border:2px solid var(--shell-border);border-top-color:var(--shell-color-accent);border-radius:var(--avatar-radius);animation:cgspin .7s linear infinite}}@keyframes cgspin{to{transform:rotate(360deg)}}
1
+ @layer elcrm-components{.cgr{display:flex;align-items:center;justify-content:center;gap:var(--shell-gap-sm);padding:var(--shell-gap-lg);color:var(--shell-color-muted)}.cgi{box-sizing:border-box;width:var(--shell-icon-size);height:var(--shell-icon-size);border:2px solid var(--shell-border);border-top-color:var(--shell-color-accent);border-radius:var(--avatar-radius);animation:cgk .7s linear infinite}}@keyframes cgk{to{transform:rotate(360deg)}}