@cloud-ru/ds-uikit-product-modal-predefined 1.0.23-preview-a0540d7f.0 → 1.0.24

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 (30) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cjs/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +3 -1
  3. package/dist/cjs/helperComponents/DesktopReleaseNotes/styles.module.css +4 -0
  4. package/dist/cjs/helperComponents/NoteItem/NoteItem.js +1 -1
  5. package/dist/cjs/helperComponents/NoteItem/NoteItemSkeleton.js +2 -1
  6. package/dist/cjs/helperComponents/NoteItem/styles.module.css +10 -2
  7. package/dist/cjs/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +6 -2
  8. package/dist/cjs/helperComponents/ReleaseNotesContent/styles.module.css +1 -1
  9. package/dist/cjs/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +1 -2
  10. package/dist/cjs/style.css +15 -3
  11. package/dist/esm/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +3 -1
  12. package/dist/esm/helperComponents/DesktopReleaseNotes/styles.module.css +4 -0
  13. package/dist/esm/helperComponents/NoteItem/NoteItem.js +2 -2
  14. package/dist/esm/helperComponents/NoteItem/NoteItemSkeleton.js +2 -1
  15. package/dist/esm/helperComponents/NoteItem/styles.module.css +10 -2
  16. package/dist/esm/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +6 -2
  17. package/dist/esm/helperComponents/ReleaseNotesContent/styles.module.css +1 -1
  18. package/dist/esm/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +1 -2
  19. package/dist/esm/style.css +15 -3
  20. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  21. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  22. package/package.json +19 -19
  23. package/src/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.tsx +4 -1
  24. package/src/helperComponents/DesktopReleaseNotes/styles.module.scss +5 -0
  25. package/src/helperComponents/NoteItem/NoteItem.tsx +8 -6
  26. package/src/helperComponents/NoteItem/NoteItemSkeleton.tsx +3 -2
  27. package/src/helperComponents/NoteItem/styles.module.scss +15 -4
  28. package/src/helperComponents/ReleaseNotesContent/ReleaseNotesContent.tsx +12 -2
  29. package/src/helperComponents/ReleaseNotesContent/styles.module.scss +2 -1
  30. package/src/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.tsx +3 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 1.0.24 (2026-09-14)
7
+
8
+ **Note:** Version bump only for package @ds/uikit-product-modal-predefined
9
+
10
+ ## 1.0.23 (2026-09-14)
11
+
12
+ ### Bug Fixes
13
+
14
+ - **FF-8945:** reserve scrollbar gutter in ReleaseNotes modal content ([76adb63](https://github.com/cloud-ru-tech/snack-v2/commit/76adb638a3c5323e4c8b50568a7341aa6a6a7be6))
15
+
6
16
  ## 1.0.22 (2026-09-11)
7
17
 
8
18
  **Note:** Version bump only for package @ds/uikit-product-modal-predefined
@@ -17,5 +17,7 @@ function DesktopReleaseNotes({ open, onClose, closeOnPopstate, contentState = co
17
17
  const { t } = locale_1.modalPredefinedLocale.useTranslations();
18
18
  const { pageIndex, readablePageNumber, setPage, handleClose, handleReadLaterClick, handleNextPageClick, handlePrevPageClick, } = (0, hooks_1.useReleaseNotesNavigation)({ onClose, onReadLaterClick, onSlideChange });
19
19
  const shouldShowFooter = contentState === constants_1.CONTENT_STATE.Data && (loading || items.length > 0);
20
- return ((0, jsx_runtime_1.jsxs)(modal_1.ModalCustom, { open: open, onClose: handleClose, closeOnPopstate: closeOnPopstate, width: modal_1.WIDTH.M, "data-content-state": contentState, "data-surface": 'modal', ...(0, utils_1.extractSupportProps)(rest), children: [(0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Header, { title: t('releaseNotes.title') }), (0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Body, { className: styles_module_scss_1.default.body, content: (0, jsx_runtime_1.jsx)(ReleaseNotesContent_1.ReleaseNotesContent, { contentState: contentState, items: items, loading: loading, pageIndex: pageIndex, surface: 'modal', noDataTitle: t('releaseNotes.noDataTitle'), noDataDescription: t('releaseNotes.noDataDescription'), errorTitle: t('releaseNotes.errorTitle'), errorDescription: t('releaseNotes.errorDescription'), retryLabel: t('releaseNotes.retry'), onPageChange: setPage, onDataErrorRetryClick: onDataErrorRetryClick }) }), shouldShowFooter && ((0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Footer, { children: (0, jsx_runtime_1.jsx)(ReleaseNotesFooter_1.ReleaseNotesFooter, { surface: 'modal', loading: loading, total: items.length, pageIndex: pageIndex, readablePageNumber: readablePageNumber, readLaterLabel: t('releaseNotes.readLater'), onPageChange: setPage, onReadLaterClick: handleReadLaterClick, onPrevPageClick: handlePrevPageClick, onNextPageClick: () => handleNextPageClick(items.length) }) }))] }));
20
+ return ((0, jsx_runtime_1.jsxs)(modal_1.ModalCustom, { open: open, onClose: handleClose, closeOnPopstate: closeOnPopstate, width: modal_1.WIDTH.M, "data-content-state": contentState, "data-surface": 'modal', ...(0, utils_1.extractSupportProps)(rest), children: [(0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Header, { title: t('releaseNotes.title') }), (0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Body, { className: styles_module_scss_1.default.body, content: (0, jsx_runtime_1.jsx)(ReleaseNotesContent_1.ReleaseNotesContent, { contentState: contentState, items: items, loading: loading, pageIndex: pageIndex, surface: 'modal', noDataTitle: t('releaseNotes.noDataTitle'), noDataDescription: t('releaseNotes.noDataDescription'), errorTitle: t('releaseNotes.errorTitle'), errorDescription: t('releaseNotes.errorDescription'), retryLabel: t('releaseNotes.retry'), onPageChange: setPage, onDataErrorRetryClick: onDataErrorRetryClick }) }), shouldShowFooter ? ((0, jsx_runtime_1.jsx)(modal_1.ModalCustom.Footer, { children: (0, jsx_runtime_1.jsx)(ReleaseNotesFooter_1.ReleaseNotesFooter, { surface: 'modal', loading: loading, total: items.length, pageIndex: pageIndex, readablePageNumber: readablePageNumber, readLaterLabel: t('releaseNotes.readLater'), onPageChange: setPage, onReadLaterClick: handleReadLaterClick, onPrevPageClick: handlePrevPageClick, onNextPageClick: () => handleNextPageClick(items.length) }) })) : (
21
+ // Нижнюю safe-area окна несёт футер; без него отступ рисуется здесь, как в `DesktopModal`.
22
+ (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.safeAreaBottom }))] }));
21
23
  }
@@ -1,3 +1,7 @@
1
1
  .body{
2
2
  min-height:calc(6 * var(--sn-primitive-dimension-64, 64px));
3
+ }
4
+
5
+ .safeAreaBottom{
6
+ padding-bottom:var(--sn-modal-anatomy-container-safeArea-paddingBottom, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
3
7
  }
@@ -42,5 +42,5 @@ function NoteItemMedia({ image, video }) {
42
42
  status !== constants_1.MEDIA_STATUS.Error && ((0, jsx_runtime_1.jsx)("img", { ref: handleImageRef, className: styles_module_scss_1.default.file, src: image.src, alt: image.alt, onLoad: handleImageLoad, onError: handleImageError, "data-test-id": constants_1.TEST_IDS.releaseNotesImage }))), status === constants_1.MEDIA_STATUS.Pending && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.mediaOverlay, "aria-hidden": true, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { loading: true, width: '100%', height: '100%' }) })), status === constants_1.MEDIA_STATUS.Error && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.mediaOverlay, role: 'img', "aria-label": image.alt, "data-test-id": constants_1.TEST_IDS.releaseNotesMediaFallback, children: (0, jsx_runtime_1.jsx)(system_1.ImageSVG, { className: styles_module_scss_1.default.mediaFallbackIcon, size: FALLBACK_ICON_SIZE }) }))] }));
