@snack-uikit/notification 0.13.30 → 0.13.31-preview-7cd3a502.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 (50) hide show
  1. package/README.md +9 -0
  2. package/dist/cjs/components/NotificationCard/NotificationCard.js +5 -0
  3. package/dist/cjs/components/NotificationCard/styles.module.css +6 -0
  4. package/dist/cjs/components/NotificationPanel/NotificationPanel.d.ts +11 -2
  5. package/dist/cjs/components/NotificationPanel/NotificationPanel.js +20 -6
  6. package/dist/cjs/components/NotificationPanel/components/NotificationCardStack/NotificationCardStack.d.ts +5 -1
  7. package/dist/cjs/components/NotificationPanel/components/NotificationCardStack/NotificationCardStack.js +7 -5
  8. package/dist/cjs/components/NotificationPanel/components/NotificationCardStack/utils.d.ts +3 -1
  9. package/dist/cjs/components/NotificationPanel/components/NotificationCardStack/utils.js +1 -1
  10. package/dist/cjs/components/NotificationPanel/components/NotificationPanelBlank/NotificationPanelBlank.js +2 -1
  11. package/dist/cjs/components/NotificationPanel/components/NotificationPanelBlank/styles.module.css +7 -0
  12. package/dist/cjs/components/NotificationPanel/components/NotificationPanelGroup/NotificationPanelGroup.d.ts +12 -0
  13. package/dist/cjs/components/NotificationPanel/components/NotificationPanelGroup/NotificationPanelGroup.js +51 -0
  14. package/dist/cjs/components/NotificationPanel/components/NotificationPanelGroup/index.d.ts +1 -0
  15. package/dist/cjs/components/NotificationPanel/components/NotificationPanelGroup/index.js +13 -0
  16. package/dist/cjs/components/NotificationPanel/components/NotificationPanelGroup/styles.module.css +34 -0
  17. package/dist/cjs/components/NotificationPanel/components/index.d.ts +1 -0
  18. package/dist/cjs/components/NotificationPanel/components/index.js +2 -1
  19. package/dist/cjs/components/NotificationPanel/styles.module.css +8 -0
  20. package/dist/esm/components/NotificationCard/NotificationCard.js +2 -1
  21. package/dist/esm/components/NotificationCard/styles.module.css +6 -0
  22. package/dist/esm/components/NotificationPanel/NotificationPanel.d.ts +11 -2
  23. package/dist/esm/components/NotificationPanel/NotificationPanel.js +9 -3
  24. package/dist/esm/components/NotificationPanel/components/NotificationCardStack/NotificationCardStack.d.ts +5 -1
  25. package/dist/esm/components/NotificationPanel/components/NotificationCardStack/NotificationCardStack.js +4 -2
  26. package/dist/esm/components/NotificationPanel/components/NotificationCardStack/utils.d.ts +3 -1
  27. package/dist/esm/components/NotificationPanel/components/NotificationCardStack/utils.js +1 -1
  28. package/dist/esm/components/NotificationPanel/components/NotificationPanelBlank/NotificationPanelBlank.js +2 -2
  29. package/dist/esm/components/NotificationPanel/components/NotificationPanelBlank/styles.module.css +7 -0
  30. package/dist/esm/components/NotificationPanel/components/NotificationPanelGroup/NotificationPanelGroup.d.ts +12 -0
  31. package/dist/esm/components/NotificationPanel/components/NotificationPanelGroup/NotificationPanelGroup.js +22 -0
  32. package/dist/esm/components/NotificationPanel/components/NotificationPanelGroup/index.d.ts +1 -0
  33. package/dist/esm/components/NotificationPanel/components/NotificationPanelGroup/index.js +1 -0
  34. package/dist/esm/components/NotificationPanel/components/NotificationPanelGroup/styles.module.css +34 -0
  35. package/dist/esm/components/NotificationPanel/components/index.d.ts +1 -0
  36. package/dist/esm/components/NotificationPanel/components/index.js +1 -0
  37. package/dist/esm/components/NotificationPanel/styles.module.css +8 -0
  38. package/package.json +4 -2
  39. package/src/components/NotificationCard/NotificationCard.tsx +3 -0
  40. package/src/components/NotificationCard/styles.module.scss +6 -0
  41. package/src/components/NotificationPanel/NotificationPanel.tsx +28 -2
  42. package/src/components/NotificationPanel/components/NotificationCardStack/NotificationCardStack.tsx +12 -1
  43. package/src/components/NotificationPanel/components/NotificationCardStack/utils.ts +4 -5
  44. package/src/components/NotificationPanel/components/NotificationPanelBlank/NotificationPanelBlank.tsx +2 -2
  45. package/src/components/NotificationPanel/components/NotificationPanelBlank/styles.module.scss +7 -0
  46. package/src/components/NotificationPanel/components/NotificationPanelGroup/NotificationPanelGroup.tsx +31 -0
  47. package/src/components/NotificationPanel/components/NotificationPanelGroup/index.ts +1 -0
  48. package/src/components/NotificationPanel/components/NotificationPanelGroup/styles.module.scss +39 -0
  49. package/src/components/NotificationPanel/components/index.ts +1 -0
  50. package/src/components/NotificationPanel/styles.module.scss +8 -1
package/README.md CHANGED
@@ -261,6 +261,7 @@ function NotificationPanelStackExample() {
261
261
  | title* | `string` | - | Заголовок панели |
262
262
  | settings | `NotificationPanelSettingsProps` | - | Кнопка настроек и выпадающий список |
263
263
  | segments | `Omit<SegmentedControlProps, "data-test-id" \| "size">` | - | Сегменты для фильтрации |
264
+ | chipToggle | `{ label: string; checked: boolean; defaultChecked?: boolean; onChange: (checked: boolean) => void; }` | { label: 'Label', checked: false, onChange: () => {}, } | Переключатель для фильтрации |
264
265
  | readAllButton | `Omit<ButtonFunctionProps, "data-test-id"> & { tooltip?: TooltipProps; }` | - | Кнопка в "шапке" панели |
265
266
  | footerButton | `{ label: string; onClick: MouseEventHandler<HTMLButtonElement>; }` | - | Кнопка внизу панели |
266
267
  | className | `string` | - | CSS-класс |
@@ -292,6 +293,14 @@ function NotificationPanelStackExample() {
292
293
  | defaultOpen | `boolean` | false | Состояние открыт/закрыт по умолчанию |
293
294
  | onOpenChanged | `(open: boolean) => void` | - | Колбек смены состояния открыт/закрыт |
294
295
  | actions | `Action[]` | - | Список действий в выпадающем меню |
296
+ | unread | `boolean` | - | Состояние непрочитанных карточек |
297
+ ## NotificationPanel.Group
298
+ ### Props
299
+ | name | type | default value | description |
300
+ |------|------|---------------|-------------|
301
+ | children* | `ReactNode` | - | Содержимое группы |
302
+ | title* | `string` | - | Заголовок группы |
303
+ | className | `string` | - | CSS-класс |
295
304
  ## NotificationPanelPopover
296
305
  Компонент-обёртка для NotificationPanel для использования как выпадающий элемент
297
306
  ### Props
@@ -23,6 +23,7 @@ const react_1 = require("react");
23
23
  const button_1 = require("@snack-uikit/button");
24
24
  const icons_1 = require("@snack-uikit/icons");
25
25
  const link_1 = require("@snack-uikit/link");
26
+ const status_1 = require("@snack-uikit/status");
26
27
  const truncate_string_1 = require("@snack-uikit/truncate-string");
27
28
  const typography_1 = require("@snack-uikit/typography");
28
29
  const utils_1 = require("@snack-uikit/utils");
@@ -150,6 +151,10 @@ function NotificationCard(_a) {
150
151
  "data-test-id": constants_1.TEST_IDS.date,
151
152
  children: date
152
153
  })]
