@cloud-ru/ds-uikit-product-modal-predefined 1.0.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 (155) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +359 -0
  4. package/dist/cjs/components/DeleteModal/DeleteModal.d.ts +2 -0
  5. package/dist/cjs/components/DeleteModal/DeleteModal.js +39 -0
  6. package/dist/cjs/components/DeleteModal/index.d.ts +1 -0
  7. package/dist/cjs/components/DeleteModal/index.js +17 -0
  8. package/dist/cjs/components/DeleteModal/styles.module.css +33 -0
  9. package/dist/cjs/components/RecallModal/RecallModal.d.ts +2 -0
  10. package/dist/cjs/components/RecallModal/RecallModal.js +37 -0
  11. package/dist/cjs/components/RecallModal/index.d.ts +1 -0
  12. package/dist/cjs/components/RecallModal/index.js +17 -0
  13. package/dist/cjs/components/RecallModal/styles.module.css +33 -0
  14. package/dist/cjs/components/ReleaseNotes/ReleaseNotes.d.ts +7 -0
  15. package/dist/cjs/components/ReleaseNotes/ReleaseNotes.js +15 -0
  16. package/dist/cjs/components/ReleaseNotes/index.d.ts +1 -0
  17. package/dist/cjs/components/ReleaseNotes/index.js +17 -0
  18. package/dist/cjs/components/index.d.ts +3 -0
  19. package/dist/cjs/components/index.js +19 -0
  20. package/dist/cjs/constants.d.ts +27 -0
  21. package/dist/cjs/constants.js +30 -0
  22. package/dist/cjs/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.d.ts +2 -0
  23. package/dist/cjs/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +21 -0
  24. package/dist/cjs/helperComponents/DesktopReleaseNotes/index.d.ts +1 -0
  25. package/dist/cjs/helperComponents/DesktopReleaseNotes/index.js +17 -0
  26. package/dist/cjs/helperComponents/DesktopReleaseNotes/styles.module.css +19 -0
  27. package/dist/cjs/helperComponents/InputConfirm/InputConfirm.d.ts +48 -0
  28. package/dist/cjs/helperComponents/InputConfirm/InputConfirm.js +15 -0
  29. package/dist/cjs/helperComponents/InputConfirm/index.d.ts +1 -0
  30. package/dist/cjs/helperComponents/InputConfirm/index.js +17 -0
  31. package/dist/cjs/helperComponents/InputConfirm/styles.module.css +38 -0
  32. package/dist/cjs/helperComponents/MobileReleaseNotes/MobileReleaseNotes.d.ts +2 -0
  33. package/dist/cjs/helperComponents/MobileReleaseNotes/MobileReleaseNotes.js +26 -0
  34. package/dist/cjs/helperComponents/MobileReleaseNotes/index.d.ts +1 -0
  35. package/dist/cjs/helperComponents/MobileReleaseNotes/index.js +17 -0
  36. package/dist/cjs/helperComponents/NoteItem/NoteItem.d.ts +6 -0
  37. package/dist/cjs/helperComponents/NoteItem/NoteItem.js +20 -0
  38. package/dist/cjs/helperComponents/NoteItem/index.d.ts +1 -0
  39. package/dist/cjs/helperComponents/NoteItem/index.js +17 -0
  40. package/dist/cjs/helperComponents/NoteItem/styles.module.css +53 -0
  41. package/dist/cjs/helperComponents/ReleaseNotesContent/ReleaseNotesContent.d.ts +28 -0
  42. package/dist/cjs/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +30 -0
  43. package/dist/cjs/helperComponents/ReleaseNotesContent/index.d.ts +1 -0
  44. package/dist/cjs/helperComponents/ReleaseNotesContent/index.js +17 -0
  45. package/dist/cjs/helperComponents/ReleaseNotesContent/styles.module.css +40 -0
  46. package/dist/cjs/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.d.ts +25 -0
  47. package/dist/cjs/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +17 -0
  48. package/dist/cjs/helperComponents/ReleaseNotesFooter/index.d.ts +1 -0
  49. package/dist/cjs/helperComponents/ReleaseNotesFooter/index.js +17 -0
  50. package/dist/cjs/helperComponents/ReleaseNotesFooter/styles.module.css +27 -0
  51. package/dist/cjs/helperComponents/index.d.ts +6 -0
  52. package/dist/cjs/helperComponents/index.js +22 -0
  53. package/dist/cjs/hooks.d.ts +26 -0
  54. package/dist/cjs/hooks.js +59 -0
  55. package/dist/cjs/index.d.ts +4 -0
  56. package/dist/cjs/index.js +20 -0
  57. package/dist/cjs/locale/index.d.ts +143 -0
  58. package/dist/cjs/locale/index.js +76 -0
  59. package/dist/cjs/package.json +3 -0
  60. package/dist/cjs/style.css +243 -0
  61. package/dist/cjs/types.d.ts +67 -0
  62. package/dist/cjs/types.js +2 -0
  63. package/dist/esm/components/DeleteModal/DeleteModal.d.ts +2 -0
  64. package/dist/esm/components/DeleteModal/DeleteModal.js +33 -0
  65. package/dist/esm/components/DeleteModal/index.d.ts +1 -0
  66. package/dist/esm/components/DeleteModal/index.js +1 -0
  67. package/dist/esm/components/DeleteModal/styles.module.css +33 -0
  68. package/dist/esm/components/RecallModal/RecallModal.d.ts +2 -0
  69. package/dist/esm/components/RecallModal/RecallModal.js +31 -0
  70. package/dist/esm/components/RecallModal/index.d.ts +1 -0
  71. package/dist/esm/components/RecallModal/index.js +1 -0
  72. package/dist/esm/components/RecallModal/styles.module.css +33 -0
  73. package/dist/esm/components/ReleaseNotes/ReleaseNotes.d.ts +7 -0
  74. package/dist/esm/components/ReleaseNotes/ReleaseNotes.js +12 -0
  75. package/dist/esm/components/ReleaseNotes/index.d.ts +1 -0
  76. package/dist/esm/components/ReleaseNotes/index.js +1 -0
  77. package/dist/esm/components/index.d.ts +3 -0
  78. package/dist/esm/components/index.js +3 -0
  79. package/dist/esm/constants.d.ts +27 -0
  80. package/dist/esm/constants.js +27 -0
  81. package/dist/esm/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.d.ts +2 -0
  82. package/dist/esm/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +15 -0
  83. package/dist/esm/helperComponents/DesktopReleaseNotes/index.d.ts +1 -0
  84. package/dist/esm/helperComponents/DesktopReleaseNotes/index.js +1 -0
  85. package/dist/esm/helperComponents/DesktopReleaseNotes/styles.module.css +19 -0
  86. package/dist/esm/helperComponents/InputConfirm/InputConfirm.d.ts +48 -0
  87. package/dist/esm/helperComponents/InputConfirm/InputConfirm.js +9 -0
  88. package/dist/esm/helperComponents/InputConfirm/index.d.ts +1 -0
  89. package/dist/esm/helperComponents/InputConfirm/index.js +1 -0
  90. package/dist/esm/helperComponents/InputConfirm/styles.module.css +38 -0
  91. package/dist/esm/helperComponents/MobileReleaseNotes/MobileReleaseNotes.d.ts +2 -0
  92. package/dist/esm/helperComponents/MobileReleaseNotes/MobileReleaseNotes.js +23 -0
  93. package/dist/esm/helperComponents/MobileReleaseNotes/index.d.ts +1 -0
  94. package/dist/esm/helperComponents/MobileReleaseNotes/index.js +1 -0
  95. package/dist/esm/helperComponents/NoteItem/NoteItem.d.ts +6 -0
  96. package/dist/esm/helperComponents/NoteItem/NoteItem.js +14 -0
  97. package/dist/esm/helperComponents/NoteItem/index.d.ts +1 -0
  98. package/dist/esm/helperComponents/NoteItem/index.js +1 -0
  99. package/dist/esm/helperComponents/NoteItem/styles.module.css +53 -0
  100. package/dist/esm/helperComponents/ReleaseNotesContent/ReleaseNotesContent.d.ts +28 -0
  101. package/dist/esm/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +24 -0
  102. package/dist/esm/helperComponents/ReleaseNotesContent/index.d.ts +1 -0
  103. package/dist/esm/helperComponents/ReleaseNotesContent/index.js +1 -0
  104. package/dist/esm/helperComponents/ReleaseNotesContent/styles.module.css +40 -0
  105. package/dist/esm/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.d.ts +25 -0
  106. package/dist/esm/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +11 -0
  107. package/dist/esm/helperComponents/ReleaseNotesFooter/index.d.ts +1 -0
  108. package/dist/esm/helperComponents/ReleaseNotesFooter/index.js +1 -0
  109. package/dist/esm/helperComponents/ReleaseNotesFooter/styles.module.css +27 -0
  110. package/dist/esm/helperComponents/index.d.ts +6 -0
  111. package/dist/esm/helperComponents/index.js +6 -0
  112. package/dist/esm/hooks.d.ts +26 -0
  113. package/dist/esm/hooks.js +55 -0
  114. package/dist/esm/index.d.ts +4 -0
  115. package/dist/esm/index.js +4 -0
  116. package/dist/esm/locale/index.d.ts +143 -0
  117. package/dist/esm/locale/index.js +73 -0
  118. package/dist/esm/style.css +243 -0
  119. package/dist/esm/types.d.ts +67 -0
  120. package/dist/esm/types.js +1 -0
  121. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  122. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  123. package/package.json +63 -0
  124. package/src/components/DeleteModal/DeleteModal.tsx +116 -0
  125. package/src/components/DeleteModal/index.ts +1 -0
  126. package/src/components/DeleteModal/styles.module.scss +37 -0
  127. package/src/components/RecallModal/RecallModal.tsx +111 -0
  128. package/src/components/RecallModal/index.ts +1 -0
  129. package/src/components/RecallModal/styles.module.scss +37 -0
  130. package/src/components/ReleaseNotes/ReleaseNotes.tsx +15 -0
  131. package/src/components/ReleaseNotes/index.ts +1 -0
  132. package/src/components/index.ts +3 -0
  133. package/src/constants.ts +29 -0
  134. package/src/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.tsx +85 -0
  135. package/src/helperComponents/DesktopReleaseNotes/index.ts +1 -0
  136. package/src/helperComponents/DesktopReleaseNotes/styles.module.scss +24 -0
  137. package/src/helperComponents/InputConfirm/InputConfirm.tsx +72 -0
  138. package/src/helperComponents/InputConfirm/index.ts +1 -0
  139. package/src/helperComponents/InputConfirm/styles.module.scss +30 -0
  140. package/src/helperComponents/MobileReleaseNotes/MobileReleaseNotes.tsx +88 -0
  141. package/src/helperComponents/MobileReleaseNotes/index.ts +1 -0
  142. package/src/helperComponents/NoteItem/NoteItem.tsx +58 -0
  143. package/src/helperComponents/NoteItem/index.ts +1 -0
  144. package/src/helperComponents/NoteItem/styles.module.scss +54 -0
  145. package/src/helperComponents/ReleaseNotesContent/ReleaseNotesContent.tsx +119 -0
  146. package/src/helperComponents/ReleaseNotesContent/index.ts +1 -0
  147. package/src/helperComponents/ReleaseNotesContent/styles.module.scss +45 -0
  148. package/src/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.tsx +84 -0
  149. package/src/helperComponents/ReleaseNotesFooter/index.ts +1 -0
  150. package/src/helperComponents/ReleaseNotesFooter/styles.module.scss +25 -0
  151. package/src/helperComponents/index.ts +6 -0
  152. package/src/hooks.ts +89 -0
  153. package/src/index.ts +4 -0
  154. package/src/locale/index.ts +77 -0
  155. package/src/types.ts +80 -0
