diff --git a/src/components/comment/CommentAvatar/index.tsx b/src/components/comment/CommentAvatar/index.tsx index a3d3b03b..e12da6ef 100644 --- a/src/components/comment/CommentAvatar/index.tsx +++ b/src/components/comment/CommentAvatar/index.tsx @@ -47,7 +47,7 @@ const CommentAvatar: FC = ({ user, withDetails, className }) => { {hovered && withDetails && ( - + {({ style, ref }) => (

{user.fullname || user.username}

diff --git a/src/components/comment/CommentAvatar/styles.module.scss b/src/components/comment/CommentAvatar/styles.module.scss index 903c6d3e..3e0f209b 100644 --- a/src/components/comment/CommentAvatar/styles.module.scss +++ b/src/components/comment/CommentAvatar/styles.module.scss @@ -11,11 +11,11 @@ background-color: darken($content_bg, 4%); padding: $gap; box-sizing:border-box; - z-index: 4; touch-action: none; pointer-events: none; border-radius: $radius; animation: appear forwards 250ms; + z-index: 100; } .username { diff --git a/src/components/menu/MenuButton/index.tsx b/src/components/menu/MenuButton/index.tsx new file mode 100644 index 00000000..1a4e5421 --- /dev/null +++ b/src/components/menu/MenuButton/index.tsx @@ -0,0 +1,64 @@ +import React, { FC, ReactNode } from 'react'; + +import classNames from 'classnames'; +import { Manager, Popper, Reference } from 'react-popper'; + +import { Icon } from '~/components/input/Icon'; +import { useFocusEvent } from '~/hooks/dom/useFocusEvent'; + +import styles from './styles.module.scss'; + +interface MenuButtonProps { + icon?: ReactNode; + className?: string; +} + +const modifiers = [ + { + name: 'offset', + options: { + offset: [5, 10], + }, + }, +]; + +const MenuButton: FC = ({ + children, + className, + icon = , +}) => { + const { focused, onFocus, onBlur } = useFocusEvent(false, 150); + + return ( + + + {({ ref }) => ( + + )} + + + {focused && ( + + {({ style, ref, placement }) => ( +
+ {children} +
+ )} +
+ )} +
+ ); +}; + +export { MenuButton }; diff --git a/src/components/menu/MenuButton/styles.module.scss b/src/components/menu/MenuButton/styles.module.scss new file mode 100644 index 00000000..66e733a1 --- /dev/null +++ b/src/components/menu/MenuButton/styles.module.scss @@ -0,0 +1,40 @@ +.menu { + position: relative; + cursor: pointer; +} + +@import "src/styles/variables.scss"; + +@keyframes appear { + 0% { opacity: 0 } + 100% { opacity: 1 } +} + +.popper { + @include outer_shadow; + + background-color: $menu_bg; + box-sizing: border-box; + z-index: 12; + border-radius: $radius; + animation: appear forwards 250ms; + + &::after { + content: ' '; + width: 0; + height: 0; + border-style: solid; + border-width: 0 10px 10px 10px; + border-color: transparent transparent lighten($menu_bg, 6%) transparent; + position: absolute; + top: -11px; + right: 10px; + } + + &.top::after { + border-width: 10px 10px 0 10px; + border-color: darken($menu_bg, 8%) transparent transparent transparent; + top: auto; + bottom: -11px; + } +} diff --git a/src/components/menu/MenuItemWithIcon/index.tsx b/src/components/menu/MenuItemWithIcon/index.tsx new file mode 100644 index 00000000..9ddbe586 --- /dev/null +++ b/src/components/menu/MenuItemWithIcon/index.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; + +import { Icon } from '~/components/input/Icon'; + +import styles from './styles.module.scss'; + +interface MenuItemWithIconProps { + children: string; + icon: string; + onClick?: () => void; +} + +const MenuItemWithIcon: FC = ({ children, icon, onClick }) => ( + +); + +export { MenuItemWithIcon }; diff --git a/src/components/menu/MenuItemWithIcon/styles.module.scss b/src/components/menu/MenuItemWithIcon/styles.module.scss new file mode 100644 index 00000000..0fce0283 --- /dev/null +++ b/src/components/menu/MenuItemWithIcon/styles.module.scss @@ -0,0 +1,29 @@ +@import "src/styles/variables"; + +.item { + @include row_shadow; + @include hover_opacity; + + font: $font_14_medium; + line-height: 20px; + padding: $gap + 2px $gap $gap - 2px; + display: flex; + flex-direction: row; + color: white; + align-items: stretch; + justify-content: center; + width: 100%; + cursor: pointer; +} + +.icon { + flex: 0 0 20px; + margin-right: $gap; +} + +.text { + flex: 1; + text-align: left; + padding-right: $gap; + white-space: nowrap; +} diff --git a/src/components/menu/SeparatedMenu/index.tsx b/src/components/menu/SeparatedMenu/index.tsx new file mode 100644 index 00000000..19a4c9e3 --- /dev/null +++ b/src/components/menu/SeparatedMenu/index.tsx @@ -0,0 +1,31 @@ +import React, { FC, ReactNode, useMemo } from 'react'; + +import classNames from 'classnames'; + +import styles from './styles.module.scss'; + +interface SeparatedMenuProps { + className?: string; +} + +const SeparatedMenu: FC = ({ children, className }) => { + const items = useMemo(() => { + if (!children) { + return []; + } + + return (Array.isArray(children) ? children : [children]).filter(it => it); + }, [children]); + + return ( +
+ {items.map((item, index) => ( +
+ {item} +
+ ))} +
+ ); +}; + +export { SeparatedMenu }; diff --git a/src/components/menu/SeparatedMenu/styles.module.scss b/src/components/menu/SeparatedMenu/styles.module.scss new file mode 100644 index 00000000..c47900f4 --- /dev/null +++ b/src/components/menu/SeparatedMenu/styles.module.scss @@ -0,0 +1,29 @@ +@import "src/styles/mixins"; +@import "src/styles/variables"; + +.menu { + flex: 0; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.item { + margin-left: $gap * 4; + position: relative; + + &:not(:last-child)::after { + @include inner_shadow; + + content: ' '; + position: absolute; + width: 3px; + height: 16px; + background: darken($content_bg, 1%); + display: flex; + top: 5px; + right: -$gap * 2 - 2px; + border-radius: 2px; + } +} diff --git a/src/components/menu/index.ts b/src/components/menu/index.ts new file mode 100644 index 00000000..b4bd97b4 --- /dev/null +++ b/src/components/menu/index.ts @@ -0,0 +1,5 @@ +export * from './VerticalMenu'; +export * from './HorizontalMenu'; +export * from './MenuButton'; +export * from './MenuItemWithIcon'; +export * from './SeparatedMenu'; diff --git a/src/components/node/NodeEditMenu/index.tsx b/src/components/node/NodeEditMenu/index.tsx index 1b85ea2b..14b81f91 100644 --- a/src/components/node/NodeEditMenu/index.tsx +++ b/src/components/node/NodeEditMenu/index.tsx @@ -1,12 +1,17 @@ import React, { VFC } from 'react'; +import Tippy from '@tippyjs/react'; import classNames from 'classnames'; import { Icon } from '~/components/input/Icon'; +import { MenuButton, MenuItemWithIcon, SeparatedMenu } from '~/components/menu'; +import { useWindowSize } from '~/hooks/dom/useWindowSize'; import styles from './styles.module.scss'; interface NodeEditMenuProps { + className?: string; + canStar: boolean; isHeroic: boolean; @@ -18,36 +23,62 @@ interface NodeEditMenuProps { } const NodeEditMenu: VFC = ({ + className, canStar, isHeroic, isLocked, onStar, onLock, onEdit, -}) => ( -
-
- -
+}) => { + const { isMobile } = useWindowSize(); -
+ if (isMobile) { + return ( + } + className={className} + > + {canStar && ( + + {isHeroic ? 'Убрать с главной' : 'На главную'} + + )} + + + Редактировать + + + + {isLocked ? 'Восстановить' : 'Удалить'} + + + ); + } + + return ( + {canStar && ( -
- {isHeroic ? ( - - ) : ( - - )} -
+ + + )} -
- -
+ + + - -
-
-); + + + + + ); +}; export { NodeEditMenu }; diff --git a/src/components/node/NodeEditMenu/styles.module.scss b/src/components/node/NodeEditMenu/styles.module.scss index 4ff4c690..f2d96880 100644 --- a/src/components/node/NodeEditMenu/styles.module.scss +++ b/src/components/node/NodeEditMenu/styles.module.scss @@ -1,109 +1,3 @@ -@import "src/styles/variables"; -@import "src/styles/mixins"; - -@mixin button { - margin: 12px $gap 0 $gap; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - - svg { - fill: darken(white, 50%); - transition: fill 0.25s; - } - - &:hover { - svg { - fill: $red; - } - } - - &::after { - content: ' '; - flex: 0 0 6px; - height: $gap; - width: 6px; - border-radius: 4px; - background: transparentize(black, 0.7); - margin-left: $gap * 2; - } -} - -.editor_buttons { - flex: 0; - padding-right: $gap; - fill: transparentize(white, 0.7); - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - - & > * { - @include button; - } - - @include tablet { - align-self: center; - display: none; - - & > * { - &:last-child { - margin-right: 0; - - &::after { - display: none; - } - } - - &:first-child { - margin-left: 0; - } - } - } -} - - -.star { - transition: fill, stroke 0.25s; - will-change: transform; - - .is_heroic { - svg { - fill: $orange; - } - } - - &:hover { - fill: $orange; - } -} - -.editor_menu_button { - display: none !important; - - @include button(); - - @include tablet { - display: flex !important; - } -} - -.editor_menu { - &:hover { - .editor_buttons { - @include tablet { - display: flex; - position: absolute; - right: 0; - top: 100%; - background: darken($content_bg, 4%); - padding: $gap * 2; - border-radius: $radius; - box-shadow: transparentize(black, 0.8) 5px 5px 5px; - transform: translate(0, -10px); - z-index: 10; - } - } - } +.icon { + fill: currentColor; } diff --git a/src/components/node/NodeTitle/index.tsx b/src/components/node/NodeTitle/index.tsx index 876633b9..cc821ee8 100644 --- a/src/components/node/NodeTitle/index.tsx +++ b/src/components/node/NodeTitle/index.tsx @@ -3,6 +3,7 @@ import React, { memo, VFC } from 'react'; import classNames from 'classnames'; import { Icon } from '~/components/input/Icon'; +import { SeparatedMenu } from '~/components/menu'; import { NodeEditMenu } from '~/components/node/NodeEditMenu'; import { Placeholder } from '~/components/placeholders/Placeholder'; import { getPrettyDate } from '~/utils/dom'; @@ -74,20 +75,23 @@ const NodeTitle: VFC = memo( )}
- {canEdit && ( - - )} + + {canEdit && ( + + )} -
{canLike && ( -
+
{isLiked ? ( ) : ( @@ -99,7 +103,7 @@ const NodeTitle: VFC = memo( )}
)} -
+
); diff --git a/src/components/node/NodeTitle/styles.module.scss b/src/components/node/NodeTitle/styles.module.scss index 17f0729b..f7c8797f 100644 --- a/src/components/node/NodeTitle/styles.module.scss +++ b/src/components/node/NodeTitle/styles.module.scss @@ -72,7 +72,7 @@ } .name { - font: $font_14_regular; + font: $font_12_regular; color: transparentize(white, 0.5); text-transform: lowercase; @@ -95,37 +95,6 @@ min-width: 0; } -.buttons { - flex: 0; - padding-right: $gap; - fill: transparentize(white, 0.7); - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - margin-top: 12px; - - & > * { - @include button; - } - - @include tablet { - align-self: center; - } -} - -.buttons { - & > * { - &:last-child { - margin-right: 0; - - &::after { - display: none; - } - } - } -} - .mark { flex: 0 0 32px; position: relative; @@ -177,8 +146,11 @@ will-change: transform; position: relative; flex: 0 0 32px; + fill: currentColor; &.is_liked { + opacity: 1; + svg { fill: $red; } @@ -213,3 +185,13 @@ pointer-events: none; touch-action: none; } + +.buttons { + margin-top: 12px; + margin-right: $gap; +} + +.button { + color: white; + @include hover_opacity; +} diff --git a/src/styles/_colors.scss b/src/styles/_colors.scss index c59291c2..ab5aca69 100644 --- a/src/styles/_colors.scss +++ b/src/styles/_colors.scss @@ -67,3 +67,4 @@ $side_pane_btn_color: lighten($main_bg_color, 0%); $node_buttons_bg: darken($main_bg_color, 6%); $tag_bg: lighten($main_bg_color, 4%); +$menu_bg: lighten($main_bg_color, 4%); diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 8a79149a..3599dc2d 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -274,3 +274,12 @@ } } } + +@mixin hover_opacity($initial_opacity: 0.5) { + opacity: $initial_opacity; + transition: opacity 0.25s; + + &:hover { + opacity: 1; + } +} diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss index d9f27b5a..d980597c 100644 --- a/src/styles/_reset.scss +++ b/src/styles/_reset.scss @@ -46,3 +46,6 @@ table { border-collapse: collapse; border-spacing: 0; } +button { + padding: 0; +}