154
+ }), unread && (0, jsx_runtime_1.jsx)(status_1.StatusIndicator, {
155
+ className: styles_module_scss_1.default.statusIndicator,
156
+ size: 'xs',
157
+ appearance: 'primary'
153
158
  })]
154
159
  }));
155
160
  }
@@ -23,6 +23,12 @@
23
23
  box-sizing:border-box;
24
24
  }
25
25
 
26
+ .statusIndicator{
27
+ position:absolute;
28
+ top:4px;
29
+ right:4px;
30
+ }
31
+
26
32
  .notificationCard{
27
33
  padding-left:var(--space-notification-panel-card-padding-horizontal, 12px);
28
34
  padding-right:var(--space-notification-panel-card-padding-horizontal, 12px);
@@ -3,7 +3,7 @@ import { ButtonFunctionProps } from '@snack-uikit/button';
3
3
  import { SegmentedControlProps } from '@snack-uikit/segmented-control';
4
4
  import { TooltipProps } from '@snack-uikit/tooltip';
5
5
  import { WithSupportProps } from '@snack-uikit/utils';
6
- import { NotificationCardStack, NotificationCardStackProps, NotificationPanelBlank, NotificationPanelBlankProps, NotificationPanelSettingsProps } from './components';
6
+ import { NotificationCardStack, NotificationCardStackProps, NotificationPanelBlank, NotificationPanelBlankProps, NotificationPanelGroup, NotificationPanelGroupProps, NotificationPanelSettingsProps } from './components';
7
7
  import { NotificationPanelDivider, NotificationPanelDividerProps } from './components/NotificationPanelDivider';
8
8
  export type { NotificationPanelBlankProps };
9
9
  export type NotificationPanelProps = WithSupportProps<{
@@ -13,6 +13,13 @@ export type NotificationPanelProps = WithSupportProps<{
13
13
  settings?: NotificationPanelSettingsProps;
14
14
  /** Сегменты для фильтрации */
15
15
  segments?: Omit<SegmentedControlProps, 'size' | 'data-test-id'>;
16
+ /** Переключатель для фильтрации */
17
+ chipToggle?: {
18
+ label: string;
19
+ checked: boolean;
20
+ defaultChecked?: boolean;
21
+ onChange: (checked: boolean) => void;
22
+ };
16
23
  /** Кнопка в "шапке" панели */
17
24
  readAllButton?: Omit<ButtonFunctionProps, 'data-test-id'> & {
18
25
  tooltip?: TooltipProps;
@@ -35,7 +42,7 @@ export type NotificationPanelProps = WithSupportProps<{
35
42
  scrollContainerRef?: RefObject<HTMLElement>;
36
43
  }>;
37
44
  /** Компонент панели для уведомлений */
38
- export declare function NotificationPanel({ title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount, scrollEndRef, scrollContainerRef, className, ...rest }: NotificationPanelProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare function NotificationPanel({ title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount, scrollEndRef, scrollContainerRef, className, chipToggle, ...rest }: NotificationPanelProps): import("react/jsx-runtime").JSX.Element;
39
46
  export declare namespace NotificationPanel {
40
47
  const Blank: typeof NotificationPanelBlank;
41
48
  type BlankProps = NotificationPanelBlankProps;
@@ -43,4 +50,6 @@ export declare namespace NotificationPanel {
43
50
  type DividerProps = NotificationPanelDividerProps;
44
51
  const Stack: typeof NotificationCardStack;
45
52
  type StackProps = NotificationCardStackProps;
53
+ const Group: typeof NotificationPanelGroup;
54
+ type GroupProps = NotificationPanelGroupProps;
46
55
  }
@@ -21,6 +21,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
21
21
  const classnames_1 = __importDefault(require("classnames"));
22
22
  const react_1 = require("react");
23
23
  const button_1 = require("@snack-uikit/button");
24
+ const chips_1 = require("@snack-uikit/chips");
24
25
  const scroll_1 = require("@snack-uikit/scroll");
25
26
  const segmented_control_1 = require("@snack-uikit/segmented-control");
26
27
  const skeleton_1 = require("@snack-uikit/skeleton");
@@ -46,9 +47,14 @@ function NotificationPanel(_a) {
46
47
  skeletonsAmount = 2,
47
48
  scrollEndRef,
48
49
  scrollContainerRef,
49
- className
50
+ className,
51
+ chipToggle = {
52
+ label: 'Label',
53
+ checked: false,
54
+ onChange: () => {}
55
+ }
50
56
  } = _a,
51
- rest = __rest(_a, ["title", "settings", "segments", "readAllButton", "footerButton", "content", "loading", "skeletonsAmount", "scrollEndRef", "scrollContainerRef", "className"]);
57
+ rest = __rest(_a, ["title", "settings", "segments", "readAllButton", "footerButton", "content", "loading", "skeletonsAmount", "scrollEndRef", "scrollContainerRef", "className", "chipToggle"]);
52
58
  const skeletons = (0, react_1.useMemo)(() => Array.from({
53
59
  length: skeletonsAmount
54
60
  }, (_, i) => i), [skeletonsAmount]);
@@ -77,14 +83,21 @@ function NotificationPanel(_a) {
77
83
  }))
78
84
  }), settings && (0, jsx_runtime_1.jsx)(components_2.NotificationPanelSettings, Object.assign({}, settings))]
79
85
  })]
80
- }), (0, jsx_runtime_1.jsx)("div", {
81
- children: segments && (0, jsx_runtime_1.jsx)(segmented_control_1.SegmentedControl, Object.assign({}, segments, {
82
- size: 's',
86
+ }), (0, jsx_runtime_1.jsxs)("div", {
87
+ className: styles_module_scss_1.default.actionsRow,
88
+ children: [segments && (0, jsx_runtime_1.jsx)(segmented_control_1.SegmentedControl, Object.assign({}, segments, {
89
+ size: 'xs',
83
90
  items: segments.items.map(item => Object.assign(Object.assign({}, item), {
84
91
  disabled: item.disabled || loading
85
92
  })),
86
93
  "data-test-id": constants_1.TEST_IDS.segments
87
- }))
94
+ })), chipToggle && (0, jsx_runtime_1.jsx)(chips_1.ChipToggle, {
95
+ size: 'xs',
96
+ disabled: loading,
97
+ label: chipToggle.label,
98
+ onChange: chipToggle.onChange,
99
+ checked: chipToggle.checked
100
+ })]
88
101
  })]