43
43
  }
44
44
  function NoteItem({ title, description, image, video, surface = constants_1.SURFACE.Modal }) {
45
- return ((0, jsx_runtime_1.jsxs)("article", { className: styles_module_scss_1.default.root, "data-surface": surface, "data-test-id": constants_1.TEST_IDS.releaseNotesItem, children: [(0, jsx_runtime_1.jsx)(NoteItemMedia, { image: image, video: video }, `${video ?? ''}|${image.src}`), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.content, children: [(0, jsx_runtime_1.jsx)(typography_1.Typography, { className: styles_module_scss_1.default.title, variant: 'title', size: 'l', children: title }), (0, jsx_runtime_1.jsx)(scroll_1.Scroll, { className: styles_module_scss_1.default.description, barHideStrategy: scroll_1.BAR_HIDE_STRATEGY.Leave, children: (0, jsx_runtime_1.jsx)(markdown_1.Markdown, { value: description }) })] })] }));
45
+ return ((0, jsx_runtime_1.jsxs)("article", { className: styles_module_scss_1.default.root, "data-surface": surface, "data-test-id": constants_1.TEST_IDS.releaseNotesItem, children: [(0, jsx_runtime_1.jsx)(NoteItemMedia, { image: image, video: video }, `${video ?? ''}|${image.src}`), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.content, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.titleWrapper, children: (0, jsx_runtime_1.jsx)(typography_1.Typography, { className: styles_module_scss_1.default.title, variant: 'title', size: 'l', children: title }) }), (0, jsx_runtime_1.jsx)(scroll_1.Scroll, { className: styles_module_scss_1.default.description, size: scroll_1.SIZE.S, barHideStrategy: scroll_1.BAR_HIDE_STRATEGY.Leave, children: (0, jsx_runtime_1.jsx)(markdown_1.Markdown, { value: description, className: styles_module_scss_1.default.markdown }) })] })] }));
46
46
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.NoteItemSkeleton = NoteItemSkeleton;
7
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const skeleton_1 = require("@cloud-ru/ds-skeleton");
9
+ const classnames_1 = __importDefault(require("classnames"));
9
10
  const constants_1 = require("../../constants");
10
11
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
11
12
  /** Строки скелетона текста: заголовок и абзацы описания — по структуре типовой новости. */
@@ -16,5 +17,5 @@ const PARAGRAPH_LINES = [2, 3, 3];
16
17
  * поэтому переход `loading → data` не меняет геометрию.
17
18
  */
