Markup for mobile fix-pack (#349)

* mediaQuery context provider
* Fix header styles
* User list markup fix
This commit is contained in:
Ilya Y 2023-12-29 09:39:16 +03:00 committed by GitHub
parent 41b5560036
commit 11d3a6c274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 198 additions and 110 deletions

View File

@ -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,17 +116,19 @@ export const App = (props: Props) => {
<MetaProvider>
<Meta name="viewport" content="width=device-width, initial-scale=1" />
<LocalizeProvider>
<SnackbarProvider>
<ConfirmProvider>
<SessionProvider>
<NotificationsProvider>
<EditorProvider>
<Dynamic component={pageComponent()} {...props} />
</EditorProvider>
</NotificationsProvider>
</SessionProvider>
</ConfirmProvider>
</SnackbarProvider>
<MediaQueryProvider>
<SnackbarProvider>
<ConfirmProvider>
<SessionProvider>
<NotificationsProvider>
<EditorProvider>
<Dynamic component={pageComponent()} {...props} />
</EditorProvider>
</NotificationsProvider>
</SessionProvider>
</ConfirmProvider>
</SnackbarProvider>
</MediaQueryProvider>
</LocalizeProvider>
</MetaProvider>
)

View File

@ -2,7 +2,6 @@
align-items: flex-start;
display: flex;
gap: 1rem;
margin-bottom: 3rem;
&.nameOnly {
align-items: center;

View File

@ -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 })}

View File

@ -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">

View File

@ -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;

View File

@ -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>

View File

@ -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%;
}
}

View File

@ -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>
<div class={styles.close} onClick={handleHide}>
<Icon name="close" class={styles.icon} />
</div>
<Show when={!isMobileView()}>
<div class={styles.close} onClick={handleHide}>
<Icon name="close" class={styles.icon} />
</div>
</Show>
</div>
</div>
</div>

View File

@ -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);
}

View File

@ -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 {

View File

@ -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,63 +47,63 @@ export const TopicBadge = (props: Props) => {
return (
<div class={styles.TopicBadge}>
<a
href={`/topic/${props.topic.slug}`}
class={clsx(styles.picture, { [styles.withImage]: props.topic.pic })}
style={
props.topic.pic && {
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
<div class={styles.basicInfo}>
<a
href={`/topic/${props.topic.slug}`}
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 })}')`,
}
}
}
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<span class={styles.title}>{props.topic.title}</span>
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<span class={styles.title}>{props.topic.title}</span>
<Show
when={props.topic.body}
fallback={
<div class={styles.description}>
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
</div>
}
>
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
</Show>
</a>
</div>
<div class={styles.actions}>
<Show
when={props.topic.body}
when={!props.minimizeSubscribeButton}
fallback={
<div class={styles.description}>
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
</div>
<CheckButton text={t('Follow')} checked={subscribed()} onClick={() => subscribe(!subscribed)} />
}
>
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
</Show>
</a>
<Show when={isAuthenticated()}>
<div class={styles.actions}>
<Show
when={!props.minimizeSubscribeButton}
when={subscribed()}
fallback={
<CheckButton
text={t('Follow')}
checked={subscribed()}
onClick={() => subscribe(!subscribed)}
<Button
variant="primary"
size="S"
value={isSubscribing() ? t('subscribing...') : t('Subscribe')}
onClick={() => subscribe(true)}
class={styles.subscribeButton}
/>
}
>
<Show
when={subscribed()}
fallback={
<Button
variant="primary"
size="S"
value={isSubscribing() ? t('subscribing...') : t('Subscribe')}
onClick={() => subscribe(true)}
class={styles.subscribeButton}
/>
}
>
<Button
onClick={() => subscribe(false)}
variant="bordered"
size="S"
value={t('Following')}
class={styles.subscribeButton}
/>
</Show>
<Button
onClick={() => subscribe(false)}
variant="bordered"
size="S"
value={t('Following')}
class={styles.subscribeButton}
/>
</Show>
</div>
</Show>
</Show>
</div>
</div>
)
}

View File

@ -75,4 +75,9 @@
.viewSwitcher {
margin-bottom: 2rem;
width: 100%;
@include media-breakpoint-down(sm) {
overflow-x: auto;
}
}

View File

@ -2,6 +2,7 @@
display: flex;
justify-content: flex-end;
position: relative;
min-width: 100px;
&.bordered {
border: 2px solid var(--black-100);

View 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>
}