common Popup component & SharePopup

This commit is contained in:
Igor Lobanov 2022-10-25 17:36:32 +02:00
parent 3374e9677a
commit 06beecac54
7 changed files with 146 additions and 103 deletions

View File

@ -10,6 +10,7 @@ import { showModal } from '../../stores/ui'
import { useAuthStore } from '../../stores/auth' import { useAuthStore } from '../../stores/auth'
import { incrementView } from '../../stores/zine/articles' import { incrementView } from '../../stores/zine/articles'
import MD from './MD' import MD from './MD'
import { SharePopup } from './SharePopup'
const MAX_COMMENT_LEVEL = 6 const MAX_COMMENT_LEVEL = 6
@ -126,9 +127,13 @@ export const FullArticle = (props: ArticleProps) => {
{/* </a>*/} {/* </a>*/}
{/*</div>*/} {/*</div>*/}
<div class="shout-stats__item"> <div class="shout-stats__item">
<a href="#share" onClick={() => showModal('share')}> <SharePopup
<Icon name="share" /> trigger={
</a> <a href="#" onClick={(event) => event.preventDefault()}>
<Icon name="share" />
</a>
}
/>
</div> </div>
{/*FIXME*/} {/*FIXME*/}
{/*<Show when={canEdit()}>*/} {/*<Show when={canEdit()}>*/}

View File

@ -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<PopupProps, 'children'>
export const SharePopup = (props: SharePopupProps) => {
return (
<Popup {...props}>
<ul class="nodash">
<li>
<a href="#">
<Icon name="vk-white" class={styles.icon} />
VK
</a>
</li>
<li>
<a href="#">
<Icon name="facebook-white" class={styles.icon} />
Facebook
</a>
</li>
<li>
<a href="#">
<Icon name="twitter-white" class={styles.icon} />
Twitter
</a>
</li>
<li>
<a href="#">
<Icon name="telegram-white" class={styles.icon} />
Telegram
</a>
</li>
<li>
<a href="#">
<Icon name="link-white" class={styles.icon} />
{t('Copy link')}
</a>
</li>
</ul>
</Popup>
)
}

View File

@ -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 { .headerFixed {
position: fixed; position: fixed;
top: 0; top: 0;
@ -348,18 +336,16 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 100%; width: 100%;
.icon { .control {
cursor: pointer;
margin-left: 1.6rem; margin-left: 1.6rem;
opacity: 0.6; border: 0;
transition: opacity 0.3s;
}
img { .icon {
vertical-align: middle; opacity: 0.6;
} transition: opacity 0.3s;
}
a {
border: none;
&:hover { &:hover {
background: none; background: none;
@ -370,4 +356,8 @@
} }
} }
} }
img {
vertical-align: middle;
}
} }

View File