18
19
  function NoteItemSkeleton({ surface = constants_1.SURFACE.Modal }) {
19
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.root, "data-surface": surface, "aria-hidden": true, "data-test-id": constants_1.TEST_IDS.releaseNotesSkeleton, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.media, "data-test-id": constants_1.TEST_IDS.releaseNotesMedia, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { loading: true, width: '100%', height: '100%' }) }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.content, children: [(0, jsx_runtime_1.jsx)(skeleton_1.SkeletonText, { loading: true, lines: TITLE_LINES, variant: 'headline', size: 's' }), PARAGRAPH_LINES.map((lines, index) => ((0, jsx_runtime_1.jsx)(skeleton_1.SkeletonText, { loading: true, lines: lines }, index)))] })] }));
20
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.root, "data-surface": surface, "aria-hidden": true, "data-test-id": constants_1.TEST_IDS.releaseNotesSkeleton, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.media, "data-test-id": constants_1.TEST_IDS.releaseNotesMedia, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { loading: true, width: '100%', height: '100%' }) }), (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.content, styles_module_scss_1.default.skeletonContent), children: [(0, jsx_runtime_1.jsx)(skeleton_1.SkeletonText, { loading: true, lines: TITLE_LINES, variant: 'title', size: 'l' }), PARAGRAPH_LINES.map((lines, index) => ((0, jsx_runtime_1.jsx)(skeleton_1.SkeletonText, { loading: true, lines: lines }, index)))] })] }));
20
21
  }
@@ -23,7 +23,10 @@
23
23
  }
24
24
 
25
25
  .root[data-surface=bottomSheet] .media{
26
+ justify-self:center;
26
27
  aspect-ratio:1;
28
+ width:100%;
29
+ max-width:calc(6 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
27
30
  }
28
31
 
29
32
  .file{
@@ -49,7 +52,7 @@
49
52
  .content{
50
53
  display:flex;
51
54
  flex-direction:column;
52
- row-gap:var(--sn-primitive-dimension-12, 12px);
55
+ row-gap:var(--sn-primitive-dimension-16, 16px);
53
56
  min-width:0;
54
57
  min-height:0;
55
58
  }
@@ -64,10 +67,15 @@
64
67
  }
65
68
 
66
69
  .root[data-surface=modal] .content{
67
- row-gap:var(--sn-primitive-dimension-16, 16px);
68
70
  order:1;
69
71
  }
70
72
 
73
+ .root[data-surface=modal] .markdown,
74
+ .root[data-surface=modal] .titleWrapper,
75
+ .root[data-surface=modal] .skeletonContent{
76
+ padding-right:var(--sn-primitive-dimension-16, 16px);
77
+ }
78
+
71
79
  .root[data-surface=modal] .media{
72
80
  order:2;
73
81
  }
@@ -9,10 +9,14 @@ const button_1 = require("@cloud-ru/ds-button");
9
9
  const carousel_1 = require("@cloud-ru/ds-carousel");
10
10
  const system_1 = require("@cloud-ru/ds-icons/interface/system");
11
11
  const info_block_1 = require("@cloud-ru/ds-info-block");
12
+ const typography_1 = require("@cloud-ru/ds-typography");
12
13
  const classnames_1 = __importDefault(require("classnames"));
13
14
  const constants_1 = require("../../constants");
14
15
  const NoteItem_1 = require("../NoteItem");
15
16
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
17
+ function InfoBlockDescription({ children }) {
18
+ return ((0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: 'body', size: 'm', children: children }));
19
+ }
16
20
  function ReleaseNotesContent({ contentState, items, loading, pageIndex, surface, noDataTitle, noDataDescription, errorTitle, errorDescription, retryLabel, onPageChange, onDataErrorRetryClick, }) {
17
21
  const total = items.length;
18
22
  const hasItems = total > 0;
@@ -20,10 +24,10 @@ function ReleaseNotesContent({ contentState, items, loading, pageIndex, surface,
20
24
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.root, styles_module_scss_1.default[surface]), children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.skeletonSlide, children: (0, jsx_runtime_1.jsx)(NoteItem_1.NoteItemSkeleton, { surface: surface }) }) }));
21
25
  }
22
26
  if (contentState === constants_1.CONTENT_STATE.Error) {
23
- return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.stateWrapper, "data-surface": surface, children: (0, jsx_runtime_1.jsx)(info_block_1.InfoBlock, { className: styles_module_scss_1.default.state, size: 'l', title: errorTitle, content: errorDescription, icon: { icon: system_1.CrossSVG, appearance: 'neutral', background: true }, "data-test-id": constants_1.TEST_IDS.releaseNotesError, footer: (0, jsx_runtime_1.jsx)(button_1.Button, { view: 'filled', appearance: 'neutral', size: 'l', label: retryLabel, onClick: onDataErrorRetryClick, "data-test-id": constants_1.TEST_IDS.releaseNotesRetryButton }) }) }));
27
+ return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.stateWrapper, "data-surface": surface, children: (0, jsx_runtime_1.jsx)(info_block_1.InfoBlock, { className: styles_module_scss_1.default.state, size: 'l', title: errorTitle, content: (0, jsx_runtime_1.jsx)(InfoBlockDescription, { children: errorDescription }), icon: { icon: system_1.CrossSVG, appearance: 'neutral', background: true }, "data-test-id": constants_1.TEST_IDS.releaseNotesError, footer: (0, jsx_runtime_1.jsx)(button_1.Button, { view: 'filled', appearance: 'neutral', size: 'l', label: retryLabel, onClick: onDataErrorRetryClick, "data-test-id": constants_1.TEST_IDS.releaseNotesRetryButton }) }) }));
24
28
  }
