flow: think I'm finally happy with mobile

This commit is contained in:
cha0s 2020-07-25 02:34:54 -05:00
parent fd1de29781
commit ef772986cf
10 changed files with 155 additions and 116 deletions

View File

@ -12,6 +12,9 @@ export default function Bar(props) {
className,
onActive,
} = props;
if (buttons.length < 2) {
return null;
}
return (
<div
className={classnames(['bar', className])}

View File

@ -22,9 +22,9 @@
align-items: center;
color: $color-muted;
display: flex;
justify-content: space-around;
font-family: var(--thick-title-font-family);
font-size: 0.95em;
justify-content: space-around;
padding: 1em;
user-select: none;
.closed & {
@ -38,12 +38,7 @@
.bar__buttonItem {
position: relative;
padding: 0.5em;
.closed & {
padding-bottom: 0;
}
.open & {
padding-right: 0;
}
}
.bar__button {

View File

@ -1,108 +1,52 @@
import './chat--center.scss';
import PropTypes from 'prop-types';
import React from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {
leftActiveIndexSelector,
rightActiveIndexSelector,
setLeftActiveIndex,
setRightActiveIndex,
toggleLeftIsOpen,
toggleRightIsOpen,
} from '~/common/state/app';
import {
channelUsersSelector,
} from '~/common/state/chat';
import {
blockedSelector,
idSelector,
pendingFriendshipSelector,
unreadChannelSelector,
unreadUserSelector,
} from '~/common/state/user';
import useChannel from '~/client/hooks/useChannel';
import Bar from './bar';
import ChatMessages from './chat--messages';
export default function ChatCenter() {
const dispatch = useDispatch();
const leftActiveIndex = useSelector(leftActiveIndexSelector);
const rightActiveIndex = useSelector(rightActiveIndexSelector);
const blockedIds = useSelector(blockedSelector);
export default function ChatCenter(props) {
const {onButton} = props;
const channel = useChannel();
const channelUsers = useSelector((state) => channelUsersSelector(state, channel));
const id = useSelector(idSelector);
const pendingFriendship = useSelector(pendingFriendshipSelector);
const unreadChannel = useSelector(unreadChannelSelector);
const unreadUser = useSelector(unreadUserSelector);
const leftButtons = [
{
count: unreadChannel,
icon: '💬',
label: 'Chat',
},
{
count: unreadUser + pendingFriendship.filter(({addeeId}) => addeeId === id).length,
icon: '😁',
label: 'Friends',
},
];
const rightButtons = []
.concat(channelUsers.length > 0 ? [{icon: '🙃', label: 'Present'}] : [])
.concat(blockedIds.length > 0 ? [{icon: '☢️', label: 'Blocked'}] : []);
return (
<div
className="center flexed"
>
<div className="center__header">
<Bar
active={leftActiveIndex}
branding={(
<li className="bar__brandItem">
<div>
reddi
<span className="muted">?</span>
</div>
{' '}
<div>
chat
<span className="muted">!</span>
</div>
</li>
)}
buttons={leftButtons}
className="bar--left"
isHorizontal
onActive={(active, i) => {
if (i === active) {
dispatch(toggleLeftIsOpen());
}
dispatch(setLeftActiveIndex(active));
}}
/>
<button
className="center__headerButton button--left"
onClick={() => onButton('left')}
type="button"
>
<span
className="bar__buttonIcon"
role="img"
aria-label="Chats"
>
💬
</span>
</button>
<div className="center__headerChannel">
<span className="tiny">chatting in</span>
<span>{channel}</span>
</div>
<Bar
active={rightActiveIndex}
buttons={rightButtons}
className="bar--right"
isHorizontal
onActive={(active, i) => {
if (i === active) {
dispatch(toggleRightIsOpen());
}
dispatch(setRightActiveIndex(active));
}}
/>
<button
className="center__headerButton button--right"
onClick={() => onButton('right')}
type="button"
>
<svg viewBox="0 0 512 512">
<path d="M437.02,330.98c-27.883-27.882-61.071-48.523-97.281-61.018C378.521,243.251,404,198.548,404,148 C404,66.393,337.607,0,256,0S108,66.393,108,148c0,50.548,25.479,95.251,64.262,121.962 c-36.21,12.495-69.398,33.136-97.281,61.018C26.629,379.333,0,443.62,0,512h40c0-119.103,96.897-216,216-216s216,96.897,216,216 h40C512,443.62,485.371,379.333,437.02,330.98z M256,256c-59.551,0-108-48.448-108-108S196.449,40,256,40 c59.551,0,108,48.448,108,108S315.551,256,256,256z" />
</svg>
</button>
</div>
<ChatMessages />
</div>
);
}
ChatCenter.propTypes = {};
ChatCenter.propTypes = {
onButton: PropTypes.func.isRequired,
};

View File

@ -5,7 +5,7 @@
flex-grow: 1;
width: 100%;
z-index: 100;
@include breakpoint(tablet) {
@include breakpoint(desktop) {
width: auto;
}
}
@ -18,6 +18,22 @@
position: relative;
}
.center__headerButton {
border: none;
height: 3em;
width: 3em;
@include breakpoint(desktop) {
display: none;
}
}
.button--right {
padding: 1em;
path {
fill: white;
}
}
.center__headerChannel {
align-self: center;
display: flex;

View File

@ -2,26 +2,73 @@ import './chat--left.scss';
import classnames from 'classnames';
import React from 'react';
import {useSelector} from 'react-redux';
import {useDispatch, useSelector} from 'react-redux';
import {
leftActiveIndexSelector,
leftIsOpenSelector,
setLeftActiveIndex,
} from '~/common/state/app';
import {
idSelector,
pendingFriendshipSelector,
unreadChannelSelector,
unreadUserSelector,
} from '~/common/state/user';
import useBreakpoints from './hooks/useBreakpoints';
import useBreakpoints from '~/client/hooks/useBreakpoints';
import Bar from './bar';
import ChatLeftFriends from './chat--leftFriends';
import ChatLeftRooms from './chat--leftRooms';
export default function ChatLeft() {
const dispatch = useDispatch();
const leftActiveIndex = useSelector(leftActiveIndexSelector);
const active = useSelector(leftActiveIndexSelector);
const isOpen = useSelector(leftIsOpenSelector);
const {tablet} = useBreakpoints();
const id = useSelector(idSelector);
const pendingFriendship = useSelector(pendingFriendshipSelector);
const unreadChannel = useSelector(unreadChannelSelector);
const unreadUser = useSelector(unreadUserSelector);
const showsAsOpen = isOpen || !tablet;
const leftButtons = [
{
count: unreadChannel,
icon: '💬',
label: 'Chat',
},
{
count: unreadUser + pendingFriendship.filter(({addeeId}) => addeeId === id).length,
icon: '😁',
label: 'Friends',
},
];
return (
<div
className={classnames('left', 'flexed', showsAsOpen ? 'open' : 'closed')}
>
<Bar
active={leftActiveIndex}
branding={(
<li className="bar__brandItem">
<div>
reddi
<span className="muted">?</span>
</div>
{' '}
<div>
chat
<span className="muted">!</span>
</div>
</li>
)}
buttons={leftButtons}
className="bar--left"
isHorizontal
onActive={(active) => dispatch(setLeftActiveIndex(active))}
/>
{0 === active && <ChatLeftRooms />}
{1 === active && <ChatLeftFriends />}
</div>

View File

@ -5,16 +5,17 @@
background-color: #373737;
flex-shrink: 0;
transform: translateX(-100%);
transition: 0.2s width;
@include breakpoint(tablet) {
transition: width 0.2s;
width: 25em;
@include breakpoint(desktop) {
transform: none;
}
}
.left.open {
width: 25em;
}
.bar__brandItem .muted {
opacity: 0.5;
}
.left .bar__buttonItem:last-child {
padding-right: 0.5em;
}

View File

@ -2,22 +2,28 @@ import './chat--right.scss';
import classnames from 'classnames';
import React from 'react';
import {useSelector} from 'react-redux';
import {useDispatch, useSelector} from 'react-redux';
import {
rightActiveIndexSelector,
rightIsOpenSelector,
setRightActiveIndex,
} from '~/common/state/app';
import {channelUsersSelector} from '~/common/state/chat';
import {blockedSelector} from '~/common/state/user';
import {
blockedSelector,
} from '~/common/state/user';
import useBreakpoints from './hooks/useBreakpoints';
import useChannel from '~/client/hooks/useChannel';
import Bar from './bar';
import ChatRightBlocked from './chat--rightBlocked';
import ChatRightUsers from './chat--rightUsers';
export default function ChatRight() {
const dispatch = useDispatch();
const rightActiveIndex = useSelector(rightActiveIndexSelector);
const active = useSelector(rightActiveIndexSelector);
const channel = useChannel();
const blockedIds = useSelector(blockedSelector);
@ -25,16 +31,23 @@ export default function ChatRight() {
const isOpen = useSelector(rightIsOpenSelector);
const {tablet} = useBreakpoints();
const showsAsOpen = isOpen || !tablet;
const buttons = []
const rightButtons = []
.concat(channelUsers.length > 0 ? [{icon: '🙃', label: 'Present'}] : [])
.concat(blockedIds.length > 0 ? [{icon: '☢️', label: 'Blocked'}] : []);
if (0 === buttons.length) {
if (0 === rightButtons.length) {
return null;
}
return (
<div
className={classnames('right', 'flexed', showsAsOpen ? 'open' : 'closed')}
>
<Bar
active={rightActiveIndex}
buttons={rightButtons}
className="bar--right"
isHorizontal
onActive={(active) => dispatch(setRightActiveIndex(active))}
/>
{0 === active && channelUsers.length > 0 && <ChatRightUsers />}
{1 === active && blockedIds.length > 0 && <ChatRightBlocked ids={blockedIds} />}
</div>

View File

@ -6,7 +6,7 @@
right: 0;
transform: translateX(100%);
transition: 0.2s width;
@include breakpoint(tablet) {
@include breakpoint(desktop) {
transform: none;
}
}

View File

@ -19,7 +19,7 @@ import ChatRight from './chat--right';
export default function Chat() {
const dispatch = useDispatch();
const ref = useRef(null);
const {tablet} = useBreakpoints();
const {desktop} = useBreakpoints();
const user = useSelector(userSelector);
const channel = useChannel();
const hasChannel = !!useSelector((state) => channelSelector(state, channel));
@ -45,10 +45,18 @@ export default function Chat() {
const abstract = {
down: ({pageX}) => {
if (ref.current) {
const left = parseInt(ref.current.style.left || 0, 10);
const windowWidth = window.innerWidth;
const max = Math.min(windowWidth - (4 * 16), 25 * 16);
origin = {
left: parseInt(ref.current.style.left || 0, 10),
left,
point: pageX,
};
if (
(left === -max && pageX < windowWidth - max)
|| (left === max && pageX > max)) {
ref.current.style.left = '0';
}
}
},
move: ({pageX}) => {
@ -56,13 +64,13 @@ export default function Chat() {
ref.current.classList.add('moving');
const offset = pageX - origin.point + origin.left;
const snapped = Math.abs(offset) < 64 ? 0 : offset;
const max = Math.min(window.innerWidth - 32, 25 * 16);
const max = Math.min(window.innerWidth - (4 * 16), 25 * 16);
ref.current.style.left = `${Math.max(-max, Math.min(max, snapped))}px`;
}
},
up: () => {
const left = parseInt(ref.current.style.left, 10);
const max = Math.min(window.innerWidth - 32, 25 * 16);
const max = Math.min(window.innerWidth - (4 * 16), 25 * 16);
if (left >= max / 2) {
ref.current.style.left = `${max}px`;
}
@ -70,7 +78,7 @@ export default function Chat() {
ref.current.style.left = `${-max}px`;
}
else {
ref.current.removeAttribute('style');
ref.current.style.left = '0';
}
origin = undefined;
ref.current.classList.remove('moving');
@ -90,18 +98,29 @@ export default function Chat() {
abstract.up(event.touches[0]);
},
};
if (tablet && ref.current) {
ref.current.removeAttribute('style');
if (desktop && ref.current) {
ref.current.style.left = '0';
}
return (
<div
className="chat"
// eslint-disable-next-line react/jsx-props-no-spreading
{...(tablet ? {} : onPointerEvents)}
{...(desktop ? {} : onPointerEvents)}
ref={ref}
>
{!user.isAnonymous && <ChatLeft />}
<ChatCenter />
<ChatCenter
onButton={(type) => {
const max = Math.min(window.innerWidth - (4 * 16), 25 * 16);
const offset = parseInt(ref.current.style.left || '0px', 10);
if ('right' === type) {
ref.current.style.left = offset > -(max / 2) ? `-${max}px` : '0';
}
else {
ref.current.style.left = offset < (max / 2) ? `${max}px` : '0';
}
}}
/>
{!user.isAnonymous && <ChatRight />}
</div>
);

View File

@ -3,6 +3,7 @@
.chat {
display: flex;
height: 100%;
left: 0;
position: absolute;
width: 100%;
&:not(.moving) {
@ -16,9 +17,9 @@
width: 0;
overflow: hidden;
&:not(.center) {
max-width: calc(100% - 2em);
max-width: calc(100% - 4em);
}
@include breakpoint(tablet) {
@include breakpoint(desktop) {
position: static;
}
}