From 06beecac54cdf02705d017a3e3ef93e2c4a9b371 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Tue, 25 Oct 2022 17:36:32 +0200 Subject: [PATCH] common Popup component & SharePopup --- src/components/Article/FullArticle.tsx | 11 ++-- src/components/Article/SharePopup.tsx | 45 ++++++++++++++++ src/components/Nav/Header.module.scss | 32 ++++------- src/components/Nav/Header.tsx | 73 ++++++++------------------ src/components/Nav/Popup.module.scss | 24 +++++++-- src/components/Nav/Popup.tsx | 61 +++++++++++++-------- src/stores/ui.ts | 3 +- 7 files changed, 146 insertions(+), 103 deletions(-) create mode 100644 src/components/Article/SharePopup.tsx diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 512cdac9..ab6d6b07 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -10,6 +10,7 @@ import { showModal } from '../../stores/ui' import { useAuthStore } from '../../stores/auth' import { incrementView } from '../../stores/zine/articles' import MD from './MD' +import { SharePopup } from './SharePopup' const MAX_COMMENT_LEVEL = 6 @@ -126,9 +127,13 @@ export const FullArticle = (props: ArticleProps) => { {/* */} {/**/}
- showModal('share')}> - - + event.preventDefault()}> + + + } + />
{/*FIXME*/} {/**/} diff --git a/src/components/Article/SharePopup.tsx b/src/components/Article/SharePopup.tsx new file mode 100644 index 00000000..851cf599 --- /dev/null +++ b/src/components/Article/SharePopup.tsx @@ -0,0 +1,45 @@ +import { Icon } from '../Nav/Icon' +import styles from '../Nav/Popup.module.scss' +import { t } from '../../utils/intl' +import { Popup, PopupProps } from '../Nav/Popup' + +type SharePopupProps = Omit + +export const SharePopup = (props: SharePopupProps) => { + return ( + + + + ) +} diff --git a/src/components/Nav/Header.module.scss b/src/components/Nav/Header.module.scss index 0a34a45e..9078872b 100644 --- a/src/components/Nav/Header.module.scss +++ b/src/components/Nav/Header.module.scss @@ -35,18 +35,6 @@ } } -.popupShare { - opacity: 1; - transition: opacity 0.3s; - z-index: 1; - - .headerScrolledTop & { - opacity: 0; - transition: opacity 0.3s, z-index 0s 0.3s; - z-index: -1; - } -} - .headerFixed { position: fixed; top: 0; @@ -348,18 +336,16 @@ transform: translateY(-50%); width: 100%; - .icon { + .control { + cursor: pointer; margin-left: 1.6rem; - opacity: 0.6; - transition: opacity 0.3s; - } + border: 0; - img { - vertical-align: middle; - } + .icon { + opacity: 0.6; + transition: opacity 0.3s; + } - a { - border: none; &:hover { background: none; @@ -370,4 +356,8 @@ } } } + + img { + vertical-align: middle; + } } diff --git a/src/components/Nav/Header.tsx b/src/components/Nav/Header.tsx index ab2d6aeb..8a4e46f8 100644 --- a/src/components/Nav/Header.tsx +++ b/src/components/Nav/Header.tsx @@ -3,18 +3,17 @@ import Private from './Private' import Notifications from './Notifications' import { Icon } from './Icon' import { Modal } from './Modal' -import { Popup } from './Popup' import AuthModal from './AuthModal' import { t } from '../../utils/intl' -import {useModalStore, showModal, useWarningsStore, toggleModal} from '../../stores/ui' +import { useModalStore, showModal, useWarningsStore } from '../../stores/ui' import { useAuthStore } from '../../stores/auth' import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router' import styles from './Header.module.scss' -import stylesPopup from './Popup.module.scss' import privateStyles from './Private.module.scss' import { getPagePath } from '@nanostores/router' import { getLogger } from '../../utils/logger' import { clsx } from 'clsx' +import { SharePopup } from '../Article/SharePopup' const log = getLogger('header') @@ -39,6 +38,7 @@ export const Header = (props: Props) => { const [getIsScrolled, setIsScrolled] = createSignal(false) const [fixed, setFixed] = createSignal(false) const [visibleWarnings, setVisibleWarnings] = createSignal(false) + const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false) // stores const { getWarnings } = useWarningsStore() const { session } = useAuthStore() @@ -48,14 +48,11 @@ export const Header = (props: Props) => { // methods const toggleWarnings = () => setVisibleWarnings(!visibleWarnings()) - const toggleFixed = () => setFixed(!fixed()) + const toggleFixed = () => setFixed((oldFixed) => !oldFixed) // effects createEffect(() => { - const isFixed = fixed() || (getModal() && getModal() !== 'share'); - - document.body.classList.toggle('fixed', isFixed); - document.body.classList.toggle(styles.fixed, isFixed && !getModal()); - }, [fixed(), getModal()]) + document.body.classList.toggle('fixed', fixed() || getModal() !== null) + }) // derived const authorized = createMemo(() => session()?.user?.slug) @@ -90,7 +87,7 @@ export const Header = (props: Props) => { classList={{ [styles.headerFixed]: props.isHeaderFixed, [styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(), - [styles.headerScrolledBottom]: getIsScrollingBottom() && getIsScrolled(), + [styles.headerScrolledBottom]: (getIsScrollingBottom() && getIsScrolled()) || isSharePopupVisible(), [styles.headerWithTitle]: Boolean(props.title) }} > @@ -99,41 +96,6 @@ export const Header = (props: Props) => { diff --git a/src/components/Nav/Popup.module.scss b/src/components/Nav/Popup.module.scss index 63405563..d62d1c09 100644 --- a/src/components/Nav/Popup.module.scss +++ b/src/components/Nav/Popup.module.scss @@ -1,6 +1,13 @@ +.container { + position: relative; +} + .popup { background: #fff; border: 2px solid #000; + top: calc(100% + 8px); + transform: translateX(-50%); + opacity: 1; @include font-size(1.6rem); @@ -24,6 +31,7 @@ a { border: none; + white-space: nowrap; } img { @@ -40,7 +48,15 @@ } } -.popupShare { - right: 1em; - top: 4.5rem; -} +// TODO: animation +// .popup { +// opacity: 1; +// transition: opacity 0.3s; +// z-index: 1; +// &.visible { +// opacity: 0; +// transition: opacity 0.3s, z-index 0s 0.3s; +// z-index: -1; +// } +// } + diff --git a/src/components/Nav/Popup.tsx b/src/components/Nav/Popup.tsx index 451caf88..a53517cf 100644 --- a/src/components/Nav/Popup.tsx +++ b/src/components/Nav/Popup.tsx @@ -1,33 +1,50 @@ -import { createEffect, createSignal, onMount, Show } from 'solid-js' -import style from './Popup.module.scss' -import { hideModal, useModalStore } from '../../stores/ui' -import {clsx} from 'clsx'; +import { createEffect, createSignal, JSX, onCleanup, onMount, Show } from 'solid-js' +import styles from './Popup.module.scss' +import { clsx } from 'clsx' -interface PopupProps { - name: string - children: any - class?: string +export type PopupProps = { + containerCssClass?: string + trigger: JSX.Element + children: JSX.Element + onVisibilityChange?: (isVisible) => void } export const Popup = (props: PopupProps) => { - const { getModal } = useModalStore() + const [isVisible, setIsVisible] = createSignal(false) + + createEffect(() => { + if (props.onVisibilityChange) { + props.onVisibilityChange(isVisible()) + } + }) + + let container: HTMLDivElement | undefined + + const handleClickOutside = (event: MouseEvent & { target: Element }) => { + if (!isVisible()) { + return + } + + if (event.target === container || container?.contains(event.target)) { + return + } + + setIsVisible(false) + } onMount(() => { - window.addEventListener('keydown', (e: KeyboardEvent) => { - if (e.key === 'Escape') hideModal() - }) - }) - - const [visible, setVisible] = createSignal(false) - createEffect(() => { - setVisible(getModal() === props.name) + document.addEventListener('click', handleClickOutside, { capture: true }) + onCleanup(() => document.removeEventListener('click', handleClickOutside, { capture: true })) }) + const toggle = () => setIsVisible((oldVisible) => !oldVisible) + // class={clsx(styles.popupShare, stylesPopup.popupShare)} return ( - -
- {props.children} -
-
+ + {props.trigger} + +
{props.children}
+
+
) } diff --git a/src/stores/ui.ts b/src/stores/ui.ts index 39d4c717..e36e48c7 100644 --- a/src/stores/ui.ts +++ b/src/stores/ui.ts @@ -5,7 +5,7 @@ import { createSignal } from 'solid-js' //export const locale = persistentAtom('locale', 'ru') export const [locale, setLocale] = createSignal('ru') -export type ModalType = 'auth' | 'subscribe' | 'feedback' | 'share' | 'thank' | 'donate' | null +export type ModalType = 'auth' | 'subscribe' | 'feedback' | 'thank' | 'donate' | null type WarnKind = 'error' | 'warn' | 'info' export interface Warning { @@ -20,7 +20,6 @@ const warnings = atom([]) export const showModal = (modalType: ModalType) => modal.set(modalType) export const hideModal = () => modal.set(null) -export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType) export const clearWarns = () => warnings.set([]) export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])