25
29
  if (contentState === constants_1.CONTENT_STATE.NoData || !hasItems) {
26
- return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.stateWrapper, "data-surface": surface, children: (0, jsx_runtime_1.jsx)(info_block_1.InfoBlock, { className: styles_module_scss_1.default.state, size: 'l', title: noDataTitle, content: noDataDescription, "data-test-id": constants_1.TEST_IDS.releaseNotesNoData }) }));
30
+ return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.stateWrapper, "data-surface": surface, children: (0, jsx_runtime_1.jsx)(info_block_1.InfoBlock, { className: styles_module_scss_1.default.state, size: 'l', title: noDataTitle, content: (0, jsx_runtime_1.jsx)(InfoBlockDescription, { children: noDataDescription }), "data-test-id": constants_1.TEST_IDS.releaseNotesNoData }) }));
27
31
  }
28
32
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.root, styles_module_scss_1.default[surface]), children: (0, jsx_runtime_1.jsx)(carousel_1.Carousel, { arrows: false, pagination: false, swipe: true, state: { page: pageIndex, onChange: onPageChange }, className: styles_module_scss_1.default.carousel, children: items.map(item => ((0, jsx_runtime_1.jsx)(NoteItem_1.NoteItem, { ...item, surface: surface }, `${item.title}-${item.image.src}`))) }) }));
29
33
  }
@@ -35,7 +35,7 @@
35
35
  height:calc(7 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
36
36
  }
37
37
  .stateWrapper[data-surface=bottomSheet]{
38
- min-height:calc(7 * var(--sn-primitive-dimension-64, 64px));
38
+ min-height:100%;
39
39
  }
40
40
 
41
41
  .state{
@@ -16,7 +16,6 @@ function ReleaseNotesFooter({ surface, total, loading, pageIndex, readablePageNu
16
16
  // появляются рывком поверх уже отрисованного контента.
17
17
  const hasPagination = loading || total > 1;
18
18
  const isModal = surface === constants_1.SURFACE.Modal;
19
- const buttonSize = isModal ? 'm' : 'l';
20
19
  const handleSliderChange = (page) => onPageChange(page - 1);
21
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.root, "data-surface": surface, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { view: 'function', appearance: 'neutral', size: buttonSize, label: readLaterLabel, "data-test-id": constants_1.TEST_IDS.releaseNotesReadLaterButton, ...readLaterButtonProps, onClick: onReadLaterClick }), isModal && hasPagination && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.slider, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { loading: loading, className: styles_module_scss_1.default.sliderSkeleton, children: (0, jsx_runtime_1.jsx)(pagination_1.PaginationSlider, { size: pagination_1.PAGINATION_SLIDER_SIZE.Xs, total: total, page: readablePageNumber, onChange: handleSliderChange, "data-test-id": constants_1.TEST_IDS.releaseNotesPaginationSlider }) }) })), hasPagination && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.controls, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { view: 'outline', appearance: 'neutral', size: buttonSize, icon: (0, jsx_runtime_1.jsx)(system_1.ChevronLeftSVG, {}), disabled: loading || pageIndex === 0, onClick: onPrevPageClick, "data-test-id": constants_1.TEST_IDS.releaseNotesPrevButton }), (0, jsx_runtime_1.jsx)(button_1.Button, { view: 'outline', appearance: 'neutral', size: buttonSize, icon: (0, jsx_runtime_1.jsx)(system_1.ChevronRightSVG, {}), disabled: loading || readablePageNumber === total, onClick: onNextPageClick, "data-test-id": constants_1.TEST_IDS.releaseNotesNextButton })] }))] }));
20
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.root, "data-surface": surface, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { view: 'function', appearance: 'neutral', size: 'm', label: readLaterLabel, "data-test-id": constants_1.TEST_IDS.releaseNotesReadLaterButton, ...readLaterButtonProps, onClick: onReadLaterClick }), isModal && hasPagination && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.slider, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { loading: loading, className: styles_module_scss_1.default.sliderSkeleton, children: (0, jsx_runtime_1.jsx)(pagination_1.PaginationSlider, { size: pagination_1.PAGINATION_SLIDER_SIZE.Xs, total: total, page: readablePageNumber, onChange: handleSliderChange, "data-test-id": constants_1.TEST_IDS.releaseNotesPaginationSlider }) }) })), hasPagination && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.controls, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { view: 'outline', appearance: 'neutral', size: 'm', icon: (0, jsx_runtime_1.jsx)(system_1.ChevronLeftSVG, {}), disabled: loading || pageIndex === 0, onClick: onPrevPageClick, "data-test-id": constants_1.TEST_IDS.releaseNotesPrevButton }), (0, jsx_runtime_1.jsx)(button_1.Button, { view: 'outline', appearance: 'neutral', size: 'm', icon: (0, jsx_runtime_1.jsx)(system_1.ChevronRightSVG, {}), disabled: loading || readablePageNumber === total, onClick: onNextPageClick, "data-test-id": constants_1.TEST_IDS.releaseNotesNextButton })] }))] }));
22
21
  }
@@ -35,6 +35,10 @@
35
35
  .body{
36
36
  min-height:calc(6 * var(--sn-primitive-dimension-64, 64px));
37
37
  }
