common Popup component & SharePopup
This commit is contained in:
parent
3374e9677a
commit
06beecac54
|
@ -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) => {
|
|||
{/* </a>*/}
|
||||
{/*</div>*/}
|
||||
<div class="shout-stats__item">
|
||||
<a href="#share" onClick={() => showModal('share')}>
|
||||
<Icon name="share" />
|
||||
</a>
|
||||
<SharePopup
|
||||
trigger={
|
||||
<a href="#" onClick={(event) => event.preventDefault()}>
|
||||
<Icon name="share" />
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/*FIXME*/}
|
||||
{/*<Show when={canEdit()}>*/}
|
||||
|
|
45
src/components/Article/SharePopup.tsx
Normal file
45
src/components/Article/SharePopup.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
|||
</Modal>
|
||||
|
||||
<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() }}>
|
||||
<div class={clsx(styles.mainLogo, 'col-auto')}>
|
||||
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
|
||||
|
@ -197,14 +159,23 @@ export const Header = (props: Props) => {
|
|||
</div>
|
||||
<Show when={props.title}>
|
||||
<div class={styles.articleControls}>
|
||||
<button onClick={() => {toggleModal('share')}}>
|
||||
<Icon name="share-outline" class={styles.icon}/>
|
||||
</button>
|
||||
<a href="#comments">
|
||||
<SharePopup
|
||||
onVisibilityChange={(isVisible) => {
|
||||
console.log({ isVisible })
|
||||
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} />
|
||||
</a>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
<Icon name="bookmark" class={styles.icon} />
|
||||
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
|
||||
<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>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<Show when={visible()}>
|
||||
<div class={clsx(style.popup, props.class)}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Show>
|
||||
<span class={clsx(styles.container, props.containerCssClass)} ref={container}>
|
||||
<span onClick={toggle}>{props.trigger}</span>
|
||||
<Show when={isVisible()}>
|
||||
<div class={clsx(styles.popup)}>{props.children}</div>
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { createSignal } from 'solid-js'
|
|||
|
||||
//export const locale = persistentAtom<string>('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<Warning[]>([])
|
|||
|
||||
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])
|
||||
|
|
Loading…
Reference in New Issue
Block a user