89
102
  }), (0, jsx_runtime_1.jsx)(scroll_1.Scroll, {
90
103
  size: 'm',
@@ -116,4 +129,5 @@ function NotificationPanel(_a) {
116
129
  NotificationPanel.Blank = components_2.NotificationPanelBlank;
117
130
  NotificationPanel.Divider = NotificationPanelDivider_1.NotificationPanelDivider;
118
131
  NotificationPanel.Stack = components_2.NotificationCardStack;
132
+ NotificationPanel.Group = components_2.NotificationPanelGroup;
119
133
  })(NotificationPanel || (exports.NotificationPanel = NotificationPanel = {}));
@@ -23,5 +23,9 @@ export type NotificationCardStackProps = {
23
23
  * Список действий в выпадающем меню
24
24
  */
25
25
  actions?: Action[];
26
+ /**
27
+ * Состояние непрочитанных карточек
28
+ */
29
+ unread?: boolean;
26
30
  };
27
- export declare function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, }: NotificationCardStackProps): import("react/jsx-runtime").JSX.Element | null;
31
+ export declare function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, unread, }: NotificationCardStackProps): import("react/jsx-runtime").JSX.Element | null;
@@ -26,7 +26,8 @@ function NotificationCardStack(_ref) {
26
26
  defaultOpen,
27
27
  onOpenChanged,
28
28
  title,
29
- actions
29
+ actions,
30
+ unread
30
31
  } = _ref;
31
32
  const [actionsOpen, setActionsOpen] = (0, react_1.useState)(false);
32
33
  const {
@@ -47,13 +48,14 @@ function NotificationCardStack(_ref) {
47
48
  children: first
48
49
  });
49
50
  }