38
+
39
+ .safeAreaBottom{
40
+ padding-bottom:var(--sn-modal-anatomy-container-safeArea-paddingBottom, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
41
+ }
38
42
  .root{
39
43
  display:flex;
40
44
  flex-direction:column;
@@ -99,7 +103,10 @@
99
103
  }
100
104
 
101
105
  .root[data-surface=bottomSheet] .media{
106
+ justify-self:center;
102
107
  aspect-ratio:1;
108
+ width:100%;
109
+ max-width:calc(6 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
103
110
  }
104
111
 
105
112
  .file{
@@ -125,7 +132,7 @@
125
132
  .content{
126
133
  display:flex;
127
134
  flex-direction:column;
128
- row-gap:var(--sn-primitive-dimension-12, 12px);
135
+ row-gap:var(--sn-primitive-dimension-16, 16px);
129
136
  min-width:0;
130
137
  min-height:0;
131
138
  }
@@ -140,10 +147,15 @@
140
147
  }
141
148
 
142
149
  .root[data-surface=modal] .content{
143
- row-gap:var(--sn-primitive-dimension-16, 16px);
144
150
  order:1;
145
151
  }
146
152
 
153
+ .root[data-surface=modal] .markdown,
154
+ .root[data-surface=modal] .titleWrapper,
155
+ .root[data-surface=modal] .skeletonContent{
156
+ padding-right:var(--sn-primitive-dimension-16, 16px);
157
+ }
158
+
147
159
  .root[data-surface=modal] .media{
148
160
  order:2;
149
161
  }
@@ -184,7 +196,7 @@
184
196
  height:calc(7 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
185
197
  }
186
198
  .stateWrapper[data-surface=bottomSheet]{
187
- min-height:calc(7 * var(--sn-primitive-dimension-64, 64px));
199
+ min-height:100%;
188
200
  }
189
201
 
190
202
  .state{
@@ -11,5 +11,7 @@ export function DesktopReleaseNotes({ open, onClose, closeOnPopstate, contentSta
11
11
  const { t } = modalPredefinedLocale.useTranslations();
12
12
  const { pageIndex, readablePageNumber, setPage, handleClose, handleReadLaterClick, handleNextPageClick, handlePrevPageClick, } = useReleaseNotesNavigation({ onClose, onReadLaterClick, onSlideChange });
13
13
  const shouldShowFooter = contentState === CONTENT_STATE.Data && (loading || items.length > 0);
14
- return (_jsxs(ModalCustom, { open: open, onClose: handleClose, closeOnPopstate: closeOnPopstate, width: WIDTH.M, "data-content-state": contentState, "data-surface": 'modal', ...extractSupportProps(rest), children: [_jsx(ModalCustom.Header, { title: t('releaseNotes.title') }), _jsx(ModalCustom.Body, { className: styles.body, content: _jsx(ReleaseNotesContent, { contentState: contentState, items: items, loading: loading, pageIndex: pageIndex, surface: 'modal', noDataTitle: t('releaseNotes.noDataTitle'), noDataDescription: t('releaseNotes.noDataDescription'), errorTitle: t('releaseNotes.errorTitle'), errorDescription: t('releaseNotes.errorDescription'), retryLabel: t('releaseNotes.retry'), onPageChange: setPage, onDataErrorRetryClick: onDataErrorRetryClick }) }), shouldShowFooter && (_jsx(ModalCustom.Footer, { children: _jsx(ReleaseNotesFooter, { surface: 'modal', loading: loading, total: items.length, pageIndex: pageIndex, readablePageNumber: readablePageNumber, readLaterLabel: t('releaseNotes.readLater'), onPageChange: setPage, onReadLaterClick: handleReadLaterClick, onPrevPageClick: handlePrevPageClick, onNextPageClick: () => handleNextPageClick(items.length) }) }))] }));
14
+ return (_jsxs(ModalCustom, { open: open, onClose: handleClose, closeOnPopstate: closeOnPopstate, width: WIDTH.M, "data-content-state": contentState, "data-surface": 'modal', ...extractSupportProps(rest), children: [_jsx(ModalCustom.Header, { title: t('releaseNotes.title') }), _jsx(ModalCustom.Body, { className: styles.body, content: _jsx(ReleaseNotesContent, { contentState: contentState, items: items, loading: loading, pageIndex: pageIndex, surface: 'modal', noDataTitle: t('releaseNotes.noDataTitle'), noDataDescription: t('releaseNotes.noDataDescription'), errorTitle: t('releaseNotes.errorTitle'), errorDescription: t('releaseNotes.errorDescription'), retryLabel: t('releaseNotes.retry'), onPageChange: setPage, onDataErrorRetryClick: onDataErrorRetryClick }) }), shouldShowFooter ? (_jsx(ModalCustom.Footer, { children: _jsx(ReleaseNotesFooter, { surface: 'modal', loading: loading, total: items.length, pageIndex: pageIndex, readablePageNumber: readablePageNumber, readLaterLabel: t('releaseNotes.readLater'), onPageChange: setPage, onReadLaterClick: handleReadLaterClick, onPrevPageClick: handlePrevPageClick, onNextPageClick: () => handleNextPageClick(items.length) }) })) : (
15
+ // Нижнюю safe-area окна несёт футер; без него отступ рисуется здесь, как в `DesktopModal`.
16
+ _jsx("div", { className: styles.safeAreaBottom }))] }));
15
17
  }
@@ -1,3 +1,7 @@
1
1
  .body{
2
2
  min-height:calc(6 * var(--sn-primitive-dimension-64, 64px));
3
+ }
4
+
5
+ .safeAreaBottom{
6
+ padding-bottom:var(--sn-modal-anatomy-container-safeArea-paddingBottom, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
3
7
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ImageSVG } from '@cloud-ru/ds-icons/interface/system';
3
3
  import { Markdown } from '@cloud-ru/ds-markdown';
4
- import { BAR_HIDE_STRATEGY, Scroll } from '@cloud-ru/ds-scroll';
4
+ import { BAR_HIDE_STRATEGY, Scroll, SIZE as SCROLL_SIZE } from '@cloud-ru/ds-scroll';
5
5
  import { Skeleton } from '@cloud-ru/ds-skeleton';
6
6
  import { Typography } from '@cloud-ru/ds-typography';
7
7
  import { useCallback, useState } from 'react';
@@ -36,5 +36,5 @@ function NoteItemMedia({ image, video }) {
36
36
  status !== MEDIA_STATUS.Error && (_jsx("img", { ref: handleImageRef, className: styles.file, src: image.src, alt: image.alt, onLoad: handleImageLoad, onError: handleImageError, "data-test-id": TEST_IDS.releaseNotesImage }))), status === MEDIA_STATUS.Pending && (_jsx("div", { className: styles.mediaOverlay, "aria-hidden": true, children: _jsx(Skeleton, { loading: true, width: '100%', height: '100%' }) })), status === MEDIA_STATUS.Error && (_jsx("div", { className: styles.mediaOverlay, role: 'img', "aria-label": image.alt, "data-test-id": TEST_IDS.releaseNotesMediaFallback, children: _jsx(ImageSVG, { className: styles.mediaFallbackIcon, size: FALLBACK_ICON_SIZE }) }))] }));
37
37
  }
