@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.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +359 -0
- package/dist/cjs/components/DeleteModal/DeleteModal.d.ts +2 -0
- package/dist/cjs/components/DeleteModal/DeleteModal.js +39 -0
- package/dist/cjs/components/DeleteModal/index.d.ts +1 -0
- package/dist/cjs/components/DeleteModal/index.js +17 -0
- package/dist/cjs/components/DeleteModal/styles.module.css +33 -0
- package/dist/cjs/components/RecallModal/RecallModal.d.ts +2 -0
- package/dist/cjs/components/RecallModal/RecallModal.js +37 -0
- package/dist/cjs/components/RecallModal/index.d.ts +1 -0
- package/dist/cjs/components/RecallModal/index.js +17 -0
- package/dist/cjs/components/RecallModal/styles.module.css +33 -0
- package/dist/cjs/components/ReleaseNotes/ReleaseNotes.d.ts +7 -0
- package/dist/cjs/components/ReleaseNotes/ReleaseNotes.js +15 -0
- package/dist/cjs/components/ReleaseNotes/index.d.ts +1 -0
- package/dist/cjs/components/ReleaseNotes/index.js +17 -0
- package/dist/cjs/components/index.d.ts +3 -0
- package/dist/cjs/components/index.js +19 -0
- package/dist/cjs/constants.d.ts +27 -0
- package/dist/cjs/constants.js +30 -0
- package/dist/cjs/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.d.ts +2 -0
- package/dist/cjs/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +21 -0
- package/dist/cjs/helperComponents/DesktopReleaseNotes/index.d.ts +1 -0
- package/dist/cjs/helperComponents/DesktopReleaseNotes/index.js +17 -0
- package/dist/cjs/helperComponents/DesktopReleaseNotes/styles.module.css +19 -0
- package/dist/cjs/helperComponents/InputConfirm/InputConfirm.d.ts +48 -0
- package/dist/cjs/helperComponents/InputConfirm/InputConfirm.js +15 -0
- package/dist/cjs/helperComponents/InputConfirm/index.d.ts +1 -0
- package/dist/cjs/helperComponents/InputConfirm/index.js +17 -0
- package/dist/cjs/helperComponents/InputConfirm/styles.module.css +38 -0
- package/dist/cjs/helperComponents/MobileReleaseNotes/MobileReleaseNotes.d.ts +2 -0
- package/dist/cjs/helperComponents/MobileReleaseNotes/MobileReleaseNotes.js +26 -0
- package/dist/cjs/helperComponents/MobileReleaseNotes/index.d.ts +1 -0
- package/dist/cjs/helperComponents/MobileReleaseNotes/index.js +17 -0
- package/dist/cjs/helperComponents/NoteItem/NoteItem.d.ts +6 -0
- package/dist/cjs/helperComponents/NoteItem/NoteItem.js +20 -0
- package/dist/cjs/helperComponents/NoteItem/index.d.ts +1 -0
- package/dist/cjs/helperComponents/NoteItem/index.js +17 -0
- package/dist/cjs/helperComponents/NoteItem/styles.module.css +53 -0
- package/dist/cjs/helperComponents/ReleaseNotesContent/ReleaseNotesContent.d.ts +28 -0
- package/dist/cjs/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +30 -0
- package/dist/cjs/helperComponents/ReleaseNotesContent/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ReleaseNotesContent/index.js +17 -0
- package/dist/cjs/helperComponents/ReleaseNotesContent/styles.module.css +40 -0
- package/dist/cjs/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.d.ts +25 -0
- package/dist/cjs/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +17 -0
- package/dist/cjs/helperComponents/ReleaseNotesFooter/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ReleaseNotesFooter/index.js +17 -0
- package/dist/cjs/helperComponents/ReleaseNotesFooter/styles.module.css +27 -0
- package/dist/cjs/helperComponents/index.d.ts +6 -0
- package/dist/cjs/helperComponents/index.js +22 -0
- package/dist/cjs/hooks.d.ts +26 -0
- package/dist/cjs/hooks.js +59 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/locale/index.d.ts +143 -0
- package/dist/cjs/locale/index.js +76 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +243 -0
- package/dist/cjs/types.d.ts +67 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/components/DeleteModal/DeleteModal.d.ts +2 -0
- package/dist/esm/components/DeleteModal/DeleteModal.js +33 -0
- package/dist/esm/components/DeleteModal/index.d.ts +1 -0
- package/dist/esm/components/DeleteModal/index.js +1 -0
- package/dist/esm/components/DeleteModal/styles.module.css +33 -0
- package/dist/esm/components/RecallModal/RecallModal.d.ts +2 -0
- package/dist/esm/components/RecallModal/RecallModal.js +31 -0
- package/dist/esm/components/RecallModal/index.d.ts +1 -0
- package/dist/esm/components/RecallModal/index.js +1 -0
- package/dist/esm/components/RecallModal/styles.module.css +33 -0
- package/dist/esm/components/ReleaseNotes/ReleaseNotes.d.ts +7 -0
- package/dist/esm/components/ReleaseNotes/ReleaseNotes.js +12 -0
- package/dist/esm/components/ReleaseNotes/index.d.ts +1 -0
- package/dist/esm/components/ReleaseNotes/index.js +1 -0
- package/dist/esm/components/index.d.ts +3 -0
- package/dist/esm/components/index.js +3 -0
- package/dist/esm/constants.d.ts +27 -0
- package/dist/esm/constants.js +27 -0
- package/dist/esm/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.d.ts +2 -0
- package/dist/esm/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.js +15 -0
- package/dist/esm/helperComponents/DesktopReleaseNotes/index.d.ts +1 -0
- package/dist/esm/helperComponents/DesktopReleaseNotes/index.js +1 -0
- package/dist/esm/helperComponents/DesktopReleaseNotes/styles.module.css +19 -0
- package/dist/esm/helperComponents/InputConfirm/InputConfirm.d.ts +48 -0
- package/dist/esm/helperComponents/InputConfirm/InputConfirm.js +9 -0
- package/dist/esm/helperComponents/InputConfirm/index.d.ts +1 -0
- package/dist/esm/helperComponents/InputConfirm/index.js +1 -0
- package/dist/esm/helperComponents/InputConfirm/styles.module.css +38 -0
- package/dist/esm/helperComponents/MobileReleaseNotes/MobileReleaseNotes.d.ts +2 -0
- package/dist/esm/helperComponents/MobileReleaseNotes/MobileReleaseNotes.js +23 -0
- package/dist/esm/helperComponents/MobileReleaseNotes/index.d.ts +1 -0
- package/dist/esm/helperComponents/MobileReleaseNotes/index.js +1 -0
- package/dist/esm/helperComponents/NoteItem/NoteItem.d.ts +6 -0
- package/dist/esm/helperComponents/NoteItem/NoteItem.js +14 -0
- package/dist/esm/helperComponents/NoteItem/index.d.ts +1 -0
- package/dist/esm/helperComponents/NoteItem/index.js +1 -0
- package/dist/esm/helperComponents/NoteItem/styles.module.css +53 -0
- package/dist/esm/helperComponents/ReleaseNotesContent/ReleaseNotesContent.d.ts +28 -0
- package/dist/esm/helperComponents/ReleaseNotesContent/ReleaseNotesContent.js +24 -0
- package/dist/esm/helperComponents/ReleaseNotesContent/index.d.ts +1 -0
- package/dist/esm/helperComponents/ReleaseNotesContent/index.js +1 -0
- package/dist/esm/helperComponents/ReleaseNotesContent/styles.module.css +40 -0
- package/dist/esm/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.d.ts +25 -0
- package/dist/esm/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.js +11 -0
- package/dist/esm/helperComponents/ReleaseNotesFooter/index.d.ts +1 -0
- package/dist/esm/helperComponents/ReleaseNotesFooter/index.js +1 -0
- package/dist/esm/helperComponents/ReleaseNotesFooter/styles.module.css +27 -0
- package/dist/esm/helperComponents/index.d.ts +6 -0
- package/dist/esm/helperComponents/index.js +6 -0
- package/dist/esm/hooks.d.ts +26 -0
- package/dist/esm/hooks.js +55 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/locale/index.d.ts +143 -0
- package/dist/esm/locale/index.js +73 -0
- package/dist/esm/style.css +243 -0
- package/dist/esm/types.d.ts +67 -0
- package/dist/esm/types.js +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +63 -0
- package/src/components/DeleteModal/DeleteModal.tsx +116 -0
- package/src/components/DeleteModal/index.ts +1 -0
- package/src/components/DeleteModal/styles.module.scss +37 -0
- package/src/components/RecallModal/RecallModal.tsx +111 -0
- package/src/components/RecallModal/index.ts +1 -0
- package/src/components/RecallModal/styles.module.scss +37 -0
- package/src/components/ReleaseNotes/ReleaseNotes.tsx +15 -0
- package/src/components/ReleaseNotes/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/constants.ts +29 -0
- package/src/helperComponents/DesktopReleaseNotes/DesktopReleaseNotes.tsx +85 -0
- package/src/helperComponents/DesktopReleaseNotes/index.ts +1 -0
- package/src/helperComponents/DesktopReleaseNotes/styles.module.scss +24 -0
- package/src/helperComponents/InputConfirm/InputConfirm.tsx +72 -0
- package/src/helperComponents/InputConfirm/index.ts +1 -0
- package/src/helperComponents/InputConfirm/styles.module.scss +30 -0
- package/src/helperComponents/MobileReleaseNotes/MobileReleaseNotes.tsx +88 -0
- package/src/helperComponents/MobileReleaseNotes/index.ts +1 -0
- package/src/helperComponents/NoteItem/NoteItem.tsx +58 -0
- package/src/helperComponents/NoteItem/index.ts +1 -0
- package/src/helperComponents/NoteItem/styles.module.scss +54 -0
- package/src/helperComponents/ReleaseNotesContent/ReleaseNotesContent.tsx +119 -0
- package/src/helperComponents/ReleaseNotesContent/index.ts +1 -0
- package/src/helperComponents/ReleaseNotesContent/styles.module.scss +45 -0
- package/src/helperComponents/ReleaseNotesFooter/ReleaseNotesFooter.tsx +84 -0
- package/src/helperComponents/ReleaseNotesFooter/index.ts +1 -0
- package/src/helperComponents/ReleaseNotesFooter/styles.module.scss +25 -0
- package/src/helperComponents/index.ts +6 -0
- package/src/hooks.ts +89 -0
- package/src/index.ts +4 -0
- package/src/locale/index.ts +77 -0
- 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
|
+
}
|
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,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);
|