@ -3,18 +3,17 @@ import Private from './Private'
import Notifications from './Notifications' import Notifications from './Notifications'
import { Icon } from './Icon' import { Icon } from './Icon'
import { Modal } from './Modal' import { Modal } from './Modal'
import { Popup } from './Popup'
import AuthModal from './AuthModal' import AuthModal from './AuthModal'
import { t } from '../../utils/intl' 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 { useAuthStore } from '../../stores/auth'
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
import styles from './Header.module.scss' import styles from './Header.module.scss'
import stylesPopup from './Popup.module.scss'
import privateStyles from './Private.module.scss' import privateStyles from './Private.module.scss'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { getLogger } from '../../utils/logger' import { getLogger } from '../../utils/logger'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { SharePopup } from '../Article/SharePopup'
const log = getLogger('header') const log = getLogger('header')
@ -39,6 +38,7 @@ export const Header = (props: Props) => {
const [getIsScrolled, setIsScrolled] = createSignal(false) const [getIsScrolled, setIsScrolled] = createSignal(false)
const [fixed, setFixed] = createSignal(false) const [fixed, setFixed] = createSignal(false)
const [visibleWarnings, setVisibleWarnings] = createSignal(false) const [visibleWarnings, setVisibleWarnings] = createSignal(false)
const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false)
// stores // stores
const { getWarnings } = useWarningsStore() const { getWarnings } = useWarningsStore()
const { session } = useAuthStore() const { session } = useAuthStore()
@ -48,14 +48,11 @@ export const Header = (props: Props) => {
// methods // methods
const toggleWarnings = () => setVisibleWarnings(!visibleWarnings()) const toggleWarnings = () => setVisibleWarnings(!visibleWarnings())
const toggleFixed = () => setFixed(!fixed()) const toggleFixed = () => setFixed((oldFixed) => !oldFixed)
// effects // effects
createEffect(() => { createEffect(() => {
const isFixed = fixed() || (getModal() && getModal() !== 'share'); document.body.classList.toggle('fixed', fixed() || getModal() !== null)
})
document.body.classList.toggle('fixed', isFixed);
document.body.classList.toggle(styles.fixed, isFixed && !getModal());
}, [fixed(), getModal()])
// derived // derived
const authorized = createMemo(() => session()?.user?.slug) const authorized = createMemo(() => session()?.user?.slug)
@ -90,7 +87,7 @@ export const Header = (props: Props) => {
classList={{ classList={{
[styles.headerFixed]: props.isHeaderFixed, [styles.headerFixed]: props.isHeaderFixed,
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(), [styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
[styles.headerScrolledBottom]: getIsScrollingBottom() && getIsScrolled(), [styles.headerScrolledBottom]: (getIsScrollingBottom() && getIsScrolled()) || isSharePopupVisible(),
[styles.headerWithTitle]: Boolean(props.title) [styles.headerWithTitle]: Boolean(props.title)
}} }}
> >
@ -99,41 +96,6 @@ export const Header = (props: Props) => {
</Modal> </Modal>
<div class={clsx(styles.mainHeaderInner, 'wide-container')}> <div class={clsx(styles.mainHeaderInner, 'wide-container')}>
<Popup name="share" class={clsx(styles.popupShare, stylesPopup.popupShare)}>
<ul class="nodash">
<li>
<a href="#">
<Icon name="vk-white" class={stylesPopup.icon}/>
VK
</a>
</li>
<li>
<a href="#">
<Icon name="facebook-white" class={stylesPopup.icon}/>
Facebook
</a>
</li>
<li>
<a href="#">
<Icon name="twitter-white" class={stylesPopup.icon}/>
Twitter
</a>
</li>
<li>
<a href="#">
<Icon name="telegram-white" class={stylesPopup.icon}/>
Telegram
</a>
</li>
<li>
<a href="#">
<Icon name="link-white" class={stylesPopup.icon}/>
{t('Copy link')}
</a>
</li>
</ul>
</Popup>
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}> <nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
<div class={clsx(styles.mainLogo, 'col-auto')}> <div class={clsx(styles.mainLogo, 'col-auto')}>
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}> <a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
@ -197,14 +159,23 @@ export const Header = (props: Props) => {
</div> </div>
<Show when={props.title}> <Show when={props.title}>
<div class={styles.articleControls}> <div class={styles.articleControls}>
<button onClick={() => {toggleModal('share')}}> <SharePopup
<Icon name="share-outline" class={styles.icon}/> onVisibilityChange={(isVisible) => {
</button> console.log({ isVisible })
<a href="#comments"> setIsSharePopupVisible(isVisible)
}}
containerCssClass={styles.control}
trigger={<Icon name="share-outline" class={styles.icon} />}
/>
<a href="#comments" class={styles.control}>
<Icon name="comments-outline" class={styles.icon} /> <Icon name="comments-outline" class={styles.icon} />
</a> </a>
<Icon name="pencil-outline" class={styles.icon} /> <a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
<Icon name="bookmark" class={styles.icon} /> <Icon name="pencil-outline" class={styles.icon} />
</a>
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
<Icon name="bookmark" class={styles.icon} />
</a>
</div> </div>
</Show> </Show>
</div> </div>

View File

@ -1,6 +1,13 @@
.container {
position: relative;
}
.popup { .popup {
background: #fff; background: #fff;
border: 2px solid #000; border: 2px solid #000;
top: calc(100% + 8px);
transform: translateX(-50%);
opacity: 1;
@include font-size(1.6rem); @include font-size(1.6rem);
@ -24,6 +31,7 @@
a { a {
border: none; border: none;
white-space: nowrap;
} }
img { img {
@ -40,7 +48,15 @@
} }
} }
.popupShare { // TODO: animation
right: 1em; // .popup {
top: 4.5rem; // opacity: 1;
} // transition: opacity 0.3s;
// z-index: 1;
// &.visible {
// opacity: 0;
// transition: opacity 0.3s, z-index 0s 0.3s;
// z-index: -1;
// }
// }

View File

@ -1,33 +1,50 @@
import { createEffect, createSignal, onMount, Show } from 'solid-js' import { createEffect, createSignal, JSX, onCleanup, onMount, Show } from 'solid-js'
import style from './Popup.module.scss' import styles from './Popup.module.scss'
import { hideModal, useModalStore } from '../../stores/ui' import { clsx } from 'clsx'
import {clsx} from 'clsx';
interface PopupProps { export type PopupProps = {
name: string containerCssClass?: string
children: any trigger: JSX.Element
class?: string children: JSX.Element
onVisibilityChange?: (isVisible) => void
} }
export const Popup = (props: PopupProps) => { 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(() => { onMount(() => {
window.addEventListener('keydown', (e: KeyboardEvent) => { document.addEventListener('click', handleClickOutside, { capture: true })
if (e.key === 'Escape') hideModal() onCleanup(() => document.removeEventListener('click', handleClickOutside, { capture: true }))
})
})
const [visible, setVisible] = createSignal(false)
createEffect(() => {
setVisible(getModal() === props.name)
}) })
const toggle = () => setIsVisible((oldVisible) => !oldVisible)
// class={clsx(styles.popupShare, stylesPopup.popupShare)}
return ( return (
<Show when={visible()}> <span class={clsx(styles.container, props.containerCssClass)} ref={container}>
<div class={clsx(style.popup, props.class)}> <span onClick={toggle}>{props.trigger}</span>
{props.children} <Show when={isVisible()}>
</div> <div class={clsx(styles.popup)}>{props.children}</div>
</Show> </Show>
</span>
) )
} }

View File

@ -5,7 +5,7 @@ import { createSignal } from 'solid-js'
//export const locale = persistentAtom<string>('locale', 'ru') //export const locale = persistentAtom<string>('locale', 'ru')
export const [locale, setLocale] = createSignal('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' type WarnKind = 'error' | 'warn' | 'info'
export interface Warning { export interface Warning {
@ -20,7 +20,6 @@ const warnings = atom<Warning[]>([])
export const showModal = (modalType: ModalType) => modal.set(modalType) export const showModal = (modalType: ModalType) => modal.set(modalType)
export const hideModal = () => modal.set(null) export const hideModal = () => modal.set(null)
export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType)
export const clearWarns = () => warnings.set([]) export const clearWarns = () => warnings.set([])
export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning]) export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])