38
38
  export function NoteItem({ title, description, image, video, surface = SURFACE.Modal }) {
39
- return (_jsxs("article", { className: styles.root, "data-surface": surface, "data-test-id": TEST_IDS.releaseNotesItem, children: [_jsx(NoteItemMedia, { image: image, video: video }, `${video ?? ''}|${image.src}`), _jsxs("div", { className: styles.content, children: [_jsx(Typography, { className: styles.title, variant: 'title', size: 'l', children: title }), _jsx(Scroll, { className: styles.description, barHideStrategy: BAR_HIDE_STRATEGY.Leave, children: _jsx(Markdown, { value: description }) })] })] }));
39
+ return (_jsxs("article", { className: styles.root, "data-surface": surface, "data-test-id": TEST_IDS.releaseNotesItem, children: [_jsx(NoteItemMedia, { image: image, video: video }, `${video ?? ''}|${image.src}`), _jsxs("div", { className: styles.content, children: [_jsx("div", { className: styles.titleWrapper, children: _jsx(Typography, { className: styles.title, variant: 'title', size: 'l', children: title }) }), _jsx(Scroll, { className: styles.description, size: SCROLL_SIZE.S, barHideStrategy: BAR_HIDE_STRATEGY.Leave, children: _jsx(Markdown, { value: description, className: styles.markdown }) })] })] }));
40
40
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Skeleton, SkeletonText } from '@cloud-ru/ds-skeleton';
3
+ import cn from 'classnames';
3
4
  import { SURFACE, TEST_IDS } from '../../constants.js';
4
5
  import styles from './styles.module.css';
5
6
  /** Строки скелетона текста: заголовок и абзацы описания — по структуре типовой новости. */
@@ -10,5 +11,5 @@ const PARAGRAPH_LINES = [2, 3, 3];
10
11
  * поэтому переход `loading → data` не меняет геометрию.
11
12
  */
12
13
  export function NoteItemSkeleton({ surface = SURFACE.Modal }) {
13
- return (_jsxs("div", { className: styles.root, "data-surface": surface, "aria-hidden": true, "data-test-id": TEST_IDS.releaseNotesSkeleton, children: [_jsx("div", { className: styles.media, "data-test-id": TEST_IDS.releaseNotesMedia, children: _jsx(Skeleton, { loading: true, width: '100%', height: '100%' }) }), _jsxs("div", { className: styles.content, children: [_jsx(SkeletonText, { loading: true, lines: TITLE_LINES, variant: 'headline', size: 's' }), PARAGRAPH_LINES.map((lines, index) => (_jsx(SkeletonText, { loading: true, lines: lines }, index)))] })] }));
14
+ return (_jsxs("div", { className: styles.root, "data-surface": surface, "aria-hidden": true, "data-test-id": TEST_IDS.releaseNotesSkeleton, children: [_jsx("div", { className: styles.media, "data-test-id": TEST_IDS.releaseNotesMedia, children: _jsx(Skeleton, { loading: true, width: '100%', height: '100%' }) }), _jsxs("div", { className: cn(styles.content, styles.skeletonContent), children: [_jsx(SkeletonText, { loading: true, lines: TITLE_LINES, variant: 'title', size: 'l' }), PARAGRAPH_LINES.map((lines, index) => (_jsx(SkeletonText, { loading: true, lines: lines }, index)))] })] }));
14
15
  }
@@ -23,7 +23,10 @@
23
23
  }