50
- const firstCardElement = open ? first : (0, utils_1.cloneCard)(first, {
51
- onClick: toggleOpen
52
- });
51
+ const firstCardElement = open ? first : (0, utils_1.cloneCard)(first, componentProps => Object.assign(Object.assign({}, componentProps), {
52
+ onClick: toggleOpen,
53
+ unread: unread || componentProps.unread
54
+ }));
53
55
  return (0, jsx_runtime_1.jsxs)("div", {
54
56
  className: styles_module_scss_1.default.container,
55
57
  style: {
56
- '--snack-notification-stack-animation-duration': "".concat(ANIMATION_DURATION, "s")
58
+ '--snack-notification-stack-animation-duration': `${ANIMATION_DURATION}s`
57
59
  },
58
60
  "data-test-id": constants_1.TEST_IDS.cardStack.wrapper,
59
61
  children: [(0, jsx_runtime_1.jsxs)("div", {
@@ -1,2 +1,4 @@
1
1
  import { ReactNode } from 'react';
2
- export declare const cloneCard: (element: ReactNode, props: Record<string, unknown>) => string | number | boolean | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined;
2
+ type ComponentProps = Record<string, unknown>;
3
+ export declare const cloneCard: (element: ReactNode, props: (componentProps: ComponentProps) => ComponentProps) => string | number | boolean | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined;
4
+ export {};
@@ -9,6 +9,6 @@ const cloneCard = (element, props) => {
9
9
  if (!(0, react_1.isValidElement)(element)) {
10
10
  return element;
11
11
  }
12
- return (0, react_1.cloneElement)(element, Object.assign(Object.assign({}, element.props), props));
12
+ return (0, react_1.cloneElement)(element, props(element.props));
13
13
  };
14
14
  exports.cloneCard = cloneCard;
@@ -29,7 +29,8 @@ function NotificationPanelBlank(_a) {
29
29
  className
30
30
  } = _a,
31
31
  props = __rest(_a, ["icon", "className"]);
32
- return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {
32
+ return (0, jsx_runtime_1.jsx)("div", {
33
+ className: styles_module_scss_1.default.wrapper,
33
34
  children: (0, jsx_runtime_1.jsx)(info_block_1.InfoBlock, Object.assign({}, props, {
34
35
  icon: icon ? Object.assign(Object.assign({}, icon), {
35
36
  appearance: (_b = icon.appearance) !== null && _b !== void 0 ? _b : 'neutral'
@@ -1,3 +1,10 @@
1
+ .wrapper{
2
+ display:flex;
3
+ align-items:center;
4
+ justify-content:center;
5
+ min-height:316px;
6
+ }
7
+
1
8
  .notificationPanelBlank{
2
9
  padding:var(--space-notification-panel-body-info-padding, 24px);
3
10
  }
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from 'react';
2
+ import { WithSupportProps } from '@snack-uikit/utils';
3
+ export type NotificationPanelGroupProps = WithSupportProps<{
4
+ /** Заголовок группы */
5
+ title: string;
6
+ /** Содержимое группы */
7
+ children: ReactNode;
8
+ /** CSS-класс */
9
+ className?: string;
10
+ }>;
11
+ /** Группа уведомлений с заголовком внутри панели */
12
+ export declare function NotificationPanelGroup({ title, children, className, ...rest }: NotificationPanelGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ var __rest = void 0 && (void 0).__rest || function (s, e) {
4
+ var t = {};
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
8
+ }
9
+ return t;
10
+ };
11
+ var __importDefault = void 0 && (void 0).__importDefault || function (mod) {
12
+ return mod && mod.__esModule ? mod : {
13
+ "default": mod
14
+ };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", {
17
+ value: true
18
+ });
19
+ exports.NotificationPanelGroup = NotificationPanelGroup;
20
+ const jsx_runtime_1 = require("react/jsx-runtime");
21
+ const classnames_1 = __importDefault(require("classnames"));
22
+ const truncate_string_1 = require("@snack-uikit/truncate-string");
23
+ const typography_1 = require("@snack-uikit/typography");
24
+ const utils_1 = require("@snack-uikit/utils");
25
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
26
+ /** Группа уведомлений с заголовком внутри панели */
27
+ function NotificationPanelGroup(_a) {
28
+ var {
29
+ title,
30
+ children,
31
+ className
32
+ } = _a,
33
+ rest = __rest(_a, ["title", "children", "className"]);
34
+ return (0, jsx_runtime_1.jsxs)("div", Object.assign({
35
+ className: (0, classnames_1.default)(styles_module_scss_1.default.root, className)
36
+ }, (0, utils_1.extractSupportProps)(rest), {
37
+ children: [(0, jsx_runtime_1.jsx)("div", {
38
+ className: styles_module_scss_1.default.title,
39
+ children: (0, jsx_runtime_1.jsx)(typography_1.Typography.LightLabelS, {
40
+ className: styles_module_scss_1.default.text,
41
+ tag: 'span',
42
+ children: (0, jsx_runtime_1.jsx)(truncate_string_1.TruncateString, {
43
+ text: title
44
+ })
45
+ })
46
+ }), (0, jsx_runtime_1.jsx)("div", {
47
+ className: styles_module_scss_1.default.content,
48
+ children: children
49
+ })]
50
+ }));
51
+ }
@@ -0,0 +1 @@
1
+ export { NotificationPanelGroup, type NotificationPanelGroupProps } from './NotificationPanelGroup';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NotificationPanelGroup = void 0;
7
+ var NotificationPanelGroup_1 = require("./NotificationPanelGroup");
8
+ Object.defineProperty(exports, "NotificationPanelGroup", {
9
+ enumerable: true,
10
+ get: function () {
11
+ return NotificationPanelGroup_1.NotificationPanelGroup;
12
+ }
13
+ });
@@ -0,0 +1,34 @@
1
+ .root{
2
+ position:relative;
3
+ display:flex;
4
+ flex-direction:column;
5
+ gap:var(--space-notification-panel-container-body-gap, 16px);
6
+ }
7
+
8
+ .title{
9
+ position:sticky;
10
+ z-index:1;
11
+ top:4px;
12
+ display:flex;
13
+ justify-content:center;
14
+ }
15
+
16
+ .text{
17
+ padding-left:var(--space-notification-panel-date-horizontal-padding, 8px);
18
+ padding-right:var(--space-notification-panel-date-horizontal-padding, 8px);
19
+ height:var(--size-notification-panel-date-height, 20px);
20
+ border-radius:var(--radius-chips-m, 20px);
21
+ display:inline-flex;
22
+ align-items:center;
23
+ justify-content:center;
24
+ color:var(--sys-neutral-text-disabled, #aaaebd);
25
+ background-color:var(--sys-neutral-background1-level, #fdfdfd);
26
+ border-color:var(--sys-neutral-decor-default, #dde0ea);
27
+ border-style:solid;
28
+ }
29
+
30
+ .content{
31
+ display:flex;
32
+ flex-direction:column;
33
+ gap:var(--space-notification-panel-container-body-gap, 16px);
34
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './NotificationPanelSettings';
2
2
  export * from './NotificationPanelBlank';
3
3
  export * from './NotificationCardStack';
4
+ export * from './NotificationPanelGroup';
@@ -24,4 +24,5 @@ Object.defineProperty(exports, "__esModule", {
24
24
  });
25
25
  __exportStar(require("./NotificationPanelSettings"), exports);
26
26
  __exportStar(require("./NotificationPanelBlank"), exports);
27
- __exportStar(require("./NotificationCardStack"), exports);
27
+ __exportStar(require("./NotificationCardStack"), exports);
28
+ __exportStar(require("./NotificationPanelGroup"), exports);
@@ -101,4 +101,12 @@
101
101
  -moz-column-gap:var(--dimension-1m, 8px);
102
102
  column-gap:var(--dimension-1m, 8px);
103
103
  align-items:center;
104
+ }
105
+
106
+ .actionsRow{
107
+ display:flex;
108
+ -moz-column-gap:var(--dimension-1m, 8px);
109
+ column-gap:var(--dimension-1m, 8px);
110
+ align-items:center;
111
+ justify-content:space-between;
104
112
  }
@@ -15,6 +15,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
15
15
  import { ButtonSimple, ButtonTonal } from '@snack-uikit/button';
16
16
  import { KebabSVG } from '@snack-uikit/icons';
17
17
  import { Link } from '@snack-uikit/link';
18
+ import { StatusIndicator } from '@snack-uikit/status';
18
19
  import { TruncateString } from '@snack-uikit/truncate-string';
19
20
  import { Typography } from '@snack-uikit/typography';
20
21
  import { extractSupportProps } from '@snack-uikit/utils';
@@ -56,5 +57,5 @@ export function NotificationCard(_a) {
56
57
  };
57
58
  return (_jsxs("div", Object.assign({ ref: cardRef, role: 'button', onClick: onClick, tabIndex: 0 }, extractSupportProps(rest), { "data-appearance": appearance, "data-unread": unread || undefined, "data-clickable": Boolean(onClick) || undefined, "data-droplist-open": isDroplistOpen || undefined, className: cn(styles.notificationCard, className), children: [(actions === null || actions === void 0 ? void 0 : actions.length) && (_jsx(ActionsButton, { className: styles.notificationCardFunction, actions: actions, open: isDroplistOpen, setDroplistOpen: setDroplistOpen, size: 's', icon: _jsx(KebabSVG, {}) })), label && (_jsx(Typography.LightLabelS, { tag: 'div', className: styles.notificationCardLabel, children: _jsx(TruncateString, { maxLines: 1, text: label, "data-test-id": TEST_IDS.label }) })), _jsxs("div", { className: styles.notificationCardTitle, children: [_jsx("div", { className: styles.notificationCardTitleIcon, children: icon }), _jsx(Typography.SansTitleS, { tag: 'div', className: styles.notificationCardTitleText, children: _jsx(TruncateString, { maxLines: 2, text: title, "data-test-id": TEST_IDS.title }) })] }), content && (_jsx(Typography.SansBodyS, { tag: 'div', className: styles.notificationCardContent, "data-test-id": TEST_IDS.content, children: content })), (primaryButton || secondaryButton) && (
58
59
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
59
- _jsxs("div", { className: styles.notificationCardButtons, onClick: stopPropagationClick, children: [secondaryButton && (_jsx(ButtonSimple, Object.assign({}, secondaryButton, { appearance: 'neutral', size: 's', "data-test-id": TEST_IDS.primaryButton }))), primaryButton && (_jsx(ButtonTonal, Object.assign({}, primaryButton, { appearance: 'neutral', size: 's', "data-test-id": TEST_IDS.secondaryButton })))] })), showFooter && (_jsxs("div", { className: styles.notificationCardFooter, children: [link && (_jsx(Link, Object.assign({}, link, { onClick: handleLinkClick, appearance: 'primary', textMode: 'accent', size: 's', "data-test-id": TEST_IDS.link }))), date && (_jsx(Typography.LightLabelS, { className: styles.notificationCardDate, "data-test-id": TEST_IDS.date, children: date }))] }))] })));
60
+ _jsxs("div", { className: styles.notificationCardButtons, onClick: stopPropagationClick, children: [secondaryButton && (_jsx(ButtonSimple, Object.assign({}, secondaryButton, { appearance: 'neutral', size: 's', "data-test-id": TEST_IDS.primaryButton }))), primaryButton && (_jsx(ButtonTonal, Object.assign({}, primaryButton, { appearance: 'neutral', size: 's', "data-test-id": TEST_IDS.secondaryButton })))] })), showFooter && (_jsxs("div", { className: styles.notificationCardFooter, children: [link && (_jsx(Link, Object.assign({}, link, { onClick: handleLinkClick, appearance: 'primary', textMode: 'accent', size: 's', "data-test-id": TEST_IDS.link }))), date && (_jsx(Typography.LightLabelS, { className: styles.notificationCardDate, "data-test-id": TEST_IDS.date, children: date }))] })), unread && _jsx(StatusIndicator, { className: styles.statusIndicator, size: 'xs', appearance: 'primary' })] })));
60
61
  }
@@ -23,6 +23,12 @@
23
23
  box-sizing:border-box;
24
24
  }
25
25
 
26
+ .statusIndicator{
27
+ position:absolute;
28
+ top:4px;
29
+ right:4px;
30
+ }
31
+
26
32
  .notificationCard{
27
33
  padding-left:var(--space-notification-panel-card-padding-horizontal, 12px);
28
34
  padding-right:var(--space-notification-panel-card-padding-horizontal, 12px);
@@ -3,7 +3,7 @@ import { ButtonFunctionProps } from '@snack-uikit/button';
3
3
  import { SegmentedControlProps } from '@snack-uikit/segmented-control';
4
4
  import { TooltipProps } from '@snack-uikit/tooltip';
5
5
  import { WithSupportProps } from '@snack-uikit/utils';
6
- import { NotificationCardStack, NotificationCardStackProps, NotificationPanelBlank, NotificationPanelBlankProps, NotificationPanelSettingsProps } from './components';
6
+ import { NotificationCardStack, NotificationCardStackProps, NotificationPanelBlank, NotificationPanelBlankProps, NotificationPanelGroup, NotificationPanelGroupProps, NotificationPanelSettingsProps } from './components';
7
7
  import { NotificationPanelDivider, NotificationPanelDividerProps } from './components/NotificationPanelDivider';
8
8
  export type { NotificationPanelBlankProps };
9
9
  export type NotificationPanelProps = WithSupportProps<{
@@ -13,6 +13,13 @@ export type NotificationPanelProps = WithSupportProps<{
13
13
  settings?: NotificationPanelSettingsProps;
14
14
  /** Сегменты для фильтрации */
15
15
  segments?: Omit<SegmentedControlProps, 'size' | 'data-test-id'>;
16
+ /** Переключатель для фильтрации */
17
+ chipToggle?: {
18
+ label: string;
19
+ checked: boolean;
20
+ defaultChecked?: boolean;
21
+ onChange: (checked: boolean) => void;
22
+ };
16
23
  /** Кнопка в "шапке" панели */
17
24
  readAllButton?: Omit<ButtonFunctionProps, 'data-test-id'> & {
18
25
  tooltip?: TooltipProps;
@@ -35,7 +42,7 @@ export type NotificationPanelProps = WithSupportProps<{
35
42
  scrollContainerRef?: RefObject<HTMLElement>;
36
43
  }>;
37
44
  /** Компонент панели для уведомлений */
38
- export declare function NotificationPanel({ title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount, scrollEndRef, scrollContainerRef, className, ...rest }: NotificationPanelProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare function NotificationPanel({ title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount, scrollEndRef, scrollContainerRef, className, chipToggle, ...rest }: NotificationPanelProps): import("react/jsx-runtime").JSX.Element;
39
46
  export declare namespace NotificationPanel {
40
47
  const Blank: typeof NotificationPanelBlank;
41
48
  type BlankProps = NotificationPanelBlankProps;
@@ -43,4 +50,6 @@ export declare namespace NotificationPanel {
43
50
  type DividerProps = NotificationPanelDividerProps;
44
51
  const Stack: typeof NotificationCardStack;
45
52
  type StackProps = NotificationCardStackProps;
53
+ const Group: typeof NotificationPanelGroup;
54
+ type GroupProps = NotificationPanelGroupProps;
46
55
  }
@@ -13,6 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import cn from 'classnames';
14
14
  import { useMemo } from 'react';
15
15
  import { ButtonFunction } from '@snack-uikit/button';
16
+ import { ChipToggle } from '@snack-uikit/chips';
16
17
  import { Scroll } from '@snack-uikit/scroll';
17
18
  import { SegmentedControl } from '@snack-uikit/segmented-control';
18
19
  import { SkeletonContextProvider, WithSkeleton } from '@snack-uikit/skeleton';
@@ -20,19 +21,24 @@ import { TruncateString } from '@snack-uikit/truncate-string';
20
21
  import { Typography } from '@snack-uikit/typography';
21
22
  import { extractSupportProps } from '@snack-uikit/utils';
22
23
  import { NotificationCardSkeleton } from '../NotificationCard/components';
23
- import { NotificationCardStack, NotificationPanelBlank, NotificationPanelSettings, } from './components';
24
+ import { NotificationCardStack, NotificationPanelBlank, NotificationPanelGroup, NotificationPanelSettings, } from './components';
24
25
  import { NotificationPanelDivider } from './components/NotificationPanelDivider';
25
26
  import { TEST_IDS } from './constants';
26
27
  import { WithTooltip } from './helperComponents';
27
28
  import styles from './styles.module.css';
28
29
  /** Компонент панели для уведомлений */
29
30
  export function NotificationPanel(_a) {
30
- var { title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount = 2, scrollEndRef, scrollContainerRef, className } = _a, rest = __rest(_a, ["title", "settings", "segments", "readAllButton", "footerButton", "content", "loading", "skeletonsAmount", "scrollEndRef", "scrollContainerRef", "className"]);
31
+ var { title, settings, segments, readAllButton, footerButton, content, loading, skeletonsAmount = 2, scrollEndRef, scrollContainerRef, className, chipToggle = {
32
+ label: 'Label',
33
+ checked: false,
34
+ onChange: () => { },
35
+ } } = _a, rest = __rest(_a, ["title", "settings", "segments", "readAllButton", "footerButton", "content", "loading", "skeletonsAmount", "scrollEndRef", "scrollContainerRef", "className", "chipToggle"]);
31
36
  const skeletons = useMemo(() => Array.from({ length: skeletonsAmount }, (_, i) => i), [skeletonsAmount]);
32
- return (_jsxs("div", Object.assign({ className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [_jsxs("div", { className: styles.notificationPanelHeader, children: [_jsxs("div", { className: styles.notificationPanelHeadline, children: [_jsx(Typography.SansHeadlineS, { className: styles.notificationPanelTitle, children: _jsx(TruncateString, { text: title, "data-test-id": TEST_IDS.title }) }), _jsxs("div", { className: styles.notificationPanelHeaderActions, children: [readAllButton && (_jsx(WithTooltip, { tooltip: readAllButton.tooltip, children: _jsx(ButtonFunction, Object.assign({}, readAllButton, { onClick: readAllButton.onClick, size: 'xs', disabled: readAllButton.disabled || loading, "data-test-id": TEST_IDS.readAll })) })), settings && _jsx(NotificationPanelSettings, Object.assign({}, settings))] })] }), _jsx("div", { children: segments && (_jsx(SegmentedControl, Object.assign({}, segments, { size: 's', items: segments.items.map(item => (Object.assign(Object.assign({}, item), { disabled: item.disabled || loading }))), "data-test-id": TEST_IDS.segments }))) })] }), _jsx(Scroll, { size: 'm', ref: scrollContainerRef, children: _jsxs("div", { className: styles.notificationPanelBody, children: [content, loading && (_jsx(SkeletonContextProvider, { loading: loading || false, children: skeletons.map(skeleton => (_jsx(WithSkeleton, { skeleton: _jsx(NotificationCardSkeleton, {}) }, skeleton))) })), _jsx("div", { className: styles.scrollStub, ref: scrollEndRef })] }) }), footerButton && (_jsx("button", { type: 'button', onClick: footerButton.onClick, className: styles.notificationPanelFooterButton, "data-test-id": TEST_IDS.footerButton, children: _jsx(Typography.SansLabelS, { children: footerButton.label }) }))] })));
37
+ return (_jsxs("div", Object.assign({ className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [_jsxs("div", { className: styles.notificationPanelHeader, children: [_jsxs("div", { className: styles.notificationPanelHeadline, children: [_jsx(Typography.SansHeadlineS, { className: styles.notificationPanelTitle, children: _jsx(TruncateString, { text: title, "data-test-id": TEST_IDS.title }) }), _jsxs("div", { className: styles.notificationPanelHeaderActions, children: [readAllButton && (_jsx(WithTooltip, { tooltip: readAllButton.tooltip, children: _jsx(ButtonFunction, Object.assign({}, readAllButton, { onClick: readAllButton.onClick, size: 'xs', disabled: readAllButton.disabled || loading, "data-test-id": TEST_IDS.readAll })) })), settings && _jsx(NotificationPanelSettings, Object.assign({}, settings))] })] }), _jsxs("div", { className: styles.actionsRow, children: [segments && (_jsx(SegmentedControl, Object.assign({}, segments, { size: 'xs', items: segments.items.map(item => (Object.assign(Object.assign({}, item), { disabled: item.disabled || loading }))), "data-test-id": TEST_IDS.segments }))), chipToggle && (_jsx(ChipToggle, { size: 'xs', disabled: loading, label: chipToggle.label, onChange: chipToggle.onChange, checked: chipToggle.checked }))] })] }), _jsx(Scroll, { size: 'm', ref: scrollContainerRef, children: _jsxs("div", { className: styles.notificationPanelBody, children: [content, loading && (_jsx(SkeletonContextProvider, { loading: loading || false, children: skeletons.map(skeleton => (_jsx(WithSkeleton, { skeleton: _jsx(NotificationCardSkeleton, {}) }, skeleton))) })), _jsx("div", { className: styles.scrollStub, ref: scrollEndRef })] }) }), footerButton && (_jsx("button", { type: 'button', onClick: footerButton.onClick, className: styles.notificationPanelFooterButton, "data-test-id": TEST_IDS.footerButton, children: _jsx(Typography.SansLabelS, { children: footerButton.label }) }))] })));
33
38
  }
34
39
  (function (NotificationPanel) {
35
40
  NotificationPanel.Blank = NotificationPanelBlank;
36
41
  NotificationPanel.Divider = NotificationPanelDivider;
37
42
  NotificationPanel.Stack = NotificationCardStack;
43
+ NotificationPanel.Group = NotificationPanelGroup;
38
44
  })(NotificationPanel || (NotificationPanel = {}));
@@ -23,5 +23,9 @@ export type NotificationCardStackProps = {
23
23
  * Список действий в выпадающем меню
24
24
  */
25
25
  actions?: Action[];
26
+ /**
27
+ * Состояние непрочитанных карточек
28
+ */
29
+ unread?: boolean;
26
30
  };
27
- export declare function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, }: NotificationCardStackProps): import("react/jsx-runtime").JSX.Element | null;
31
+ export declare function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, unread, }: NotificationCardStackProps): import("react/jsx-runtime").JSX.Element | null;
@@ -9,7 +9,7 @@ import { useAnimatedOpening } from './hooks';
9
9
  import styles from './styles.module.css';
10
10
  import { cloneCard } from './utils';
11
11
  const ANIMATION_DURATION = 0.3;
12
- export function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, }) {
12
+ export function NotificationCardStack({ children, defaultOpen, onOpenChanged, title, actions, unread, }) {
13
13
  const [actionsOpen, setActionsOpen] = useState(false);
14
14
  const { open, toggleOpen, isVisible } = useAnimatedOpening({
15
15
  defaultOpen,
@@ -23,6 +23,8 @@ export function NotificationCardStack({ children, defaultOpen, onOpenChanged, ti
23
23
  if (!stack.length) {
24
24
  return _jsx(_Fragment, { children: first });
25
25
  }
26
- const firstCardElement = open ? first : cloneCard(first, { onClick: toggleOpen });
26
+ const firstCardElement = open
27
+ ? first
28
+ : cloneCard(first, componentProps => (Object.assign(Object.assign({}, componentProps), { onClick: toggleOpen, unread: unread || componentProps.unread })));
27
29
  return (_jsxs("div", { className: styles.container, style: { '--snack-notification-stack-animation-duration': `${ANIMATION_DURATION}s` }, "data-test-id": TEST_IDS.cardStack.wrapper, children: [_jsxs("div", { className: styles.header, "data-test-id": TEST_IDS.cardStack.headline, children: [_jsx("label", { className: styles.title, onClick: toggleOpen, "data-test-id": TEST_IDS.cardStack.title, children: title }), _jsxs("div", { className: styles.right, children: [actions && actions.length > 0 && (_jsx(ActionsButton, { actions: actions, open: actionsOpen, setDroplistOpen: setActionsOpen, size: 'xs', icon: _jsx(KebabSVG, {}) })), _jsx(ButtonFunction, { size: 'xs', onClick: toggleOpen, "data-test-id": TEST_IDS.cardStack.openButton, icon: open ? _jsx(ChevronUpSVG, {}) : _jsx(ChevronDownSVG, {}) })] })] }), _jsxs("div", { className: styles.cards, children: [_jsxs("div", { className: styles.first, children: [_jsx(StackTail, { open: open, count: stack.length }), firstCardElement] }), _jsx("div", { className: styles.stack, "data-open": open || undefined, "data-tail-size": stack.length, children: _jsx("div", { className: styles.animationContainer, children: isVisible ? stack : [] }) })] })] }));
28
30
  }
@@ -1,2 +1,4 @@
1
1
  import { ReactNode } from 'react';
2
- export declare const cloneCard: (element: ReactNode, props: Record<string, unknown>) => string | number | boolean | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined;
2
+ type ComponentProps = Record<string, unknown>;
3
+ export declare const cloneCard: (element: ReactNode, props: (componentProps: ComponentProps) => ComponentProps) => string | number | boolean | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined;
4
+ export {};
@@ -3,5 +3,5 @@ export const cloneCard = (element, props) => {
3
3
  if (!isValidElement(element)) {
4
4
  return element;
5
5
  }
6
- return cloneElement(element, Object.assign(Object.assign({}, element.props), props));
6
+ return cloneElement(element, props(element.props));
7
7
  };
@@ -9,7 +9,7 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import cn from 'classnames';
14
14
  import { InfoBlock } from '@snack-uikit/info-block';
15
15
  import styles from './styles.module.css';
@@ -17,5 +17,5 @@ import styles from './styles.module.css';
17
17
  export function NotificationPanelBlank(_a) {
18
18
  var _b;
19
19
  var { icon, className } = _a, props = __rest(_a, ["icon", "className"]);
20
- return (_jsx(_Fragment, { children: _jsx(InfoBlock, Object.assign({}, props, { icon: icon ? Object.assign(Object.assign({}, icon), { appearance: (_b = icon.appearance) !== null && _b !== void 0 ? _b : 'neutral' }) : undefined, size: 'l', align: 'vertical', className: cn(styles.notificationPanelBlank, className) })) }));
20
+ return (_jsx("div", { className: styles.wrapper, children: _jsx(InfoBlock, Object.assign({}, props, { icon: icon ? Object.assign(Object.assign({}, icon), { appearance: (_b = icon.appearance) !== null && _b !== void 0 ? _b : 'neutral' }) : undefined, size: 'l', align: 'vertical', className: cn(styles.notificationPanelBlank, className) })) }));
21
21
  }
@@ -1,3 +1,10 @@
1
+ .wrapper{
2
+ display:flex;
3
+ align-items:center;
4
+ justify-content:center;
5
+ min-height:316px;
6
+ }
7
+
1
8
  .notificationPanelBlank{
2
9
  padding:var(--space-notification-panel-body-info-padding, 24px);
3
10
  }
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from 'react';
2
+ import { WithSupportProps } from '@snack-uikit/utils';
3
+ export type NotificationPanelGroupProps = WithSupportProps<{
4
+ /** Заголовок группы */
5
+ title: string;
6
+ /** Содержимое группы */
7
+ children: ReactNode;
8
+ /** CSS-класс */
9
+ className?: string;
10
+ }>;
11
+ /** Группа уведомлений с заголовком внутри панели */
12
+ export declare function NotificationPanelGroup({ title, children, className, ...rest }: NotificationPanelGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import { TruncateString } from '@snack-uikit/truncate-string';
15
+ import { Typography } from '@snack-uikit/typography';
16
+ import { extractSupportProps } from '@snack-uikit/utils';
17
+ import styles from './styles.module.css';
18
+ /** Группа уведомлений с заголовком внутри панели */
19
+ export function NotificationPanelGroup(_a) {
20
+ var { title, children, className } = _a, rest = __rest(_a, ["title", "children", "className"]);
21
+ return (_jsxs("div", Object.assign({ className: cn(styles.root, className) }, extractSupportProps(rest), { children: [_jsx("div", { className: styles.title, children: _jsx(Typography.LightLabelS, { className: styles.text, tag: 'span', children: _jsx(TruncateString, { text: title }) }) }), _jsx("div", { className: styles.content, children: children })] })));
22
+ }
@@ -0,0 +1 @@
1
+ export { NotificationPanelGroup, type NotificationPanelGroupProps } from './NotificationPanelGroup';
@@ -0,0 +1 @@
1
+ export { NotificationPanelGroup } from './NotificationPanelGroup';
@@ -0,0 +1,34 @@
1
+ .root{
2
+ position:relative;
3
+ display:flex;
4
+ flex-direction:column;
5
+ gap:var(--space-notification-panel-container-body-gap, 16px);
6
+ }
7
+
8
+ .title{
9
+ position:sticky;
10
+ z-index:1;
11
+ top:4px;
12
+ display:flex;
13
+ justify-content:center;
14
+ }
15
+
16
+ .text{
17
+ padding-left:var(--space-notification-panel-date-horizontal-padding, 8px);
18
+ padding-right:var(--space-notification-panel-date-horizontal-padding, 8px);
19
+ height:var(--size-notification-panel-date-height, 20px);
20
+ border-radius:var(--radius-chips-m, 20px);
21
+ display:inline-flex;
22
+ align-items:center;
23
+ justify-content:center;
24
+ color:var(--sys-neutral-text-disabled, #aaaebd);
25
+ background-color:var(--sys-neutral-background1-level, #fdfdfd);
26
+ border-color:var(--sys-neutral-decor-default, #dde0ea);
27
+ border-style:solid;
28
+ }
29
+
30
+ .content{
31
+ display:flex;
32
+ flex-direction:column;
33
+ gap:var(--space-notification-panel-container-body-gap, 16px);
34
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './NotificationPanelSettings';
2
2
  export * from './NotificationPanelBlank';
3
3
  export * from './NotificationCardStack';
4
+ export * from './NotificationPanelGroup';
@@ -1,3 +1,4 @@
1
1
  export * from './NotificationPanelSettings';
2
2
  export * from './NotificationPanelBlank';
3
3
  export * from './NotificationCardStack';
4
+ export * from './NotificationPanelGroup';
@@ -101,4 +101,12 @@
101
101
  -moz-column-gap:var(--dimension-1m, 8px);
102
102
  column-gap:var(--dimension-1m, 8px);
103
103
  align-items:center;
104
+ }
105
+
106
+ .actionsRow{
107
+ display:flex;
108
+ -moz-column-gap:var(--dimension-1m, 8px);
109
+ column-gap:var(--dimension-1m, 8px);
110
+ align-items:center;
111
+ justify-content:space-between;
104
112
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Notification",
7
- "version": "0.13.30",
7
+ "version": "0.13.31-preview-7cd3a502.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -37,6 +37,7 @@
37
37
  "scripts": {},
38
38
  "dependencies": {
39
39
  "@snack-uikit/button": "0.19.17",
40
+ "@snack-uikit/chips": "0.28.17",
40
41
  "@snack-uikit/icons": "0.27.7",
41
42
  "@snack-uikit/info-block": "0.6.39",
42
43
  "@snack-uikit/link": "0.17.19",
@@ -45,6 +46,7 @@
45
46
  "@snack-uikit/scroll": "0.10.8",
46
47
  "@snack-uikit/segmented-control": "0.6.19",
47
48
  "@snack-uikit/skeleton": "0.6.10",
49
+ "@snack-uikit/status": "0.10.11",
48
50
  "@snack-uikit/tag": "0.15.17",
49
51
  "@snack-uikit/tooltip": "0.18.11",
50
52
  "@snack-uikit/truncate-string": "0.7.10",
@@ -53,5 +55,5 @@
53
55
  "classnames": "2.5.1",
54
56
  "uncontrollable": "9.0.0"
55
57
  },
56
- "gitHead": "d1bb005216777d5730f95394b5f1b823feb694df"
58
+ "gitHead": "32e4b2dbb52e9da27a48b5bee0e238a0ab785b1d"
57
59
  }
@@ -4,6 +4,7 @@ import { ElementType, MouseEventHandler, ReactNode, useEffect, useMemo, useRef,
4
4
  import { ButtonSimple, ButtonSimpleProps, ButtonTonal, ButtonTonalProps } from '@snack-uikit/button';
5
5
  import { KebabSVG } from '@snack-uikit/icons';
6
6
  import { Link, PickLinkProps } from '@snack-uikit/link';
7
+ import { StatusIndicator } from '@snack-uikit/status';
7
8
  import { TruncateString } from '@snack-uikit/truncate-string';
8
9
  import { Typography } from '@snack-uikit/typography';
9
10
  import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
@@ -180,6 +181,8 @@ export function NotificationCard<LinkElement extends ElementType = 'a'>({
180
181
  )}
181
182
  </div>
182
183
  )}
184
+
185
+ {unread && <StatusIndicator className={styles.statusIndicator} size='xs' appearance='primary' />}
183
186
  </div>
184
187
  );
185
188
  }
@@ -29,6 +29,12 @@
29
29
  box-sizing: border-box;
30
30
  }
31
31
 
32
+ .statusIndicator {
33
+ position: absolute;
34
+ top: 4px;
35
+ right: 4px;
36
+ }
37
+
32
38
  .notificationCard {
33
39
  @include styles-tokens-notification-notificationCard.composite-var(styles-tokens-notification-notificationCard.$notification-card-container);
34
40
 
@@ -2,6 +2,7 @@ import cn from 'classnames';
2
2
  import { MouseEventHandler, ReactNode, RefObject, useMemo } from 'react';
3
3
 
4
4
  import { ButtonFunction, ButtonFunctionProps } from '@snack-uikit/button';
5
+ import { ChipToggle } from '@snack-uikit/chips';
5
6
  import { Scroll } from '@snack-uikit/scroll';
6
7
  import { SegmentedControl, SegmentedControlProps } from '@snack-uikit/segmented-control';
7
8
  import { SkeletonContextProvider, WithSkeleton } from '@snack-uikit/skeleton';
@@ -16,6 +17,8 @@ import {
16
17
  NotificationCardStackProps,
17
18
  NotificationPanelBlank,
18
19
  NotificationPanelBlankProps,
20
+ NotificationPanelGroup,
21
+ NotificationPanelGroupProps,
19
22
  NotificationPanelSettings,
20
23
  NotificationPanelSettingsProps,
21
24
  } from './components';
@@ -33,6 +36,13 @@ export type NotificationPanelProps = WithSupportProps<{
33
36
  settings?: NotificationPanelSettingsProps;
34
37
  /** Сегменты для фильтрации */
35
38
  segments?: Omit<SegmentedControlProps, 'size' | 'data-test-id'>;
39
+ /** Переключатель для фильтрации */
40
+ chipToggle?: {
41
+ label: string;
42
+ checked: boolean;
43
+ defaultChecked?: boolean;
44
+ onChange: (checked: boolean) => void;
45
+ };
36
46
  /** Кнопка в "шапке" панели */
37
47
  readAllButton?: Omit<ButtonFunctionProps, 'data-test-id'> & {
38
48
  tooltip?: TooltipProps;
@@ -68,6 +78,11 @@ export function NotificationPanel({
68
78
  scrollEndRef,
69
79
  scrollContainerRef,
70
80
  className,
81
+ chipToggle = {
82
+ label: 'Label',
83
+ checked: false,
84
+ onChange: () => {},
85
+ },
71
86
  ...rest
72
87
  }: NotificationPanelProps) {
73
88
  const skeletons = useMemo(() => Array.from({ length: skeletonsAmount }, (_, i) => i), [skeletonsAmount]);
@@ -97,11 +112,11 @@ export function NotificationPanel({
97
112
  </div>
98
113
  </div>
99
114
 
100
- <div>
115
+ <div className={styles.actionsRow}>
101
116
  {segments && (
102
117
  <SegmentedControl
103
118
  {...segments}
104
- size='s'
119
+ size='xs'
105
120
  items={segments.items.map(item => ({
106
121
  ...item,
107
122
  disabled: item.disabled || loading,
@@ -109,6 +124,15 @@ export function NotificationPanel({
109
124
  data-test-id={TEST_IDS.segments}
110
125
  />
111
126
  )}
127
+ {chipToggle && (
128
+ <ChipToggle
129
+ size='xs'
130
+ disabled={loading}
131
+ label={chipToggle.label}
132
+ onChange={chipToggle.onChange}
133
+ checked={chipToggle.checked}
134
+ />
135
+ )}
112
136
  </div>
113
137
  </div>
114
138
 
@@ -148,4 +172,6 @@ export namespace NotificationPanel {
148
172
  export type DividerProps = NotificationPanelDividerProps;
149
173
  export const Stack: typeof NotificationCardStack = NotificationCardStack;
150
174
  export type StackProps = NotificationCardStackProps;
175
+ export const Group: typeof NotificationPanelGroup = NotificationPanelGroup;
176
+ export type GroupProps = NotificationPanelGroupProps;
151
177
  }
@@ -33,6 +33,10 @@ export type NotificationCardStackProps = {
33
33
  * Список действий в выпадающем меню
34
34
  */
35
35
  actions?: Action[];
36
+ /**
37
+ * Состояние непрочитанных карточек
38
+ */
39
+ unread?: boolean;
36
40
  };
37
41
 
38
42
  const ANIMATION_DURATION = 0.3;
@@ -43,6 +47,7 @@ export function NotificationCardStack({
43
47
  onOpenChanged,
44
48
  title,
45
49
  actions,
50
+ unread,
46
51
  }: NotificationCardStackProps) {
47
52
  const [actionsOpen, setActionsOpen] = useState(false);
48
53
  const { open, toggleOpen, isVisible } = useAnimatedOpening({
@@ -61,7 +66,13 @@ export function NotificationCardStack({
61
66
  return <>{first}</>;
62
67
  }
63
68
 
64
- const firstCardElement = open ? first : cloneCard(first, { onClick: toggleOpen });
69
+ const firstCardElement = open
70
+ ? first
71
+ : cloneCard(first, componentProps => ({
72
+ ...componentProps,
73
+ onClick: toggleOpen,
74
+ unread: unread || componentProps.unread,
75
+ }));
65
76
 
66
77
  return (
67
78
  <div
@@ -1,12 +1,11 @@
1
1
  import { cloneElement, isValidElement, ReactNode } from 'react';
2
2
 
3
- export const cloneCard = (element: ReactNode, props: Record<string, unknown>) => {
3
+ type ComponentProps = Record<string, unknown>;
4
+
5
+ export const cloneCard = (element: ReactNode, props: (componentProps: ComponentProps) => ComponentProps) => {
4
6
  if (!isValidElement(element)) {
5
7
  return element;
6
8
  }
7
9
 
8
- return cloneElement(element, {
9
- ...element.props,
10
- ...props,
11
- });
10
+ return cloneElement(element, props(element.props));
12
11
  };
@@ -9,7 +9,7 @@ export type NotificationPanelBlankProps = Omit<InfoBlockProps, 'footer' | 'align
9
9
  /** Компонента для "заглушки" вместо карточек в панели */
10
10
  export function NotificationPanelBlank({ icon, className, ...props }: NotificationPanelBlankProps) {
11
11
  return (
12
- <>
12
+ <div className={styles.wrapper}>
13
13
  <InfoBlock
14
14
  {...props}
15
15
  icon={icon ? { ...icon, appearance: icon.appearance ?? 'neutral' } : undefined}
@@ -17,6 +17,6 @@ export function NotificationPanelBlank({ icon, className, ...props }: Notificati
17
17
  align='vertical'
18
18
  className={cn(styles.notificationPanelBlank, className)}
19
19
  />
20
- </>
20
+ </div>
21
21
  );
22
22
  }
@@ -1,5 +1,12 @@
1
1
  @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-notification-notificationPanel';
2
2
 
3
+ .wrapper {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ min-height: 316px; // two skeleton cards height
8
+ }
9
+
3
10
  .notificationPanelBlank {
4
11
  @include styles-tokens-notification-notificationPanel.composite-var(styles-tokens-notification-notificationPanel.$notification-panel-body-info-container);
5
12
  }
@@ -0,0 +1,31 @@
1
+ import cn from 'classnames';
2
+ import { ReactNode } from 'react';
3
+
4
+ import { TruncateString } from '@snack-uikit/truncate-string';
5
+ import { Typography } from '@snack-uikit/typography';
6
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
7
+
8
+ import styles from './styles.module.scss';
9
+
10
+ export type NotificationPanelGroupProps = WithSupportProps<{
11
+ /** Заголовок группы */
12
+ title: string;
13
+ /** Содержимое группы */
14
+ children: ReactNode;
15
+ /** CSS-класс */
16
+ className?: string;
17
+ }>;
18
+
19
+ /** Группа уведомлений с заголовком внутри панели */
20
+ export function NotificationPanelGroup({ title, children, className, ...rest }: NotificationPanelGroupProps) {
21
+ return (
22
+ <div className={cn(styles.root, className)} {...extractSupportProps(rest)}>
23
+ <div className={styles.title}>
24
+ <Typography.LightLabelS className={styles.text} tag='span'>
25
+ <TruncateString text={title} />
26
+ </Typography.LightLabelS>
27
+ </div>
28
+ <div className={styles.content}>{children}</div>
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1 @@
1
+ export { NotificationPanelGroup, type NotificationPanelGroupProps } from './NotificationPanelGroup';
@@ -0,0 +1,39 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-notification-notificationPanel' as panel;
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element' as element;
3
+
4
+ .root {
5
+ position: relative;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: element.$space-notification-panel-container-body-gap;
9
+ }
10
+
11
+ .title {
12
+ position: sticky;
13
+ z-index: 1; /* stylelint-disable-line declaration-property-value-allowed-list */
14
+ top: 4px;
15
+
16
+ display: flex;
17
+ justify-content: center;
18
+
19
+ }
20
+
21
+ .text {
22
+ @include element.composite-var(panel.$notification-panel-date-container);
23
+
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+
28
+ color: panel.$sys-neutral-text-disabled;
29
+
30
+ background-color: panel.$sys-neutral-background1-level;
31
+ border-color: panel.$sys-neutral-decor-default;
32
+ border-style: solid;
33
+ }
34
+
35
+ .content {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: element.$space-notification-panel-container-body-gap;
39
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './NotificationPanelSettings';
2
2
  export * from './NotificationPanelBlank';
3
3
  export * from './NotificationCardStack';
4
+ export * from './NotificationPanelGroup';
@@ -106,4 +106,11 @@
106
106
  display: flex;
107
107
  column-gap: styles-tokens-notification-notificationPanel.$dimension-1m;
108
108
  align-items: center;
109
- }
109
+ }
110
+
111
+ .actionsRow {
112
+ display: flex;
113
+ column-gap: styles-tokens-notification-notificationPanel.$dimension-1m;
114
+ align-items: center;
115
+ justify-content: space-between;
116
+ }