From 7cf13874b6d1ebc8edaa5f7b149aa4a6482c26a3 Mon Sep 17 00:00:00 2001 From: 3y3 <3y3@ya.ru> Date: Fri, 8 Sep 2023 12:50:15 +0300 Subject: [PATCH] wip --- .../BookmarkButton/BookmarkButton.tsx | 15 +- src/components/Breadcrumbs/Breadcrumbs.tsx | 1 + src/components/Contributors/Contributors.tsx | 22 +- src/components/Control/Control.tsx | 55 +- src/components/Controls/Controls.tsx | 373 +++++------- src/components/Controls/contexts.ts | 16 + .../DividerControl/DividerControl.tsx | 17 +- .../Controls/single-controls/EditControl.tsx | 60 ++ .../single-controls/FullScreenControl.tsx | 45 +- .../Controls/single-controls/LangControl.tsx | 148 ++--- .../Controls/single-controls/PdfControl.tsx | 46 +- .../SettingsControl/SettingsControl.tsx | 71 +-- .../single-controls/SinglePageControl.tsx | 52 +- .../Controls/single-controls/index.ts | 1 + src/components/DocLayout/DocLayout.tsx | 5 +- .../DocLeadingPage/DocLeadingPage.tsx | 5 +- src/components/DocPage/DocPage.tsx | 66 +-- src/components/DocPageTitle/DocPageTitle.tsx | 13 +- src/components/EditButton/EditButton.scss | 8 - src/components/EditButton/EditButton.tsx | 45 -- src/components/EditButton/index.ts | 2 - src/components/ErrorPage/ErrorPage.tsx | 20 +- src/components/Feedback/Feedback.scss | 12 - src/components/Feedback/Feedback.tsx | 538 ++++-------------- .../Feedback/controls/DislikeControl.tsx | 58 ++ .../controls/DislikeVariantsPopup.tsx | 185 ++++++ .../Feedback/controls/LikeControl.tsx | 65 +++ .../Feedback/controls/SuccessPopup.tsx | 46 ++ src/components/MiniToc/MiniToc.tsx | 111 ++-- src/components/SearchBar/SearchBar.tsx | 26 +- src/components/SearchItem/SearchItem.tsx | 149 +++-- src/components/SearchPage/SearchPage.tsx | 113 ++-- src/components/Subscribe/Subscribe.tsx | 167 +++--- .../SubscribeSuccessPopup.tsx | 40 +- .../SubscribeVariantsPopup.tsx | 47 +- src/components/Toc/Toc.tsx | 6 +- src/components/TocItem/TocItem.tsx | 1 + src/components/TocNavPanel/TocNavPanel.tsx | 190 +++---- src/components/ToggleArrow/ToggleArrow.tsx | 1 + src/config/i18n.ts | 3 +- src/constants.ts | 16 +- src/hooks/index.ts | 3 +- src/hooks/useForkRef.ts | 25 - src/hooks/useIsomorphicLayoutEffect.ts | 5 - src/hooks/usePopper.ts | 1 + src/hooks/usePopupState.ts | 42 ++ src/hooks/useTimeout.ts | 22 - src/hooks/useTimer.ts | 19 + src/i18n/en.json | 6 + src/i18n/index.ts | 15 - src/i18n/ru.json | 6 + src/index.ts | 5 +- src/models/index.ts | 7 + 53 files changed, 1386 insertions(+), 1630 deletions(-) create mode 100644 src/components/Controls/contexts.ts create mode 100644 src/components/Controls/single-controls/EditControl.tsx delete mode 100644 src/components/EditButton/EditButton.scss delete mode 100644 src/components/EditButton/EditButton.tsx delete mode 100644 src/components/EditButton/index.ts create mode 100644 src/components/Feedback/controls/DislikeControl.tsx create mode 100644 src/components/Feedback/controls/DislikeVariantsPopup.tsx create mode 100644 src/components/Feedback/controls/LikeControl.tsx create mode 100644 src/components/Feedback/controls/SuccessPopup.tsx delete mode 100644 src/hooks/useForkRef.ts delete mode 100644 src/hooks/useIsomorphicLayoutEffect.ts create mode 100644 src/hooks/usePopupState.ts delete mode 100644 src/hooks/useTimeout.ts create mode 100644 src/hooks/useTimer.ts delete mode 100644 src/i18n/index.ts diff --git a/src/components/BookmarkButton/BookmarkButton.tsx b/src/components/BookmarkButton/BookmarkButton.tsx index 375e6669..f907a4f8 100644 --- a/src/components/BookmarkButton/BookmarkButton.tsx +++ b/src/components/BookmarkButton/BookmarkButton.tsx @@ -12,24 +12,21 @@ const b = block('dc-bookmark-button'); export interface BookmarkButtonProps { className?: string; - bookmarkedPage: boolean; - onChangeBookmarkPage: (value: boolean) => void; + isBookmarked: boolean; + onBookmark: (value: boolean) => void; } -export const BookmarkButton: React.FC = ({ - bookmarkedPage, - onChangeBookmarkPage, -}) => { +export const BookmarkButton: React.FC = ({isBookmarked, onBookmark}) => { return ( ); diff --git a/src/components/Breadcrumbs/Breadcrumbs.tsx b/src/components/Breadcrumbs/Breadcrumbs.tsx index c41bc5e7..658eeb45 100644 --- a/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -12,6 +12,7 @@ export interface BreadcrumbsProps { items: BreadcrumbItem[]; className?: string; } + export const Breadcrumbs: React.FC = ({items, className}) => { if (!items || !items.length) { return null; diff --git a/src/components/Contributors/Contributors.tsx b/src/components/Contributors/Contributors.tsx index f472c87c..7db8e145 100644 --- a/src/components/Contributors/Contributors.tsx +++ b/src/components/Contributors/Contributors.tsx @@ -1,16 +1,16 @@ -import React, {useEffect} from 'react'; +import React from 'react'; + import block from 'bem-cn-lite'; -import {useTranslation} from 'react-i18next'; +import {useTranslation} from '../../hooks'; +import {Contributor} from '../../models'; import {ContributorAvatars} from '../ContributorAvatars'; -import {Lang, Contributor} from '../../models'; import './Contributors.scss'; const b = block('contributors'); export interface ContributorsProps { - lang: Lang; users: Contributor[]; onlyAuthor?: boolean; isAuthor?: boolean; @@ -18,18 +18,8 @@ export interface ContributorsProps { } const Contributors: React.FC = (props) => { - const { - users, - lang, - onlyAuthor = false, - isAuthor = false, - translationName = 'contributors', - } = props; - const {t, i18n} = useTranslation(translationName); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); + const {users, onlyAuthor = false, isAuthor = false, translationName = 'contributors'} = props; + const {t} = useTranslation(translationName); return (
diff --git a/src/components/Control/Control.tsx b/src/components/Control/Control.tsx index 8ee101bf..340ff21f 100644 --- a/src/components/Control/Control.tsx +++ b/src/components/Control/Control.tsx @@ -1,9 +1,10 @@ -import React, {useCallback, useState, useRef} from 'react'; +import React, {forwardRef, useCallback, useImperativeHandle, useRef} from 'react'; + +import {Button, Popup} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {Popup, Button} from '@gravity-ui/uikit'; +import {PopperPosition, usePopupState} from '../../hooks'; import {ControlSizes} from '../../models'; -import {PopperPosition} from '../../hooks'; import './Control.scss'; @@ -30,23 +31,21 @@ const ICONS_SIZES = { [ControlSizes.L]: 20, }; -const Control = (props: ControlProps) => { +const Control = forwardRef((props: ControlProps, ref) => { const { onClick, className, tooltipText, isVerticalView, - setRef, size = ControlSizes.M, icon, popupPosition, } = props; const controlRef = useRef(null); - const [isVisibleTooltip, setIsVisibleTooltip] = useState(false); - const showTooltip = () => setIsVisibleTooltip(true); - const hideTooltip = () => setIsVisibleTooltip(false); + const popupState = usePopupState({autoclose: 3000}); + const getTooltipAlign = useCallback(() => { if (popupPosition) { return popupPosition; @@ -54,16 +53,8 @@ const Control = (props: ControlProps) => { return isVerticalView ? PopperPosition.LEFT_START : PopperPosition.BOTTOM_END; }, [isVerticalView, popupPosition]); - const _setRef = useCallback( - (ref: HTMLButtonElement) => { - controlRef.current = ref; - - if (setRef) { - setRef(ref); - } - }, - [setRef], - ); + + useImperativeHandle(ref, () => controlRef.current, [controlRef]); const position = getTooltipAlign(); const Icon = icon; @@ -74,9 +65,9 @@ const Control = (props: ControlProps) => { - - {tooltipText} - + {controlRef.current && ( + + {tooltipText} + + )} ); -}; +}); Control.displayName = 'DCControl'; diff --git a/src/components/Controls/Controls.tsx b/src/components/Controls/Controls.tsx index 6d350b6e..0ab6baf2 100644 --- a/src/components/Controls/Controls.tsx +++ b/src/components/Controls/Controls.tsx @@ -1,28 +1,30 @@ -import React from 'react'; +import React, {memo} from 'react'; + import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Control} from '../Control'; +import {PopperPosition} from '../../hooks'; +import {ControlSizes, FeedbackSendData, Lang, SubscribeData, TextSizes, Theme} from '../../models'; import {Feedback, FeedbackView} from '../Feedback'; import {Subscribe, SubscribeView} from '../Subscribe'; + +import {CommonPropsContext} from './contexts'; + import { + DividerControl, + EditControl, FullScreenControl, - SettingsControl, - SinglePageControl, LangControl, - DividerControl, PdfControl, + SettingsControl, + SinglePageControl, } from './'; -import {PopperPosition} from '../../hooks'; -import {Lang, TextSizes, Theme, FeedbackSendData, ControlSizes, SubscribeData} from '../../models'; -import EditIcon from '@gravity-ui/icons/svgs/pencil.svg'; import './Controls.scss'; const b = block('dc-controls'); export interface ControlsProps { - lang: Lang; + lang?: Lang; langs?: string[]; fullScreen?: boolean; singlePage?: boolean; @@ -32,7 +34,7 @@ export interface ControlsProps { textSize?: TextSizes; vcsUrl?: string; vcsType?: string; - showEditControl?: boolean; + hideEditControl?: boolean; isLiked?: boolean; isDisliked?: boolean; dislikeVariants?: string[]; @@ -53,207 +55,148 @@ export interface ControlsProps { popupPosition?: PopperPosition; } -type ControlsInnerProps = ControlsProps & WithTranslation & WithTranslationProps; - -class Controls extends React.Component { - componentDidUpdate(prevProps: ControlsProps) { - const {i18n, lang} = this.props; - - if (prevProps.lang !== lang) { - i18n.changeLanguage(lang); - } - } - - render() { - const {lang, i18n, className, isVerticalView} = this.props; - - if (i18n.language !== lang) { - i18n.changeLanguage(lang); - } - - return ( -
- {this.renderCommonControls()} - {this.renderEditLink()} - {this.renderFeedbackControls()} - {this.renderSubscribeControls()} -
- ); - } - - private renderEditLink() { - const { - vcsUrl, - vcsType, - showEditControl, - singlePage, - isVerticalView, - controlSize, - popupPosition, - t, - } = this.props; - - if (!showEditControl || singlePage) { - return null; - } - - return ( - - - - - - - ); - } - - private renderCommonControls() { - const { - fullScreen, - singlePage, - theme, - wideFormat, - showMiniToc, - textSize, - onChangeFullScreen, - onChangeTheme, - onChangeShowMiniToc, - onChangeTextSize, - onChangeWideFormat, - onChangeLang, - onChangeSinglePage, - isVerticalView, - controlSize, - lang, - langs, - popupPosition, - pdfLink, - } = this.props; - - return ( - - - - - - - - ); - } - - private renderFeedbackControls = () => { - const { - lang, - singlePage, - onSendFeedback, - isLiked, - isDisliked, - dislikeVariants, - isVerticalView, - hideFeedbackControls, - popupPosition, - } = this.props; - - if (singlePage || !onSendFeedback || hideFeedbackControls) { - return null; - } - - return ( - - - - - ); - }; - - private renderSubscribeControls = () => { - const {lang, singlePage, onSubscribe, isVerticalView, popupPosition} = this.props; - - if (singlePage || !onSubscribe) { - return null; - } - - return ( - - - - - ); - }; -} - -export default withTranslation('controls')(Controls); +type Defined = { + [P in keyof ControlsProps]-?: ControlsProps[P]; +}; + +const Controls = memo((props) => { + const { + className, + fullScreen, + singlePage, + theme, + wideFormat, + showMiniToc, + hideEditControl, + hideFeedbackControls, + textSize, + onChangeFullScreen, + onChangeTheme, + onChangeShowMiniToc, + onChangeTextSize, + onChangeWideFormat, + onChangeLang, + onChangeSinglePage, + onSendFeedback, + onSubscribe, + isVerticalView = false, + controlSize = ControlSizes.M, + popupPosition, + lang, + langs, + pdfLink, + vcsUrl, + vcsType, + isLiked, + isDisliked, + dislikeVariants, + } = props; + + const withFullscreenControl = Boolean(onChangeFullScreen); + const withSettingsControl = Boolean( + onChangeWideFormat || onChangeTheme || onChangeShowMiniToc || onChangeTextSize, + ); + const withLangControl = Boolean(lang && onChangeLang); + const withSinglePageControl = Boolean(onChangeSinglePage); + const withPdfControl = Boolean(pdfLink); + const withEditControl = Boolean(!singlePage && !hideEditControl && vcsUrl); + const withFeedbackControl = Boolean(!singlePage && !hideFeedbackControls && onSendFeedback); + const withSubscribeControls = Boolean(!singlePage && onSubscribe); + + const controls = [ + withFullscreenControl && ( + + ), + withSettingsControl && ( + + ), + withLangControl && ( + + ), + withSinglePageControl && ( + + ), + withPdfControl && , + '---', + withEditControl && ( + + ), + '---', + withFeedbackControl && ( + + ), + '---', + withSubscribeControls && ( + + ), + ] + .filter(Boolean) + .reduce((result, control, index, array) => { + if (control === '---') { + if (array[index - 1] && array[index + 1] && array[index + 1] !== '---') { + result.push( + , + ); + } + } else { + result.push(control as React.ReactElement); + } + + return result; + }, [] as React.ReactElement[]); + + return ( + +
{controls}
+
+ ); +}); + +Controls.displayName = 'DCControls'; + +export default Controls; diff --git a/src/components/Controls/contexts.ts b/src/components/Controls/contexts.ts new file mode 100644 index 00000000..c7bb390d --- /dev/null +++ b/src/components/Controls/contexts.ts @@ -0,0 +1,16 @@ +import {createContext} from 'react'; + +import {PopperPosition} from '../../hooks'; +import {ControlSizes} from '../../models'; + +export const CommonPropsContext = createContext<{ + isVerticalView?: boolean; + controlClassName?: string; + controlSize?: ControlSizes; + popupPosition?: PopperPosition; +}>({ + controlClassName: '', + isVerticalView: false, + controlSize: ControlSizes.M, + // popupPosition?: PopperPosition; +}); diff --git a/src/components/Controls/single-controls/DividerControl/DividerControl.tsx b/src/components/Controls/single-controls/DividerControl/DividerControl.tsx index 9c58c159..963f37d1 100644 --- a/src/components/Controls/single-controls/DividerControl/DividerControl.tsx +++ b/src/components/Controls/single-controls/DividerControl/DividerControl.tsx @@ -1,24 +1,21 @@ -import React from 'react'; +import React, {useContext} from 'react'; + import cn from 'bem-cn-lite'; -import {ControlSizes} from '../../../../models'; +import {CommonPropsContext} from '../../contexts'; import './DividerControl.scss'; const b = cn('dc-divider-control'); interface DividerControlProps { - size?: ControlSizes; className?: string; - isVerticalView?: boolean; } -const DividerControl = ({ - size = ControlSizes.M, - className, - isVerticalView = true, -}: DividerControlProps) => { - return
; +const DividerControl: React.FC = ({className}) => { + const {isVerticalView, controlSize} = useContext(CommonPropsContext); + + return
; }; export default DividerControl; diff --git a/src/components/Controls/single-controls/EditControl.tsx b/src/components/Controls/single-controls/EditControl.tsx new file mode 100644 index 00000000..27806ae2 --- /dev/null +++ b/src/components/Controls/single-controls/EditControl.tsx @@ -0,0 +1,60 @@ +import React, {memo, useContext} from 'react'; + +import {Button, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import {useTranslation} from '../../../hooks'; +import {Control} from '../../Control'; +import {CommonPropsContext} from '../contexts'; + +import EditIcon from '@gravity-ui/icons/svgs/pencil.svg'; + +interface EditControlProps { + vcsUrl: string; + vcsType?: string; + view?: string; + className?: string; +} + +const b = block('dc-controls'); + +const EditControl = memo(({vcsUrl, vcsType = 'github', view, className}) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + + if (view === 'wide') { + return ( + + + + ); + } + + return ( + + + + ); +}); + +EditControl.displayName = 'EditControl'; + +export default EditControl; diff --git a/src/components/Controls/single-controls/FullScreenControl.tsx b/src/components/Controls/single-controls/FullScreenControl.tsx index df913e4b..fc7a35ca 100644 --- a/src/components/Controls/single-controls/FullScreenControl.tsx +++ b/src/components/Controls/single-controls/FullScreenControl.tsx @@ -1,27 +1,24 @@ -import React, {useCallback, useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useCallback, useContext, useEffect} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; +import {CommonPropsContext} from '../contexts'; -import FullScreenClickedIcon from '../../../../assets/icons/full-screen-clicked.svg'; +import FullScreenClickedIcon from '@gravity-ui/icons/svgs/square-dashed-circle.svg'; import FullScreenIcon from '@gravity-ui/icons/svgs/square-dashed.svg'; interface ControlProps { - lang: Lang; value?: boolean; onChange?: (value: boolean) => void; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const FullScreenControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, value, onChange, lang, popupPosition, i18n, t} = props; +const FullScreenControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {value, onChange} = props; const onClick = useCallback(() => { if (onChange) { @@ -46,29 +43,23 @@ const FullScreenControl = (props: ControlInnerProps) => { }; }, [onKeyDown]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - const activeMode = value ? 'enabled' : 'disabled'; - if (!onChange) { - return null; - } - return ( ( - + )} popupPosition={popupPosition} /> ); -}; +}); + +FullScreenControl.displayName = 'FullScreenControl'; -export default withTranslation('controls')(FullScreenControl); +export default FullScreenControl; diff --git a/src/components/Controls/single-controls/LangControl.tsx b/src/components/Controls/single-controls/LangControl.tsx index 8fe65c8a..3d4fa0c6 100644 --- a/src/components/Controls/single-controls/LangControl.tsx +++ b/src/components/Controls/single-controls/LangControl.tsx @@ -1,18 +1,25 @@ -import React, {useCallback, useEffect, useState, useRef} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import allLangs from 'langs'; -import {Popup, Icon as IconComponent, List} from '@gravity-ui/uikit'; +import React, {useCallback, useContext, useMemo, useRef} from 'react'; + +import {Icon, List, Popup} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; +import allLangs from 'langs'; +import {usePopupState, useTranslation} from '../../../hooks'; +import {Lang} from '../../../models'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; +import {CommonPropsContext} from '../contexts'; + import {getPopupPosition} from './utils'; -import {PopperPosition} from '../../../hooks'; import LangIcon from '@gravity-ui/icons/svgs/globe.svg'; import '../Controls.scss'; +const ICONS: Record = { + en: '🇬🇧', + ru: '🇷🇺', +}; +const DEFAULT_LANGS = ['en', 'ru']; const LEGACY_LANG_ITEMS = [ {value: Lang.En, text: 'English', icon: '🇬🇧'}, {value: Lang.Ru, text: 'Русский', icon: '🇷🇺'}, @@ -23,11 +30,7 @@ const b = block('dc-controls'); interface ControlProps { lang: Lang; langs?: string[]; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - onChangeLang?: (lang: Lang) => void; - popupPosition?: PopperPosition; + onChangeLang: (lang: Lang) => void; } interface ListItem { @@ -38,37 +41,16 @@ interface ListItem { const LIST_ITEM_HEIGHT = 36; -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const LangControl = (props: ControlInnerProps) => { - const { - className, - isVerticalView, - size, - lang, - langs = [], - i18n, - onChangeLang, - popupPosition, - t, - } = props; - - const [langItems, setLangItems] = useState(LEGACY_LANG_ITEMS); +const LangControl = (props: ControlProps) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {lang, langs = DEFAULT_LANGS, onChangeLang} = props; + const controlRef = useRef(null); - const [isVisiblePopup, setIsVisiblePopup] = useState(false); - const showPopup = () => setIsVisiblePopup(true); - const hidePopup = () => setIsVisiblePopup(false); - - const _onChangeLang = useCallback( - (value: Lang) => { - if (onChangeLang) { - onChangeLang(value); - } - }, - [onChangeLang], - ); - useEffect(() => { + const popupState = usePopupState(); + const langItems = useMemo(() => { const preparedLangs = langs .map((code) => { const langData = allLangs.where('1', code); @@ -77,29 +59,27 @@ const LangControl = (props: ControlInnerProps) => { ? { text: langData.name, value: langData['1'], + icon: ICONS[code] || '', } : undefined; }) .filter(Boolean) as ListItem[]; - if (preparedLangs.length) { - setLangItems(preparedLangs); - } else { - setLangItems(LEGACY_LANG_ITEMS); - } + return preparedLangs.length ? preparedLangs : LEGACY_LANG_ITEMS; }, [langs]); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setRef = useCallback((ref: HTMLButtonElement) => { - controlRef.current = ref; + const renderItem = useCallback((item: ListItem) => { + return ( +
+
{item.icon}
{item.text} +
+ ); }, []); - - if (!onChangeLang) { - return null; - } + const onItemClick = useCallback( + (item: ListItem) => { + onChangeLang(item.value as Lang); + }, + [onChangeLang], + ); const itemsHeight = LIST_ITEM_HEIGHT * langItems.length; const selectedItemIndex = langItems.findIndex(({value}) => value === lang); @@ -107,42 +87,36 @@ const LangControl = (props: ControlInnerProps) => { return ( } - setRef={setRef} + icon={(args) => } popupPosition={popupPosition} /> - - { - _onChangeLang(item.value as Lang); - }} - selectedItemIndex={selectedItemIndex} - itemHeight={LIST_ITEM_HEIGHT} - itemsHeight={itemsHeight} - renderItem={(item) => { - return ( -
-
{item.icon}
{item.text} -
- ); - }} - /> -
+ {popupState.visible && ( + + + + )}
); }; -export default withTranslation('controls')(LangControl); +export default LangControl; diff --git a/src/components/Controls/single-controls/PdfControl.tsx b/src/components/Controls/single-controls/PdfControl.tsx index 1b5a4acb..51c27cb0 100644 --- a/src/components/Controls/single-controls/PdfControl.tsx +++ b/src/components/Controls/single-controls/PdfControl.tsx @@ -1,47 +1,37 @@ -import React, {useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useContext} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; -import {PopperPosition} from '../../../hooks'; +import {CommonPropsContext} from '../contexts'; import PdfIcon from '../../../../assets/icons/pdf.svg'; interface ControlProps { - lang: Lang; - pdfLink?: string; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; + pdfLink: string; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const PdfControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, pdfLink, lang, i18n, popupPosition, t} = props; - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - if (!pdfLink) { - return null; - } +const PdfControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {pdfLink} = props; return ( } + icon={(args) => } popupPosition={popupPosition} /> ); -}; +}); + +PdfControl.displayName = 'PdfControl'; -export default withTranslation('controls')(PdfControl); +export default PdfControl; diff --git a/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx b/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx index 7a04f6e3..89fee568 100644 --- a/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx +++ b/src/components/Controls/single-controls/SettingsControl/SettingsControl.tsx @@ -1,12 +1,12 @@ -import React, {useCallback, useEffect, useState, useRef} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; +import React, {ReactElement, useCallback, useContext, useRef, useState} from 'react'; + +import {Button, Icon, List, Popup, Switch} from '@gravity-ui/uikit'; import cn from 'bem-cn-lite'; -import {Button, Popup, Switch, List, Icon as IconComponent} from '@gravity-ui/uikit'; +import {useTranslation} from '../../../../hooks'; +import {TextSizes, Theme} from '../../../../models'; import {Control} from '../../../Control'; -import {ControlSizes, Lang, TextSizes, Theme} from '../../../../models'; -import {PopperPosition} from '../../../../hooks'; - +import {CommonPropsContext} from '../../contexts'; import {getPopupPosition} from '../utils'; import SettingsIcon from '@gravity-ui/icons/svgs/gear.svg'; @@ -23,32 +23,23 @@ interface ControlProps { showMiniToc?: boolean; theme?: Theme; textSize?: TextSizes; - lang: Lang; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; onChangeWideFormat?: (value: boolean) => void; onChangeShowMiniToc?: (value: boolean) => void; onChangeTheme?: (theme: Theme) => void; onChangeTextSize?: (textSize: TextSizes) => void; - popupPosition?: PopperPosition; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - interface SettingControlItem { text: string; description: string; - control: Element; + control: ReactElement; } -const SettingsControl = (props: ControlInnerProps) => { +const SettingsControl = (props: ControlProps) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); const { - className, - isVerticalView, - size, - lang, - i18n, textSize, theme, wideFormat, @@ -59,8 +50,6 @@ const SettingsControl = (props: ControlInnerProps) => { onChangeWideFormat, onChangeShowMiniToc, onChangeTextSize, - popupPosition, - t, } = props; const controlRef = useRef(null); @@ -68,13 +57,11 @@ const SettingsControl = (props: ControlInnerProps) => { const showPopup = () => setIsVisiblePopup(true); const hidePopup = () => setIsVisiblePopup(false); - const makeOnChangeTextSize = useCallback( - (textSizeKey) => () => { + const _onChangeTextSize = useCallback((textSizeKey: TextSizes) => () => { if (onChangeTextSize) { onChangeTextSize(textSizeKey); } - }, - [onChangeTextSize], + }, [onChangeTextSize], ); const _onChangeTheme = useCallback(() => { if (onChangeTheme) { @@ -142,7 +129,7 @@ const SettingsControl = (props: ControlInnerProps) => { [textSizeKey]: true, })} view="flat" - onClick={makeOnChangeTextSize(textSizeKey)} + onClick={_onChangeTextSize(textSizeKey)} > { showMiniToc, fullScreen, singlePage, - onChangeTheme, - onChangeWideFormat, - onChangeShowMiniToc, _onChangeTheme, _onChangeWideFormat, _onChangeShowMiniToc, - onChangeTextSize, - makeOnChangeTextSize, + _onChangeTextSize, ]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setRef = useCallback((ref: HTMLButtonElement) => { - controlRef.current = ref; - }, []); - - if (!(onChangeWideFormat || onChangeTheme || onChangeShowMiniToc || onChangeTextSize)) { - return null; - } - const settingsItems = getSettingsItems(); return ( } + icon={(args) => } /> { itemHeight={ITEM_HEIGHT} itemsHeight={ITEM_HEIGHT * settingsItems.length} filterable={false} - renderItem={(item) => { + renderItem={(item: SettingControlItem) => { return (
@@ -234,4 +205,4 @@ const SettingsControl = (props: ControlInnerProps) => { ); }; -export default withTranslation('controls')(SettingsControl); +export default SettingsControl; diff --git a/src/components/Controls/single-controls/SinglePageControl.tsx b/src/components/Controls/single-controls/SinglePageControl.tsx index bed7cd43..170cff1e 100644 --- a/src/components/Controls/single-controls/SinglePageControl.tsx +++ b/src/components/Controls/single-controls/SinglePageControl.tsx @@ -1,58 +1,46 @@ -import React, {useCallback, useEffect} from 'react'; -import {WithTranslation, withTranslation, WithTranslationProps} from 'react-i18next'; -import {Icon as IconComponent} from '@gravity-ui/uikit'; +import React, {memo, useCallback, useContext} from 'react'; +import {Icon} from '@gravity-ui/uikit'; + +import {useTranslation} from '../../../hooks'; import {Control} from '../../Control'; -import {ControlSizes, Lang} from '../../../models'; -import {PopperPosition} from '../../../hooks'; +import {CommonPropsContext} from '../contexts'; -import SinglePageIcon from '../../../../assets/icons/single-page.svg'; import SinglePageClickedIcon from '../../../../assets/icons/single-page-clicked.svg'; +import SinglePageIcon from '../../../../assets/icons/single-page.svg'; interface ControlProps { - lang: Lang; value?: boolean; - onChange?: (value: boolean) => void; - isVerticalView?: boolean; - className?: string; - size?: ControlSizes; - popupPosition?: PopperPosition; + onChange: (value: boolean) => void; } -type ControlInnerProps = ControlProps & WithTranslation & WithTranslationProps; - -const SinglePageControl = (props: ControlInnerProps) => { - const {className, isVerticalView, size, value, onChange, lang, i18n, popupPosition, t} = props; +const SinglePageControl = memo((props) => { + const {t} = useTranslation('controls'); + const {controlClassName, controlSize, isVerticalView, popupPosition} = + useContext(CommonPropsContext); + const {value, onChange} = props; const onClick = useCallback(() => { - if (onChange) { - onChange(!value); - } + onChange(!value); }, [value, onChange]); - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - const activeMode = value ? 'enabled' : 'disabled'; - if (!onChange) { - return null; - } - return ( ( - + )} popupPosition={popupPosition} /> ); -}; +}); + +SinglePageControl.displayName = 'SinglePageControl'; -export default withTranslation('controls')(SinglePageControl); +export default SinglePageControl; diff --git a/src/components/Controls/single-controls/index.ts b/src/components/Controls/single-controls/index.ts index b0c435a8..e3211962 100644 --- a/src/components/Controls/single-controls/index.ts +++ b/src/components/Controls/single-controls/index.ts @@ -4,3 +4,4 @@ export {default as SinglePageControl} from './SinglePageControl'; export {default as LangControl} from './LangControl'; export {default as DividerControl} from './DividerControl/DividerControl'; export {default as PdfControl} from './PdfControl'; +export {default as EditControl} from './EditControl'; diff --git a/src/components/DocLayout/DocLayout.tsx b/src/components/DocLayout/DocLayout.tsx index ad9ee9f7..6e8bbc70 100644 --- a/src/components/DocLayout/DocLayout.tsx +++ b/src/components/DocLayout/DocLayout.tsx @@ -2,7 +2,7 @@ import React, {PropsWithChildren, ReactElement} from 'react'; import block from 'bem-cn-lite'; -import {TocData, Router, Lang} from '../../models'; +import {Router, TocData} from '../../models'; import {getStateKey} from '../../utils'; import {Toc} from '../Toc'; @@ -17,7 +17,6 @@ const Right: React.FC = () => null; export interface DocLayoutProps { toc: TocData; router: Router; - lang: Lang; children: (ReactElement | null)[] | ReactElement; fullScreen?: boolean; hideRight?: boolean; @@ -104,7 +103,6 @@ export class DocLayout extends React.Component { wideFormat, hideTocHeader, hideToc, - lang, singlePage, onChangeSinglePage, pdfLink, @@ -124,7 +122,6 @@ export class DocLayout extends React.Component { headerHeight={headerHeight} tocTitleIcon={tocTitleIcon} hideTocHeader={hideTocHeader} - lang={lang} singlePage={singlePage} onChangeSinglePage={onChangeSinglePage} pdfLink={pdfLink} diff --git a/src/components/DocLeadingPage/DocLeadingPage.tsx b/src/components/DocLeadingPage/DocLeadingPage.tsx index eea825f7..786bd58e 100644 --- a/src/components/DocLeadingPage/DocLeadingPage.tsx +++ b/src/components/DocLeadingPage/DocLeadingPage.tsx @@ -2,8 +2,8 @@ import React from 'react'; import block from 'bem-cn-lite'; -import {DocLeadingPageData, DocLeadingLinks, Router, Lang} from '../../models'; import {DEFAULT_SETTINGS} from '../../constants'; +import {DocLeadingLinks, DocLeadingPageData, Router} from '../../models'; import {DocLayout} from '../DocLayout'; import {DocPageTitle} from '../DocPageTitle'; import {HTML} from '../HTML'; @@ -17,7 +17,6 @@ const {wideFormat: defaultWideFormat} = DEFAULT_SETTINGS; export interface DocLeadingPageProps extends DocLeadingPageData { router: Router; - lang: Lang; headerHeight?: number; wideFormat?: boolean; hideTocHeader?: boolean; @@ -106,7 +105,6 @@ export const DocLeadingPage: React.FC = ({ data: {title, description, links}, toc, router, - lang, headerHeight, wideFormat = defaultWideFormat, hideTocHeader, @@ -122,7 +120,6 @@ export const DocLeadingPage: React.FC = ({ { const { toc, router, - lang, headerHeight, wideFormat, fullScreen, @@ -155,7 +154,6 @@ class DocPage extends React.Component { { } private addLinksToOriginalArticle = () => { - const {singlePage, lang, convertPathToOriginalArticle, generatePathToVcs} = this.props; + const {singlePage, convertPathToOriginalArticle, generatePathToVcs, vcsType} = this.props; if (singlePage) { const elements = document.querySelectorAll('[data-original-article]'); @@ -291,7 +289,12 @@ class DocPage extends React.Component { const vcsHref = callSafe(generatePathToVcs, href); const linkToVcs = createElementFromHTML( ReactDOMServer.renderToStaticMarkup( - , + , ), ); linkWrapperEl.append(linkToVcs); @@ -336,19 +339,21 @@ class DocPage extends React.Component { private renderTitle() { const {title, meta, bookmarkedPage, onChangeBookmarkPage} = this.props; + const withBookmarks = onChangeBookmarkPage; if (!title) { return null; } return ( - + {title} + {withBookmarks && ( + + )} ); } @@ -373,7 +378,7 @@ class DocPage extends React.Component { } private renderAuthor(onlyAuthor: boolean) { - const {meta, lang} = this.props; + const {meta} = this.props; if (!isContributor(meta?.author)) { return null; @@ -381,7 +386,6 @@ class DocPage extends React.Component { return ( { } private renderContributors() { - const {meta, lang} = this.props; + const {meta} = this.props; if (!meta?.contributors || meta.contributors.length === 0) { return null; } - return ; + return ; } private renderContentMiniToc() { @@ -463,7 +467,7 @@ class DocPage extends React.Component { } private renderAsideMiniToc() { - const {headings, router, headerHeight, lang} = this.props; + const {headings, router, headerHeight} = this.props; const {keyDOM} = this.state; return ( @@ -472,7 +476,6 @@ class DocPage extends React.Component { headings={headings} router={router} headerHeight={headerHeight} - lang={lang} key={keyDOM} />
@@ -480,29 +483,16 @@ class DocPage extends React.Component { } private renderFeedback() { - const { - toc, - lang, - singlePage, - isLiked, - isDisliked, - onSendFeedback, - dislikeVariants, - hideFeedbackControls, - } = this.props; + const {toc, isLiked, isDisliked, onSendFeedback, dislikeVariants, hideFeedbackControls} = + this.props; - if (!toc || toc.singlePage || hideFeedbackControls) { + if (!toc || toc.singlePage || hideFeedbackControls || !onSendFeedback) { return null; } - const isVerticalView = this.getIsVerticalView(); - return (
{ } private renderTocNavPanel() { - const {toc, router, fullScreen, lang} = this.props; + const {toc, router, fullScreen} = this.props; if (!toc || toc.singlePage) { return null; @@ -523,7 +513,6 @@ class DocPage extends React.Component { return ( { onClickPrevSearch, onClickNextSearch, onCloseSearchBar, - lang, singlePage, } = this.props; @@ -567,7 +555,6 @@ class DocPage extends React.Component { return (
{ return null; } - const showEditControl = !hideEditControl && !fullScreen && this.isEditable(); const isVerticalView = this.getIsVerticalView(); return ( @@ -630,7 +616,7 @@ class DocPage extends React.Component { isLiked={isLiked} isDisliked={isDisliked} dislikeVariants={dislikeVariants} - showEditControl={showEditControl} + hideEditControl={hideEditControl || fullScreen || !this.isEditable()} onChangeFullScreen={onChangeFullScreen} onChangeWideFormat={onChangeWideFormat} onChangeShowMiniToc={onChangeShowMiniToc} diff --git a/src/components/DocPageTitle/DocPageTitle.tsx b/src/components/DocPageTitle/DocPageTitle.tsx index 82a6f6d1..a0521a90 100644 --- a/src/components/DocPageTitle/DocPageTitle.tsx +++ b/src/components/DocPageTitle/DocPageTitle.tsx @@ -2,7 +2,7 @@ import React, {PropsWithChildren} from 'react'; import block from 'bem-cn-lite'; -import {BookmarkButton} from '../BookmarkButton'; +import {StageLabel, StageType} from '../StageLabel'; import './DocPageTitle.scss'; @@ -11,16 +11,12 @@ const b = block('dc-doc-page-title'); export interface DocPageTitleProps { stage?: string; className?: string; - bookmarkedPage?: boolean; - onChangeBookmarkPage?: (value: boolean) => void; } export const DocPageTitle: React.FC> = ({ children, stage, className, - bookmarkedPage, - onChangeBookmarkPage, }) => { const visibleStage = stage === 'tech-preview' ? 'preview' : (stage as StageType | undefined); const label = ; @@ -29,13 +25,6 @@ export const DocPageTitle: React.FC> = ({

{label} {children} - {typeof bookmarkedPage !== 'undefined' && - typeof onChangeBookmarkPage !== 'undefined' && ( - - )}

); }; diff --git a/src/components/EditButton/EditButton.scss b/src/components/EditButton/EditButton.scss deleted file mode 100644 index 13c046e1..00000000 --- a/src/components/EditButton/EditButton.scss +++ /dev/null @@ -1,8 +0,0 @@ -.dc-edit-button { - position: absolute; - right: 0; - - &__text { - margin-left: 8px; - } -} diff --git a/src/components/EditButton/EditButton.tsx b/src/components/EditButton/EditButton.tsx deleted file mode 100644 index 0d8e188a..00000000 --- a/src/components/EditButton/EditButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Button, Icon as IconComponent} from '@gravity-ui/uikit'; - - -import {Lang} from '../../models'; -import EditIcon from '@gravity-ui/icons/svgs/pencil.svg'; - -import './EditButton.scss'; - -const b = block('dc-edit-button'); - -export interface EditButtonProps { - lang: Lang; - href: string; -} - -type EditButtonInnerProps = EditButtonProps & WithTranslation & WithTranslationProps; - -class EditButton extends React.Component { - componentDidUpdate(prevProps: EditButtonProps) { - const {i18n, lang} = this.props; - if (prevProps.lang !== lang) { - i18n.changeLanguage(lang); - } - } - - render() { - const {t, href} = this.props; - - return ( - - - - ); - } -} - -export default withTranslation('controls')(EditButton); diff --git a/src/components/EditButton/index.ts b/src/components/EditButton/index.ts deleted file mode 100644 index 77e2f618..00000000 --- a/src/components/EditButton/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {default as EditButton} from './EditButton'; -export * from './EditButton'; diff --git a/src/components/ErrorPage/ErrorPage.tsx b/src/components/ErrorPage/ErrorPage.tsx index 980ea549..6277d29f 100644 --- a/src/components/ErrorPage/ErrorPage.tsx +++ b/src/components/ErrorPage/ErrorPage.tsx @@ -2,10 +2,9 @@ import React from 'react'; import {Button, Link} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Lang} from '../../models'; import {ERROR_CODES} from '../../constants'; +import {useTranslation} from '../../hooks'; import './ErrorPage.scss'; @@ -13,29 +12,20 @@ const b = block('ErrorPage'); export interface ErrorPageProps { code?: number; - lang?: Lang; pageGroup?: string; homeUrl?: string; receiveAccessUrl?: string; } -type ErrorPagePropsInnerProps = ErrorPageProps & WithTranslation & WithTranslationProps; - -const ErrorPage = ({ +const ErrorPage: React.FC = ({ code = 500, - lang = Lang.En, - i18n, - t, homeUrl, receiveAccessUrl, pageGroup, -}: ErrorPagePropsInnerProps): JSX.Element => { - if (i18n.language !== lang) { - i18n.changeLanguage(lang); - } - +}) => { let title; let description; + const {t} = useTranslation('error'); const href = homeUrl || '/'; const homeLink = ( @@ -91,4 +81,4 @@ const ErrorPage = ({ ); }; -export default withTranslation('error')(ErrorPage); +export default ErrorPage; diff --git a/src/components/Feedback/Feedback.scss b/src/components/Feedback/Feedback.scss index b3bea843..92469aaf 100644 --- a/src/components/Feedback/Feedback.scss +++ b/src/components/Feedback/Feedback.scss @@ -39,12 +39,6 @@ $popupWidth: 320px; } } - &__like-button { - &_active { - color: var(--g-color-base-brand); - } - } - &__success-popup { padding: $popupPadding 50px $popupPadding $popupPadding; width: $popupWidth; @@ -145,10 +139,4 @@ $popupWidth: 320px; } } } - - &__feedback-button { - &_active { - color: var(--g-color-base-brand); - } - } } diff --git a/src/components/Feedback/Feedback.tsx b/src/components/Feedback/Feedback.tsx index 08ef49f3..b091f0d1 100644 --- a/src/components/Feedback/Feedback.tsx +++ b/src/components/Feedback/Feedback.tsx @@ -1,15 +1,14 @@ -import React, {useCallback, useState, useEffect, useRef} from 'react'; +import React, {PropsWithChildren, useCallback, useEffect, useRef, useState} from 'react'; + import block from 'bem-cn-lite'; -import {withTranslation, WithTranslation, WithTranslationProps} from 'react-i18next'; -import {Checkbox, Popup, TextArea, Button, Icon as IconComponent} from '@gravity-ui/uikit'; -import {Control} from '../Control'; -import {PopperPosition} from '../../hooks'; -import {FeedbackSendData, FeedbackType, Lang} from '../../models'; -import {DISLIKE_VARIANTS} from '../../constants'; +import {usePopupState, useTranslation} from '../../hooks'; +import {FeedbackSendData, FeedbackType} from '../../models'; -import DislikeActiveIcon from '@gravity-ui/icons/svgs/thumbs-down-fill.svg'; -import DislikeIcon from '@gravity-ui/icons/svgs/thumbs-down.svg'; +import DislikeControl from './controls/DislikeControl'; +import DislikeVariantsPopup, {FormData} from './controls/DislikeVariantsPopup'; +import LikeControl from './controls/LikeControl'; +import SuccessPopup from './controls/SuccessPopup'; import './Feedback.scss'; @@ -21,455 +20,144 @@ export enum FeedbackView { } export interface FeedbackProps { - lang: Lang; - singlePage?: boolean; isLiked?: boolean; isDisliked?: boolean; dislikeVariants?: string[]; - isVerticalView?: boolean; - onSendFeedback?: (data: FeedbackSendData) => void; + onSendFeedback: (data: FeedbackSendData) => void; view?: FeedbackView; - classNameControl?: string; - popupPosition?: PopperPosition; -} - -interface FeedbackCheckboxes { - [key: string]: boolean; } -type FeedbackInnerProps = FeedbackProps & WithTranslation & WithTranslationProps; +const getInnerState = (isLiked: boolean, isDisliked: boolean) => { + return isDisliked + ? FeedbackType.dislike + : isLiked + ? FeedbackType.like + : FeedbackType.indeterminate; +}; -const Feedback: React.FC = (props) => { +const Feedback: React.FC = (props) => { + const {i18n} = useTranslation('feedback-variants'); const { - lang, - singlePage, - isLiked, - isDisliked, - dislikeVariants = DISLIKE_VARIANTS[lang], - isVerticalView, + isLiked = false, + isDisliked = false, onSendFeedback, - view, - classNameControl, - i18n, - popupPosition, - t, + dislikeVariants = i18n.getResourceBundle(i18n.language, 'feedback-variants'), + view = FeedbackView.Regular, } = props; const likeControlRef = useRef(null); const dislikeControlRef = useRef(null); - const timerId = useRef(); - const timeout = 3000; - - const [innerIsDisliked, setInnerIsDisliked] = useState(isDisliked); - const [feedbackComment, setFeedbackComment] = useState(''); - const [feedbackCheckboxes, setFeedbackCheckboxes] = useState({} as FeedbackCheckboxes); - const [showLikeSuccessPopup, setShowLikeSuccessPopup] = useState(false); - const [showDislikeSuccessPopup, setShowDislikeSuccessPopup] = useState(false); - const [showDislikeVariantsPopup, setShowDislikeVariantsPopup] = useState(false); - - const hideFeedbackPopups = useCallback(() => { - setShowDislikeSuccessPopup(false); - setShowLikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - }, []); - - const resetFeedbackAdditionalInfo = useCallback(() => { - setFeedbackComment(''); - setFeedbackCheckboxes({}); - }, []); - - useEffect(() => { - setInnerIsDisliked(isDisliked); - }, [isDisliked]); - - useEffect(() => { - i18n.changeLanguage(lang); - }, [i18n, lang]); - - const setTimer = useCallback((callback: () => void) => { - timerId.current = setTimeout(async () => { - callback(); - }, timeout); - }, []); - - const clearTimer = useCallback(() => { - clearTimeout(timerId.current as number); - timerId.current = undefined; - }, []); + const [innerState, setInnerState] = useState(getInnerState(isLiked, isDisliked)); useEffect(() => { - if (showLikeSuccessPopup || showDislikeSuccessPopup) { - setTimer(() => { - setShowDislikeSuccessPopup(false); - setShowLikeSuccessPopup(false); - clearTimer(); - }); - } - }, [isDisliked, clearTimer, setTimer, showLikeSuccessPopup, showDislikeSuccessPopup]); - - const setLikeControlRef = useCallback((ref) => { - likeControlRef.current = ref; - }, []); - - const setDislikeControlRef = useCallback((ref) => { - dislikeControlRef.current = ref; - }, []); - - const onOutsideClick = useCallback(() => { - hideFeedbackPopups(); - - if (showDislikeVariantsPopup && innerIsDisliked && !isDisliked) { - setInnerIsDisliked(false); - } - }, [ - isDisliked, - innerIsDisliked, - hideFeedbackPopups, - setInnerIsDisliked, - showDislikeVariantsPopup, - ]); - - const onSendDislikeInformation = useCallback(() => { - setShowDislikeSuccessPopup(true); - setShowLikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - - if (onSendFeedback) { - const type = FeedbackType.dislike; - - const additionalInfo = getPreparedFeedbackAdditionalInfo( - feedbackComment, - feedbackCheckboxes, - ); - const data = { - type, - ...additionalInfo, - }; - - onSendFeedback(data); - - resetFeedbackAdditionalInfo(); - } - }, [onSendFeedback, feedbackComment, feedbackCheckboxes, resetFeedbackAdditionalInfo]); + setInnerState(getInnerState(isLiked, isDisliked)); + }, [isLiked, isDisliked, setInnerState]); - const getPopupPosition = useCallback(() => { - if (!view || view === FeedbackView.Regular) { - return isVerticalView ? PopperPosition.LEFT_START : PopperPosition.BOTTOM_END; - } + const likeSuccessPopup = usePopupState({autoclose: 3000}); + const dislikeSuccessPopup = usePopupState({autoclose: 3000}); + const dislikeVariantsPopup = usePopupState(); - return PopperPosition.RIGHT; - }, [isVerticalView, view]); + const hideFeedbackPopups = useCallback(() => { + likeSuccessPopup.close(); + dislikeSuccessPopup.close(); + dislikeVariantsPopup.close(); + }, [likeSuccessPopup.close, dislikeSuccessPopup.close, dislikeVariantsPopup.close]); const onChangeLike = useCallback(() => { - setShowLikeSuccessPopup(true); - setShowDislikeSuccessPopup(false); - setShowDislikeVariantsPopup(false); - setInnerIsDisliked(false); + hideFeedbackPopups(); - if (onSendFeedback) { - onSendFeedback({ - type: isLiked ? FeedbackType.indeterminate : FeedbackType.like, - }); + if (innerState === FeedbackType.like) { + setInnerState(FeedbackType.indeterminate); + onSendFeedback({type: FeedbackType.indeterminate}); + } else { + setInnerState(FeedbackType.like); + onSendFeedback({type: FeedbackType.like}); + likeSuccessPopup.open(); } - }, [isLiked, onSendFeedback]); + }, [isLiked, onSendFeedback, setInnerState, likeSuccessPopup.open, hideFeedbackPopups]); const onChangeDislike = useCallback(() => { - if (!isDisliked && !innerIsDisliked) { - // Нажать дизлайк и показать окно с доп. информацией - setShowDislikeVariantsPopup(true); - setInnerIsDisliked(true); - setShowLikeSuccessPopup(false); - setShowDislikeSuccessPopup(false); - - if (isLiked && onSendFeedback) { - onSendFeedback({type: FeedbackType.indeterminate}); - } - } else if (!isDisliked && innerIsDisliked) { - hideFeedbackPopups(); - setInnerIsDisliked(false); - } else if (isDisliked && innerIsDisliked) { - // Отжать дизлайк и отправить событие в неопределенное состояние - hideFeedbackPopups(); - setInnerIsDisliked(false); - - if (onSendFeedback) { - onSendFeedback({type: FeedbackType.indeterminate}); - } - } - }, [innerIsDisliked, isDisliked, isLiked, onSendFeedback, hideFeedbackPopups]); - - const renderLikeControl = useCallback(() => { - return ( - ( - - )} - popupPosition={popupPosition} - /> - ); - }, [ - onChangeLike, - classNameControl, - view, - isVerticalView, - isLiked, - setLikeControlRef, - popupPosition, - t, - ]); - - const renderDislikeControl = useCallback(() => { - return ( - ( - - )} - /> - ); - }, [ - innerIsDisliked, - onChangeDislike, - classNameControl, - view, - isVerticalView, - setDislikeControlRef, - t, - ]); - - const renderRegularFeedbackControls = useCallback(() => { - return ( - - {renderLikeControl()} - {renderDislikeControl()} - - ); - }, [renderLikeControl, renderDislikeControl]); - - const renderWideFeedbackControls = useCallback(() => { - return ( -
-
-

{t('main-question')}

-
- - -
-
-
- ); - }, [ - innerIsDisliked, - isLiked, - view, - t, - setLikeControlRef, - setDislikeControlRef, - onChangeLike, - onChangeDislike, - ]); - - const renderFeedbackControls = useCallback(() => { - return view === FeedbackView.Regular - ? renderRegularFeedbackControls() - : renderWideFeedbackControls(); - }, [view, renderRegularFeedbackControls, renderWideFeedbackControls]); - - const renderFeedbackSuccessPopup = useCallback(() => { - const anchor = showLikeSuccessPopup ? likeControlRef : dislikeControlRef; - const visible = showLikeSuccessPopup || showDislikeSuccessPopup; - - if (!visible) { - return null; - } - - return ( - -

{t('success-title')}

-

{t('success-text')}

-
- ); - }, [ - showLikeSuccessPopup, - showDislikeSuccessPopup, - hideFeedbackPopups, - view, - getPopupPosition, - t, - ]); - - const renderDislikeVariantsList = useCallback(() => { - if (!dislikeVariants.length) { - return null; - } - - return ( -
- {dislikeVariants.map((variant, index) => ( - { - setFeedbackCheckboxes({ - ...feedbackCheckboxes, - [variant]: checked, - }); - }} - content={variant} - /> - ))} -
- ); - }, [dislikeVariants, feedbackCheckboxes]); - - const renderDislikeVariantsTextArea = useCallback(() => { - return ( -
-