24
24
 
25
25
  .root[data-surface=bottomSheet] .media{
26
+ justify-self:center;
26
27
  aspect-ratio:1;
28
+ width:100%;
29
+ max-width:calc(6 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
27
30
  }
28
31
 
29
32
  .file{
@@ -49,7 +52,7 @@
49
52
  .content{
50
53
  display:flex;
51
54
  flex-direction:column;
52
- row-gap:var(--sn-primitive-dimension-12, 12px);
55
+ row-gap:var(--sn-primitive-dimension-16, 16px);
53
56
  min-width:0;
54
57
  min-height:0;
55
58
  }
@@ -64,10 +67,15 @@
64
67
  }
65
68
 
66
69
  .root[data-surface=modal] .content{
67
- row-gap:var(--sn-primitive-dimension-16, 16px);
68
70
  order:1;
69
71
  }
70
72
 
73
+ .root[data-surface=modal] .markdown,
74
+ .root[data-surface=modal] .titleWrapper,
75
+ .root[data-surface=modal] .skeletonContent{
76
+ padding-right:var(--sn-primitive-dimension-16, 16px);
77
+ }
78
+
71
79
  .root[data-surface=modal] .media{
72
80
  order:2;
73
81
  }
@@ -3,10 +3,14 @@ import { Button } from '@cloud-ru/ds-button';
3
3
  import { Carousel } from '@cloud-ru/ds-carousel';
4
4
  import { CrossSVG } from '@cloud-ru/ds-icons/interface/system';
5
5
  import { InfoBlock } from '@cloud-ru/ds-info-block';
6
+ import { Typography } from '@cloud-ru/ds-typography';
6
7
  import cn from 'classnames';
7
8
  import { CONTENT_STATE, TEST_IDS } from '../../constants.js';
8
9
  import { NoteItem, NoteItemSkeleton } from '../NoteItem/index.js';
9
10
  import styles from './styles.module.css';
11
+ function InfoBlockDescription({ children }) {
12
+ return (_jsx(Typography, { variant: 'body', size: 'm', children: children }));
13
+ }
10
14
  export function ReleaseNotesContent({ contentState, items, loading, pageIndex, surface, noDataTitle, noDataDescription, errorTitle, errorDescription, retryLabel, onPageChange, onDataErrorRetryClick, }) {
11
15
  const total = items.length;
12
16
  const hasItems = total > 0;
@@ -14,10 +18,10 @@ export function ReleaseNotesContent({ contentState, items, loading, pageIndex, s
14
18
  return (_jsx("div", { className: cn(styles.root, styles[surface]), children: _jsx("div", { className: styles.skeletonSlide, children: _jsx(NoteItemSkeleton, { surface: surface }) }) }));
15
19
  }
16
20
  if (contentState === CONTENT_STATE.Error) {
17
- return (_jsx("div", { className: styles.stateWrapper, "data-surface": surface, children: _jsx(InfoBlock, { className: styles.state, size: 'l', title: errorTitle, content: errorDescription, icon: { icon: CrossSVG, appearance: 'neutral', background: true }, "data-test-id": TEST_IDS.releaseNotesError, footer: _jsx(Button, { view: 'filled', appearance: 'neutral', size: 'l', label: retryLabel, onClick: onDataErrorRetryClick, "data-test-id": TEST_IDS.releaseNotesRetryButton }) }) }));
21
+ return (_jsx("div", { className: styles.stateWrapper, "data-surface": surface, children: _jsx(InfoBlock, { className: styles.state, size: 'l', title: errorTitle, content: _jsx(InfoBlockDescription, { children: errorDescription }), icon: { icon: CrossSVG, appearance: 'neutral', background: true }, "data-test-id": TEST_IDS.releaseNotesError, footer: _jsx(Button, { view: 'filled', appearance: 'neutral', size: 'l', label: retryLabel, onClick: onDataErrorRetryClick, "data-test-id": TEST_IDS.releaseNotesRetryButton }) }) }));
18
22
  }
19
23
  if (contentState === CONTENT_STATE.NoData || !hasItems) {
20
- return (_jsx("div", { className: styles.stateWrapper, "data-surface": surface, children: _jsx(InfoBlock, { className: styles.state, size: 'l', title: noDataTitle, content: noDataDescription, "data-test-id": TEST_IDS.releaseNotesNoData }) }));
24
+ return (_jsx("div", { className: styles.stateWrapper, "data-surface": surface, children: _jsx(InfoBlock, { className: styles.state, size: 'l', title: noDataTitle, content: _jsx(InfoBlockDescription, { children: noDataDescription }), "data-test-id": TEST_IDS.releaseNotesNoData }) }));
21
25
  }
22
26
  return (_jsx("div", { className: cn(styles.root, styles[surface]), children: _jsx(Carousel, { arrows: false, pagination: false, swipe: true, state: { page: pageIndex, onChange: onPageChange }, className: styles.carousel, children: items.map(item => (_jsx(NoteItem, { ...item, surface: surface }, `${item.title}-${item.image.src}`))) }) }));
23
27
  }
@@ -35,7 +35,7 @@
35
35
  height:calc(7 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
36
36
  }
37
37
  .stateWrapper[data-surface=bottomSheet]{
38
- min-height:calc(7 * var(--sn-primitive-dimension-64, 64px));
38
+ min-height:100%;
39
39
  }
40
40
 
41
41
  .state{
@@ -10,7 +10,6 @@ export function ReleaseNotesFooter({ surface, total, loading, pageIndex, readabl
10
10
  // появляются рывком поверх уже отрисованного контента.
11
11
  const hasPagination = loading || total > 1;
12
12
  const isModal = surface === SURFACE.Modal;
13
- const buttonSize = isModal ? 'm' : 'l';
14
13
  const handleSliderChange = (page) => onPageChange(page - 1);
15
- return (_jsxs("div", { className: styles.root, "data-surface": surface, children: [_jsx(Button, { view: 'function', appearance: 'neutral', size: buttonSize, label: readLaterLabel, "data-test-id": TEST_IDS.releaseNotesReadLaterButton, ...readLaterButtonProps, onClick: onReadLaterClick }), isModal && hasPagination && (_jsx("div", { className: styles.slider, children: _jsx(Skeleton, { loading: loading, className: styles.sliderSkeleton, children: _jsx(PaginationSlider, { size: PAGINATION_SLIDER_SIZE.Xs, total: total, page: readablePageNumber, onChange: handleSliderChange, "data-test-id": TEST_IDS.releaseNotesPaginationSlider }) }) })), hasPagination && (_jsxs("div", { className: styles.controls, children: [_jsx(Button, { view: 'outline', appearance: 'neutral', size: buttonSize, icon: _jsx(ChevronLeftSVG, {}), disabled: loading || pageIndex === 0, onClick: onPrevPageClick, "data-test-id": TEST_IDS.releaseNotesPrevButton }), _jsx(Button, { view: 'outline', appearance: 'neutral', size: buttonSize, icon: _jsx(ChevronRightSVG, {}), disabled: loading || readablePageNumber === total, onClick: onNextPageClick, "data-test-id": TEST_IDS.releaseNotesNextButton })] }))] }));
14
+ return (_jsxs("div", { className: styles.root, "data-surface": surface, children: [_jsx(Button, { view: 'function', appearance: 'neutral', size: 'm', label: readLaterLabel, "data-test-id": TEST_IDS.releaseNotesReadLaterButton, ...readLaterButtonProps, onClick: onReadLaterClick }), isModal && hasPagination && (_jsx("div", { className: styles.slider, children: _jsx(Skeleton, { loading: loading, className: styles.sliderSkeleton, children: _jsx(PaginationSlider, { size: PAGINATION_SLIDER_SIZE.Xs, total: total, page: readablePageNumber, onChange: handleSliderChange, "data-test-id": TEST_IDS.releaseNotesPaginationSlider }) }) })), hasPagination && (_jsxs("div", { className: styles.controls, children: [_jsx(Button, { view: 'outline', appearance: 'neutral', size: 'm', icon: _jsx(ChevronLeftSVG, {}), disabled: loading || pageIndex === 0, onClick: onPrevPageClick, "data-test-id": TEST_IDS.releaseNotesPrevButton }), _jsx(Button, { view: 'outline', appearance: 'neutral', size: 'm', icon: _jsx(ChevronRightSVG, {}), disabled: loading || readablePageNumber === total, onClick: onNextPageClick, "data-test-id": TEST_IDS.releaseNotesNextButton })] }))] }));
16
15
  }
@@ -35,6 +35,10 @@
35
35
  .body{
36
36
  min-height:calc(6 * var(--sn-primitive-dimension-64, 64px));
37
37
  }
38
+
39
+ .safeAreaBottom{
40
+ padding-bottom:var(--sn-modal-anatomy-container-safeArea-paddingBottom, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
41
+ }
38
42
  .root{
39
43
  display:flex;
40
44
  flex-direction:column;
@@ -99,7 +103,10 @@
99
103
  }
100
104
 
101
105
  .root[data-surface=bottomSheet] .media{
106
+ justify-self:center;
102
107
  aspect-ratio:1;
108
+ width:100%;
109
+ max-width:calc(6 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
103
110
  }
104
111
 
105
112
  .file{
@@ -125,7 +132,7 @@
125
132
  .content{
126
133
  display:flex;
127
134
  flex-direction:column;
128
- row-gap:var(--sn-primitive-dimension-12, 12px);
135
+ row-gap:var(--sn-primitive-dimension-16, 16px);
129
136
  min-width:0;
130
137
  min-height:0;
131
138
  }
@@ -140,10 +147,15 @@
140
147
  }
141
148
 
142
149
  .root[data-surface=modal] .content{
143
- row-gap:var(--sn-primitive-dimension-16, 16px);
144
150
  order:1;
145
151
  }
146
152
 
153
+ .root[data-surface=modal] .markdown,
154
+ .root[data-surface=modal] .titleWrapper,
155
+ .root[data-surface=modal] .skeletonContent{
156
+ padding-right:var(--sn-primitive-dimension-16, 16px);
157
+ }
158
+
147
159
  .root[data-surface=modal] .media{
148
160
  order:2;
149
161
  }
@@ -184,7 +196,7 @@
184
196
  height:calc(7 * var(--sn-primitive-dimension-64, 64px) - var(--sn-primitive-dimension-4, 4px));
185
197
  }
186
198
  .stateWrapper[data-surface=bottomSheet]{
187
- min-height:calc(7 * var(--sn-primitive-dimension-64, 64px));
199
+ min-height:100%;
188
200
  }
189
201
 
190
202
  .state{