Markup for mobile fix-pack (#349)
* mediaQuery context provider * Fix header styles * User list markup fix
This commit is contained in:
parent
41b5560036
commit
11d3a6c274
|
@ -7,6 +7,7 @@ import { Dynamic } from 'solid-js/web'
|
|||
import { ConfirmProvider } from '../context/confirm'
|
||||
import { EditorProvider } from '../context/editor'
|
||||
import { LocalizeProvider } from '../context/localize'
|
||||
import { MediaQueryProvider } from '../context/mediaQuery'
|
||||
import { NotificationsProvider } from '../context/notifications'
|
||||
import { SessionProvider } from '../context/session'
|
||||
import { SnackbarProvider } from '../context/snackbar'
|
||||
|
@ -115,6 +116,7 @@ export const App = (props: Props) => {
|
|||
<MetaProvider>
|
||||
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<LocalizeProvider>
|
||||
<MediaQueryProvider>
|
||||
<SnackbarProvider>
|
||||
<ConfirmProvider>
|
||||
<SessionProvider>
|
||||
|
@ -126,6 +128,7 @@ export const App = (props: Props) => {
|
|||
</SessionProvider>
|
||||
</ConfirmProvider>
|
||||
</SnackbarProvider>
|
||||
</MediaQueryProvider>
|
||||
</LocalizeProvider>
|
||||
</MetaProvider>
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
&.nameOnly {
|
||||
align-items: center;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { openPage } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
|
||||
import { createEffect, createMemo, createSignal, Match, Show, Switch } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useMediaQuery } from '../../../context/mediaQuery'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { Author, FollowingEntity } from '../../../graphql/types.gen'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
|
@ -23,7 +24,14 @@ type Props = {
|
|||
nameOnly?: boolean
|
||||
}
|
||||
export const AuthorBadge = (props: Props) => {
|
||||
const { mediaMatches } = useMediaQuery()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
setIsMobileView(!mediaMatches.sm)
|
||||
})
|
||||
|
||||
const {
|
||||
session,
|
||||
subscriptions,
|
||||
|
@ -65,7 +73,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
<div class={styles.basicInfo}>
|
||||
<Userpic
|
||||
hasLink={true}
|
||||
size={'M'}
|
||||
size={isMobileView() ? 'M' : 'L'}
|
||||
name={props.author.name}
|
||||
userpic={props.author.userpic}
|
||||
slug={props.author.slug}
|
||||
|
@ -111,7 +119,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
fallback={
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="M"
|
||||
size="S"
|
||||
value={
|
||||
<Show
|
||||
when={props.iconButtons}
|
||||
|
@ -135,7 +143,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
>
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="M"
|
||||
size="S"
|
||||
value={
|
||||
<Show
|
||||
when={props.iconButtons}
|
||||
|
@ -161,7 +169,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
<Show when={props.showMessageButton}>
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="M"
|
||||
size="S"
|
||||
value={props.iconButtons ? <Icon name="inbox-white" /> : t('Message')}
|
||||
onClick={initChat}
|
||||
class={clsx(styles.actionButton, { [styles.iconed]: props.iconButtons })}
|
||||
|
|
|
@ -28,7 +28,6 @@ type Props = {
|
|||
followers?: Author[]
|
||||
following?: Array<Author | Topic>
|
||||
}
|
||||
|
||||
export const AuthorCard = (props: Props) => {
|
||||
const { t, lang } = useLocalize()
|
||||
const {
|
||||
|
@ -258,7 +257,7 @@ export const AuthorCard = (props: Props) => {
|
|||
</Show>
|
||||
</ShowOnlyOnClient>
|
||||
<Show when={props.followers}>
|
||||
<Modal variant="medium" name="followers" maxHeight>
|
||||
<Modal variant="medium" isResponsive={true} name="followers" maxHeight>
|
||||
<>
|
||||
<h2>{t('Followers')}</h2>
|
||||
<div class={styles.listWrapper}>
|
||||
|
@ -274,7 +273,7 @@ export const AuthorCard = (props: Props) => {
|
|||
</Modal>
|
||||
</Show>
|
||||
<Show when={props.following}>
|
||||
<Modal variant="medium" name="following" maxHeight>
|
||||
<Modal variant="medium" isResponsive={true} name="following" maxHeight>
|
||||
<>
|
||||
<h2>{t('Subscriptions')}</h2>
|
||||
<ul class="view-switcher">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
margin-bottom: 2.2rem;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
z-index: 10003;
|
||||
|
||||
.wide-container {
|
||||
background: #fff;
|
||||
|
@ -149,7 +149,7 @@
|
|||
position: fixed;
|
||||
top: 58px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
z-index: 10003;
|
||||
|
||||
li {
|
||||
margin-bottom: 2.4rem !important;
|
||||
|
|
|
@ -61,7 +61,10 @@ export const Header = (props: Props) => {
|
|||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||
const toggleFixed = () => setFixed((oldFixed) => !oldFixed)
|
||||
const toggleFixed = () => {
|
||||
setFixed(!fixed())
|
||||
console.log('!!! toggleFixed:')
|
||||
}
|
||||
|
||||
const tag = (topic: Topic) =>
|
||||
/[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title
|
||||
|
@ -138,6 +141,9 @@ export const Header = (props: Props) => {
|
|||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
console.log('!!! mo:', modal())
|
||||
})
|
||||
const hideSubnavigation = (event, time = 500) => {
|
||||
timer = setTimeout(() => {
|
||||
toggleSubnavigation(false)
|
||||
|
@ -183,9 +189,9 @@ export const Header = (props: Props) => {
|
|||
</Modal>
|
||||
|
||||
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
|
||||
<nav class={clsx('row', styles.headerInner, { ['fixed']: fixed() })}>
|
||||
<nav class={clsx('row', styles.headerInner, { [styles.fixed]: fixed() })}>
|
||||
<div class={clsx(styles.burgerContainer, 'col-auto')}>
|
||||
<div class={styles.burger} classList={{ fixed: fixed() }} onClick={toggleFixed}>
|
||||
<div class={clsx(styles.burger, { [styles.fixed]: fixed() })} onClick={toggleFixed}>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10002;
|
||||
z-index: 10003;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
background: var(--background-color);
|
||||
max-width: 1000px;
|
||||
position: relative;
|
||||
|
||||
|
@ -114,3 +114,23 @@
|
|||
flex-direction: column;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.backdrop.isMobile {
|
||||
z-index: 10002;
|
||||
top: 56px;
|
||||
height: calc(100% - 58px);
|
||||
bottom: 0;
|
||||
|
||||
.maxHeight {
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
.modalInner {
|
||||
padding: 1rem 1rem 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { redirectPage } from '@nanostores/router'
|
|||
import { clsx } from 'clsx'
|
||||
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
||||
|
||||
import { useMediaQuery } from '../../../context/mediaQuery'
|
||||
import { router } from '../../../stores/router'
|
||||
import { hideModal, useModalStore } from '../../../stores/ui'
|
||||
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
||||
|
@ -19,12 +20,15 @@ interface Props {
|
|||
noPadding?: boolean
|
||||
maxHeight?: boolean
|
||||
allowClose?: boolean
|
||||
isResponsive?: boolean
|
||||
}
|
||||
|
||||
export const Modal = (props: Props) => {
|
||||
const { modal } = useModalStore()
|
||||
const [visible, setVisible] = createSignal(false)
|
||||
const allowClose = createMemo(() => props.allowClose !== false)
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const { mediaMatches } = useMediaQuery()
|
||||
const handleHide = () => {
|
||||
if (modal()) {
|
||||
if (allowClose()) {
|
||||
|
@ -33,7 +37,6 @@ export const Modal = (props: Props) => {
|
|||
redirectPage(router, 'home')
|
||||
}
|
||||
}
|
||||
|
||||
hideModal()
|
||||
}
|
||||
|
||||
|
@ -43,10 +46,21 @@ export const Modal = (props: Props) => {
|
|||
setVisible(modal() === props.name)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (props.isResponsive) {
|
||||
setIsMobileView(!mediaMatches.sm)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={visible()}>
|
||||
<div class={styles.backdrop} onClick={handleHide}>
|
||||
<div class="wide-container">
|
||||
<div
|
||||
class={clsx(styles.backdrop, {
|
||||
[styles.isMobile]: isMobileView(),
|
||||
})}
|
||||
onClick={handleHide}
|
||||
>
|
||||
<div class={clsx('wide-container', styles.container)}>
|
||||
<div
|
||||
class={clsx(styles.modal, {
|
||||
[styles.narrow]: props.variant === 'narrow',
|
||||
|
@ -57,9 +71,11 @@ export const Modal = (props: Props) => {
|
|||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div class={styles.modalInner}>{props.children}</div>
|
||||
<Show when={!isMobileView()}>
|
||||
<div class={styles.close} onClick={handleHide}>
|
||||
<Icon name="close" class={styles.icon} />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
overflow: hidden;
|
||||
position: relative;
|
||||
transform: translateY(-2px);
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
overflow: auto;
|
||||
padding: 0 divide($container-padding-x, 2);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 2rem;
|
||||
gap: 1rem;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
text-align: left;
|
||||
.basicInfo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
flex: 1;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.picture {
|
||||
|
@ -24,7 +25,12 @@
|
|||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
border: none;
|
||||
margin-right: 1.2rem;
|
||||
|
||||
&.smallSize {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--black-50);
|
||||
|
@ -40,21 +46,18 @@
|
|||
|
||||
border: none;
|
||||
display: flex;
|
||||
flex: 0 calc(100% - 5.2rem);
|
||||
flex-direction: column;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
flex: 1 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include font-size(1.4rem);
|
||||
|
||||
font-weight: 500;
|
||||
line-height: 1em;
|
||||
color: var(--blue-500);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
@ -64,19 +67,9 @@
|
|||
}
|
||||
|
||||
.actions {
|
||||
flex: 0 20%;
|
||||
margin-left: 5.2rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
flex: 1;
|
||||
margin-left: auto;
|
||||
padding-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.subscribeButton {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo, createSignal, Show } from 'solid-js'
|
||||
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useMediaQuery } from '../../../context/mediaQuery'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
|
||||
import { follow, unfollow } from '../../../stores/zine/common'
|
||||
|
@ -17,10 +18,14 @@ type Props = {
|
|||
}
|
||||
|
||||
export const TopicBadge = (props: Props) => {
|
||||
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
||||
const { t } = useLocalize()
|
||||
const { mediaMatches } = useMediaQuery()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
||||
createEffect(() => {
|
||||
setIsMobileView(!mediaMatches.sm)
|
||||
})
|
||||
const {
|
||||
isAuthenticated,
|
||||
subscriptions,
|
||||
actions: { loadSubscriptions },
|
||||
} = useSession()
|
||||
|
@ -42,9 +47,13 @@ export const TopicBadge = (props: Props) => {
|
|||
|
||||
return (
|
||||
<div class={styles.TopicBadge}>
|
||||
<div class={styles.basicInfo}>
|
||||
<a
|
||||
href={`/topic/${props.topic.slug}`}
|
||||
class={clsx(styles.picture, { [styles.withImage]: props.topic.pic })}
|
||||
class={clsx(styles.picture, {
|
||||
[styles.withImage]: props.topic.pic,
|
||||
[styles.smallSize]: isMobileView(),
|
||||
})}
|
||||
style={
|
||||
props.topic.pic && {
|
||||
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
|
||||
|
@ -64,16 +73,13 @@ export const TopicBadge = (props: Props) => {
|
|||
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
|
||||
</Show>
|
||||
</a>
|
||||
<Show when={isAuthenticated()}>
|
||||
</div>
|
||||
|
||||
<div class={styles.actions}>
|
||||
<Show
|
||||
when={!props.minimizeSubscribeButton}
|
||||
fallback={
|
||||
<CheckButton
|
||||
text={t('Follow')}
|
||||
checked={subscribed()}
|
||||
onClick={() => subscribe(!subscribed)}
|
||||
/>
|
||||
<CheckButton text={t('Follow')} checked={subscribed()} onClick={() => subscribe(!subscribed)} />
|
||||
}
|
||||
>
|
||||
<Show
|
||||
|
@ -98,7 +104,6 @@ export const TopicBadge = (props: Props) => {
|
|||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -75,4 +75,9 @@
|
|||
|
||||
.viewSwitcher {
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
|
||||
&.bordered {
|
||||
border: 2px solid var(--black-100);
|
||||
|
|
31
src/context/mediaQuery.tsx
Normal file
31
src/context/mediaQuery.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import type { JSX } from 'solid-js'
|
||||
|
||||
import { createBreakpoints } from '@solid-primitives/media'
|
||||
import { createContext, useContext } from 'solid-js'
|
||||
|
||||
const breakpoints = {
|
||||
xs: '0',
|
||||
sm: '576px',
|
||||
md: '768px',
|
||||
lg: '992px',
|
||||
xl: '1200px',
|
||||
xxl: '1400px',
|
||||
}
|
||||
|
||||
type MediaQueryContextType = {
|
||||
mediaMatches: ReturnType<typeof createBreakpoints>
|
||||
}
|
||||
|
||||
const MediaQueryContext = createContext<MediaQueryContextType>()
|
||||
|
||||
export function useMediaQuery() {
|
||||
return useContext(MediaQueryContext)
|
||||
}
|
||||
|
||||
export const MediaQueryProvider = (props: { children: JSX.Element }) => {
|
||||
const mediaMatches = createBreakpoints(breakpoints)
|
||||
|
||||
const value: MediaQueryContextType = { mediaMatches }
|
||||
|
||||
return <MediaQueryContext.Provider value={value}>{props.children}</MediaQueryContext.Provider>
|
||||
}
|
Loading…
Reference in New Issue
Block a user