@@ -0,0 +1,88 @@
1
+ import { BottomSheet } from '@cloud-ru/ds-bottom-sheet';
2
+ import { extractSupportProps } from '@cloud-ru/ds-utils';
3
+ import { MouseEvent } from 'react';
4
+
5
+ import { CONTENT_STATE } from '../../constants';
6
+ import { useReleaseNotesNavigation } from '../../hooks';
7
+ import { modalPredefinedLocale } from '../../locale';
8
+ import { ReleaseNotesProps } from '../../types';
9
+ import { ReleaseNotesContent } from '../ReleaseNotesContent';
10
+ import { ReleaseNotesFooter } from '../ReleaseNotesFooter';
11
+
12
+ export function MobileReleaseNotes({
13
+ open,
14
+ onClose,
15
+ closeOnPopstate,
16
+ contentState = CONTENT_STATE.Data,
17
+ items,
18
+ loading,
19
+ onReadLaterClick,
20
+ onDataErrorRetryClick,
21
+ onSlideChange,
22
+ readLaterButtonProps,
23
+ ...rest
24
+ }: ReleaseNotesProps) {
25
+ const { t } = modalPredefinedLocale.useTranslations();
26
+ const {
27
+ pageIndex,
28
+ readablePageNumber,
29
+ setPage,
30
+ handleClose,
31
+ handleReadLaterClick,
32
+ handleNextPageClick,
33
+ handlePrevPageClick,
34
+ } = useReleaseNotesNavigation({ onClose, onReadLaterClick, onSlideChange });
35
+ const { onClick: onReadLaterButtonClick, ...readLaterButtonRest } = readLaterButtonProps ?? {};
36
+ const shouldShowFooter = contentState === CONTENT_STATE.Data && (loading || items.length > 0);
37
+
38
+ const handleReadLaterButtonClick = (event: MouseEvent<HTMLButtonElement>) => {
39
+ onReadLaterButtonClick?.(event);
40
+ handleReadLaterClick();
41
+ };
42
+
43
+ return (
44
+ <BottomSheet
45
+ // Дефолт `withDividers` сменился на `true` — здесь вид сохраняем прежним.
46
+ withDividers={false}
47
+ open={open}
48
+ onClose={handleClose}
49
+ closeOnPopstate={closeOnPopstate}
50
+ title={t('releaseNotes.title')}
51
+ content={
52
+ <ReleaseNotesContent
53
+ contentState={contentState}
54
+ items={items}
55
+ loading={loading}
56
+ pageIndex={pageIndex}
57
+ surface='bottomSheet'
58
+ noDataTitle={t('releaseNotes.noDataTitle')}
59
+ noDataDescription={t('releaseNotes.noDataDescription')}
60
+ errorTitle={t('releaseNotes.errorTitle')}
61
+ errorDescription={t('releaseNotes.errorDescription')}
62
+ retryLabel={t('releaseNotes.retry')}
63
+ onPageChange={setPage}
64
+ onDataErrorRetryClick={onDataErrorRetryClick}
65
+ />
66
+ }
67
+ footer={
68
+ shouldShowFooter ? (
69
+ <ReleaseNotesFooter
70
+ surface='bottomSheet'
71
+ total={items.length}
72
+ pageIndex={pageIndex}
73
+ readablePageNumber={readablePageNumber}
74
+ counterLabel={t('releaseNotes.counter', { page: readablePageNumber, total: items.length })}
75
+ readLaterLabel={t('releaseNotes.readLater')}
76
+ onReadLaterClick={handleReadLaterButtonClick}
77
+ onPrevPageClick={handlePrevPageClick}
78
+ onNextPageClick={() => handleNextPageClick(items.length)}
79
+ readLaterButtonProps={readLaterButtonRest}
80
+ />
81
+ ) : undefined
82
+ }
83
+ data-content-state={contentState}
84
+ data-surface='bottomSheet'
85
+ {...extractSupportProps(rest)}
86
+ />
87
+ );
88
+ }
@@ -0,0 +1 @@
1
+ export * from './MobileReleaseNotes';
@@ -0,0 +1,58 @@
1
+ import { Markdown } from '@cloud-ru/ds-markdown';
2
+ import { BAR_HIDE_STRATEGY, Scroll } from '@cloud-ru/ds-scroll';
3
+ import { Skeleton } from '@cloud-ru/ds-skeleton';
4
+ import { Typography } from '@cloud-ru/ds-typography';
5
+ import { useState } from 'react';
6
+
7
+ import { TEST_IDS } from '../../constants';
8
+ import { NoteItemProps } from '../../types';
9
+ import styles from './styles.module.scss';
10
+
11
+ type NoteItemComponentProps = NoteItemProps & {
12
+ surface?: 'modal' | 'bottomSheet';
13
+ };
14
+
15
+ export function NoteItem({ title, description, image, video, surface = 'modal' }: NoteItemComponentProps) {
16
+ const [isVideoReady, setIsVideoReady] = useState(false);
17
+ const [hasVideoError, setHasVideoError] = useState(false);
18
+ const shouldShowVideo = Boolean(video && !hasVideoError);
19
+
20
+ return (
21
+ <article className={styles.root} data-surface={surface} data-test-id={TEST_IDS.releaseNotesItem}>
22
+ <div className={styles.media} data-test-id={TEST_IDS.releaseNotesMedia}>
23
+ {shouldShowVideo && (
24
+ <>
25
+ <video
26
+ className={styles.video}
27
+ src={video}
28
+ muted
29
+ loop
30
+ playsInline
31
+ autoPlay
32
+ onLoadedData={() => setIsVideoReady(true)}
33
+ onError={() => setHasVideoError(true)}
34
+ data-test-id={TEST_IDS.releaseNotesVideo}
35
+ />
36
+ {!isVideoReady && (
37
+ <div className={styles.videoSkeleton} aria-hidden>
38
+ <Skeleton loading width='100%' height='100%' />
39
+ </div>
40
+ )}
41
+ </>
42
+ )}
43
+
44
+ {!shouldShowVideo && <img className={styles.image} src={image.src} alt={image.alt} />}
45
+ </div>
46
+
47
+ <div className={styles.content}>
48
+ <Typography variant='headline' size='s'>
49
+ {title}
50
+ </Typography>
51
+
52
+ <Scroll className={styles.description} barHideStrategy={BAR_HIDE_STRATEGY.Leave}>
53
+ <Markdown value={description} />
54
+ </Scroll>
55
+ </div>
56
+ </article>
57
+ );
58
+ }
@@ -0,0 +1 @@
1
+ export * from './NoteItem';
@@ -0,0 +1,54 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+
3
+ .root {
4
+ display: grid;
5
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
6
+ column-gap: base.$sn-primitive-dimension-24;
7
+ min-height: calc(5 * #{base.$sn-primitive-dimension-64});
8
+
9
+ &[data-surface='bottomSheet'] {
10
+ grid-template-columns: minmax(0, 1fr);
11
+ row-gap: base.$sn-primitive-dimension-16;
12
+ min-height: 0;
13
+ }
14
+ }
15
+
16
+ .media {
17
+ position: relative;
18
+ overflow: hidden;
19
+ min-width: 0;
20
+ border-radius: base.$sn-primitive-dimension-16;
21
+ }
22
+
23
+ .image,
24
+ .video {
25
+ display: block;
26
+ width: 100%;
27
+ height: 100%;
28
+ object-fit: cover;
29
+ }
30
+
31
+ .videoSkeleton {
32
+ position: absolute;
33
+ inset: 0;
34
+ }
35
+
36
+ .content {
37
+ display: flex;
38
+ flex-direction: column;
39
+ row-gap: base.$sn-primitive-dimension-12;
40
+ min-width: 0;
41
+ }
42
+
43
+ .description {
44
+ min-height: 0;
45
+ }
46
+
47
+ .root[data-surface='modal'] .content {
48
+ row-gap: base.$sn-primitive-dimension-16;
49
+ order: 1;
50
+ }
51
+
52
+ .root[data-surface='modal'] .media {
53
+ order: 2;
54
+ }
@@ -0,0 +1,119 @@
1
+ import { Button } from '@cloud-ru/ds-button';
2
+ import { Carousel } from '@cloud-ru/ds-carousel';
3
+ import { CrossSVG } from '@cloud-ru/ds-icons/interface/system';
4
+ import { InfoBlock } from '@cloud-ru/ds-info-block';
5
+ import { Skeleton } from '@cloud-ru/ds-skeleton';
6
+ import cn from 'classnames';
7
+
8
+ import { CONTENT_STATE, TEST_IDS } from '../../constants';
9
+ import { NoteItemProps, ReleaseNotesContentState } from '../../types';
10
+ import { NoteItem } from '../NoteItem';
11
+ import styles from './styles.module.scss';
12
+
13
+ export type ReleaseNotesContentProps = {
14
+ /** Визуальное состояние */
15
+ contentState: ReleaseNotesContentState;
16
+ /** Новости */
17
+ items: NoteItemProps[];
18
+ /** Состояние загрузки */
19
+ loading?: boolean;
20
+ /** Текущая страница, zero-based */
21
+ pageIndex: number;
22
+ /** Поверхность */
23
+ surface: 'modal' | 'bottomSheet';
24
+ /** Заголовок no-data */
25
+ noDataTitle: string;
26
+ /** Описание no-data */
27
+ noDataDescription: string;
28
+ /** Заголовок ошибки */
29
+ errorTitle: string;
30
+ /** Описание ошибки */
31
+ errorDescription: string;
32
+ /** Label retry-кнопки */
33
+ retryLabel: string;
34
+ /** Смена страницы */
35
+ onPageChange(pageIndex: number): void;
36
+ /** Повторная загрузка */
37
+ onDataErrorRetryClick?(): void;
38
+ };
39
+
40
+ export function ReleaseNotesContent({
41
+ contentState,
42
+ items,
43
+ loading,
44
+ pageIndex,
45
+ surface,
46
+ noDataTitle,
47
+ noDataDescription,
48
+ errorTitle,
49
+ errorDescription,
50
+ retryLabel,
51
+ onPageChange,
52
+ onDataErrorRetryClick,
53
+ }: ReleaseNotesContentProps) {
54
+ const total = items.length;
55
+ const hasItems = total > 0;
56
+
57
+ if (loading) {
58
+ return (
59
+ <div className={styles.loading} data-surface={surface}>
60
+ <Skeleton loading width='100%' height='100%' borderRadius='var(--border-radius-l)' />
61
+ </div>
62
+ );
63
+ }
64
+
65
+ if (contentState === CONTENT_STATE.Error) {
66
+ return (
67
+ <div className={styles.stateWrapper} data-surface={surface}>
68
+ <InfoBlock
69
+ className={styles.state}
70
+ size='l'
71
+ title={errorTitle}
72
+ content={errorDescription}
73
+ icon={{ icon: CrossSVG, appearance: 'neutral', background: true }}
74
+ data-test-id={TEST_IDS.releaseNotesError}
75
+ footer={
76
+ <Button
77
+ view='filled'
78
+ appearance='neutral'
79
+ size='l'
80
+ label={retryLabel}
81
+ onClick={onDataErrorRetryClick}
82
+ data-test-id={TEST_IDS.releaseNotesRetryButton}
83
+ />
84
+ }
85
+ />
86
+ </div>
87
+ );
88
+ }
89
+
90
+ if (contentState === CONTENT_STATE.NoData || !hasItems) {
91
+ return (
92
+ <div className={styles.stateWrapper} data-surface={surface}>
93
+ <InfoBlock
94
+ className={styles.state}
95
+ size='l'
96
+ title={noDataTitle}
97
+ content={noDataDescription}
98
+ data-test-id={TEST_IDS.releaseNotesNoData}
99
+ />
100
+ </div>
101
+ );
102
+ }
103
+
104
+ return (
105
+ <div className={cn(styles.root, styles[surface])}>
106
+ <Carousel
107
+ arrows={false}
108
+ pagination={false}
109
+ swipe
110
+ state={{ page: pageIndex, onChange: onPageChange }}
111
+ className={styles.carousel}
112
+ >
113
+ {items.map(item => (
114
+ <NoteItem key={`${item.title}-${item.image.src}`} {...item} surface={surface} />
115
+ ))}
116
+ </Carousel>
117
+ </div>
118
+ );
119
+ }
@@ -0,0 +1 @@
1
+ export * from './ReleaseNotesContent';
@@ -0,0 +1,45 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+
3
+ .root {
4
+ display: flex;
5
+ flex-direction: column;
6
+ row-gap: base.$sn-primitive-dimension-16;
7
+ }
8
+
9
+ .modal {
10
+ min-width: calc(11 * #{base.$sn-primitive-dimension-64});
11
+ }
12
+
13
+ .bottomSheet {
14
+ min-width: 0;
15
+ }
16
+
17
+ .carousel {
18
+ min-width: 0;
19
+ }
20
+
21
+ .stateWrapper {
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ justify-content: center;
26
+
27
+ width: 100%;
28
+
29
+ &[data-surface='modal'] {
30
+ height: calc(7 * #{base.$sn-primitive-dimension-64} - #{base.$sn-primitive-dimension-4});
31
+ }
32
+
33
+ &[data-surface='bottomSheet'] {
34
+ min-height: calc(7 * #{base.$sn-primitive-dimension-64});
35
+ }
36
+ }
37
+
38
+ .state {
39
+ width: 100%;
40
+ max-width: calc(9 * #{base.$sn-primitive-dimension-64} + 3 * #{base.$sn-primitive-dimension-16});
41
+ }
42
+
43
+ .loading {
44
+ height: calc(5 * #{base.$sn-primitive-dimension-64});
45
+ }
@@ -0,0 +1,84 @@
1
+ import { Button, ButtonProps } from '@cloud-ru/ds-button';
2
+ import { ChevronLeftSVG, ChevronRightSVG } from '@cloud-ru/ds-icons/interface/system';
3
+ import { MouseEvent } from 'react';
4
+
5
+ import { TEST_IDS } from '../../constants';
6
+ import styles from './styles.module.scss';
7
+
8
+ export type ReleaseNotesFooterProps = {
9
+ /** Поверхность отображения */
10
+ surface: 'modal' | 'bottomSheet';
11
+ /** Количество страниц */
12
+ total: number;
13
+ /** Текущая страница, zero-based */
14
+ pageIndex: number;
15
+ /** Номер страницы для отображения */
16
+ readablePageNumber: number;
17
+ /** Текст счётчика «1 из N» (используется на bottomSheet) */
18
+ counterLabel: string;
19
+ /** Label кнопки «Ознакомиться позже» */
20
+ readLaterLabel: string;
21
+ /** Действие «Ознакомиться позже» */
22
+ onReadLaterClick(event: MouseEvent<HTMLButtonElement>): void;
23
+ /** Предыдущая страница */
24
+ onPrevPageClick(): void;
25
+ /** Следующая страница */
26
+ onNextPageClick(): void;
27
+ /** Дополнительные пропсы кнопки «Ознакомиться позже» */
28
+ readLaterButtonProps?: Partial<ButtonProps>;
29
+ };
30
+
31
+ export function ReleaseNotesFooter({
32
+ surface,
33
+ total,
34
+ pageIndex,
35
+ readablePageNumber,
36
+ counterLabel,
37
+ readLaterLabel,
38
+ onReadLaterClick,
39
+ onPrevPageClick,
40
+ onNextPageClick,
41
+ readLaterButtonProps,
42
+ }: ReleaseNotesFooterProps) {
43
+ const hasPagination = total > 1;
44
+ const isModal = surface === 'modal';
45
+ const buttonSize = isModal ? 'm' : 'l';
46
+
47
+ return (
48
+ <div className={styles.root} data-surface={surface}>
49
+ <Button
50
+ view='function'
51
+ appearance='neutral'
52
+ size={buttonSize}
53
+ label={readLaterLabel}
54
+ data-test-id={TEST_IDS.releaseNotesReadLaterButton}
55
+ {...readLaterButtonProps}
56
+ onClick={onReadLaterClick}
57
+ />
58
+
59
+ {hasPagination && (
60
+ <div className={styles.controls}>
61
+ {!isModal && <span className={styles.counter}>{counterLabel}</span>}
62
+ <Button
63
+ view='outline'
64
+ appearance='neutral'
65
+ size={buttonSize}
66
+ icon={<ChevronLeftSVG />}
67
+ disabled={pageIndex === 0}
68
+ onClick={onPrevPageClick}
69
+ data-test-id={TEST_IDS.releaseNotesPrevButton}
70
+ />
71
+ <Button
72
+ view='outline'
73
+ appearance='neutral'
74
+ size={buttonSize}
75
+ icon={<ChevronRightSVG />}
76
+ disabled={readablePageNumber === total}
77
+ onClick={onNextPageClick}
78
+ data-test-id={TEST_IDS.releaseNotesNextButton}
79
+ />
80
+ </div>
81
+ )}
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1 @@
1
+ export * from './ReleaseNotesFooter';
@@ -0,0 +1,25 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+
3
+ .root {
4
+ position: relative;
5
+
6
+ display: flex;
7
+ column-gap: base.$sn-primitive-dimension-16;
8
+ align-items: center;
9
+ justify-content: space-between;
10
+
11
+ width: 100%;
12
+ }
13
+
14
+ .controls {
15
+ display: flex;
16
+ column-gap: base.$sn-primitive-dimension-8;
17
+ align-items: center;
18
+ }
19
+
20
+ .counter {
21
+ @include base.composite-var(base.$base-styles, 'sn', 'regular', 'body', 'm');
22
+
23
+ color: base.$sn-theme-color-available-version-textTertiary;
24
+ white-space: nowrap;
25
+ }
@@ -0,0 +1,6 @@
1
+ export * from './DesktopReleaseNotes';
2
+ export * from './InputConfirm';
3
+ export * from './MobileReleaseNotes';
4
+ export * from './NoteItem';
5
+ export * from './ReleaseNotesContent';
6
+ export * from './ReleaseNotesFooter';
package/src/hooks.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ type TextFieldValidationParams = {
4
+ target?: string;
5
+ errorText: string;
6
+ };
7
+
8
+ export function useTextFieldValidation({ target, errorText }: TextFieldValidationParams) {
9
+ const [value, setValue] = useState('');
10
+ const [error, setError] = useState('');
11
+
12
+ const handleChange = useCallback(
13
+ (nextValue: string) => {
14
+ setValue(nextValue);
15
+
16
+ if (target === nextValue) {
17
+ setError('');
18
+ }
19
+ },
20
+ [target],
21
+ );
22
+
23
+ const reset = useCallback(() => {
24
+ setValue('');
25
+ setError('');
26
+ }, []);
27
+
28
+ const validate = useCallback(() => {
29
+ if (!target || target === value) {
30
+ setError('');
31
+ return true;
32
+ }
33
+
34
+ setError(errorText);
35
+ return false;
36
+ }, [errorText, target, value]);
37
+
38
+ return { value, error, handleChange, reset, validate };
39
+ }
40
+
41
+ type ReleaseNotesNavigationParams = {
42
+ onClose(): void;
43
+ onReadLaterClick?(): void;
44
+ onSlideChange?(slide: number): void;
45
+ };
46
+
47
+ export function useReleaseNotesNavigation({ onClose, onReadLaterClick, onSlideChange }: ReleaseNotesNavigationParams) {
48
+ const [pageIndex, setPageIndex] = useState(0);
49
+
50
+ const setPage = useCallback(
51
+ (nextPage: number) => {
52
+ setPageIndex(nextPage);
53
+ onSlideChange?.(nextPage);
54
+ },
55
+ [onSlideChange],
56
+ );
57
+
58
+ const handleClose = useCallback(() => {
59
+ setPageIndex(0);
60
+ onClose();
61
+ }, [onClose]);
62
+
63
+ const handleReadLaterClick = useCallback(() => {
64
+ setPageIndex(0);
65
+ onReadLaterClick?.();
66
+ onClose();
67
+ }, [onClose, onReadLaterClick]);
68
+
69
+ const handleNextPageClick = useCallback(
70
+ (totalPages: number) => {
71
+ setPage(Math.min(totalPages - 1, pageIndex + 1));
72
+ },
73
+ [pageIndex, setPage],
74
+ );
75
+
76
+ const handlePrevPageClick = useCallback(() => {
77
+ setPage(Math.max(0, pageIndex - 1));
78
+ }, [pageIndex, setPage]);
79
+
80
+ return {
81
+ pageIndex,
82
+ readablePageNumber: pageIndex + 1,
83
+ setPage,
84
+ handleClose,
85
+ handleReadLaterClick,
86
+ handleNextPageClick,
87
+ handlePrevPageClick,
88
+ };
89
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './components';
2
+ export * from './constants';
3
+ export * from './types';
4
+ export * from './locale';
@@ -0,0 +1,77 @@
1
+ import { defineLocale, defineMessages } from '@cloud-ru/ds-locale';
2
+
3
+ const MODAL_PREDEFINED_MESSAGES = defineMessages({
4
+ 'en-GB': {
5
+ deleteModal: {
6
+ title: 'Delete {{objectType}}',
7
+ fallbackTitle: 'Delete object',
8
+ content: 'This action cannot be undone.',
9
+ confirmLabel: 'To allow deletion, enter:',
10
+ approve: 'Delete',
11
+ cancel: 'Cancel',
12
+ },
13
+ recallModal: {
14
+ title: 'Recall request',
15
+ content: 'This action cannot be undone.',
16
+ confirmLabel: 'To recall the request, enter:',
17
+ approve: 'Recall request',
18
+ cancel: 'Cancel',
19
+ },
20
+ confirm: {
21
+ nameLabel: 'Enter name to confirm',
22
+ textLabel: 'Enter text to confirm',
23
+ namePlaceholder: 'Name',
24
+ textPlaceholder: 'Text',
25
+ error: 'The entered value does not match the confirmation text',
26
+ },
27
+ releaseNotes: {
28
+ title: 'What is new',
29
+ readLater: 'Read later',
30
+ counter: '{{page}} of {{total}}',
31
+ noDataTitle: 'No news yet',
32
+ noDataDescription: 'Platform news will appear here soon',
33
+ errorTitle: 'Could not get data',
34
+ errorDescription: 'Try to reload the page',
35
+ retry: 'Refresh',
36
+ },
37
+ },
38
+ 'ru-RU': {
39
+ deleteModal: {
40
+ title: 'Удаление {{objectType}}',
41
+ fallbackTitle: 'Удалить объект',
42
+ content: 'Это действие нельзя отменить.',
43
+ confirmLabel: 'Чтобы разрешить удаление, введите:',
44
+ approve: 'Удалить',
45
+ cancel: 'Отмена',
46
+ },
47
+ recallModal: {
48
+ title: 'Отзыв заявки',
49
+ content: 'Это действие нельзя отменить.',
50
+ confirmLabel: 'Чтобы отозвать заявку, введите:',
51
+ approve: 'Отозвать заявку',
52
+ cancel: 'Отмена',
53
+ },
54
+ confirm: {
55
+ nameLabel: 'Введите название для подтверждения',
56
+ textLabel: 'Введите текст для подтверждения',
57
+ namePlaceholder: 'Введите название',
58
+ textPlaceholder: 'Текст',
59
+ error: 'Введенное значение не совпадает с текстом подтверждения',
60
+ },
61
+ releaseNotes: {
62
+ title: 'Что нового',
63
+ readLater: 'Ознакомиться позже',
64
+ counter: '{{page}} из {{total}}',
65
+ noDataTitle: 'Новостей пока нет',
66
+ noDataDescription: 'Скоро здесь появятся новости платформы',
67
+ errorTitle: 'Не удалось получить данные',
68
+ errorDescription: 'Попробуйте перезагрузить страницу',
69
+ retry: 'Обновить',
70
+ },
71
+ },
72
+ });
73
+
74
+ export type ModalPredefinedMessages = (typeof MODAL_PREDEFINED_MESSAGES)['en-GB'];
75
+
76
+ /** locale компонента ModalPredefined: `modalPredefinedLocale.useTranslations()` в коде, `.extend(...)` в сервисе. */
77
+ export const modalPredefinedLocale = defineLocale('@cloud-ru/ds-uikit-product-modal-predefined', MODAL_PREDEFINED_MESSAGES);