Mobile menu support
This commit is contained in:
parent
96428c88f3
commit
a8801c0525
13 changed files with 186 additions and 63 deletions
|
@ -46,6 +46,9 @@
|
|||
}
|
||||
|
||||
.comment-text * {
|
||||
user-select: text;
|
||||
-webkit-user-select: text; /* Chrome all / Safari all */
|
||||
-moz-user-select: text; /* Firefox all */
|
||||
-ms-user-select: text; /* IE 10+ */
|
||||
user-select: text; /* Likely future */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
margin: 72px auto;
|
||||
max-width: 975px;
|
||||
height: calc(100% - 144px);
|
||||
|
||||
.hideOnWidescreen {
|
||||
/* Hide controls (e.g. close button) on larger screens */
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 975px) {
|
||||
position: fixed;
|
||||
|
@ -64,12 +69,5 @@
|
|||
> .content.fullwidth {
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
.hideOnWidescreen {
|
||||
/* Hide controls (e.g. close button) on larger screens */
|
||||
@media not screen and (max-width: 975px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
left: -200px;
|
||||
z-index: 10;
|
||||
|
||||
min-width: 420px;
|
||||
min-width: 430px;
|
||||
box-shadow: rgba(var(--main-fg), 0.1) 0px 0px 0px 1px, rgba(var(--main-fg), 0.1) 0px 2px 4px;
|
||||
background-color: rgb(var(--main-bg));
|
||||
padding: 10px;
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
@media screen and (max-width: 430px) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -20,7 +20,7 @@
|
|||
|
||||
.hideOnWidescreen {
|
||||
/* Hide controls (e.g. close button) on larger screens */
|
||||
@media not screen and (max-width: 420px) {
|
||||
@media not screen and (max-width: 430px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
user-select: none;
|
||||
outline: 0;
|
||||
-webkit-user-select: none; /* Chrome all / Safari all */
|
||||
-moz-user-select: none; /* Firefox all */
|
||||
-ms-user-select: none; /* IE 10+ */
|
||||
user-select: none; /* Likely future */
|
||||
}
|
||||
|
||||
html, body {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.Button {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
|
|
|
@ -23,8 +23,8 @@ export default class ColorOption extends React.PureComponent<ColorOptionProps> {
|
|||
className='MenuOption ColorOption menu-option'
|
||||
onClick={this.handleOnClick}
|
||||
>
|
||||
{icon ?? <div className='noicon'/>}
|
||||
<div className='menu-name'>{name}</div>
|
||||
{icon}
|
||||
<div className={`menu-colorbox ${id}`}/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -13,8 +13,9 @@ export default class LabelOption extends React.PureComponent<LabelOptionProps> {
|
|||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className='MenuOption LabelOption menu-option'>
|
||||
{this.props.icon ?? <div className='noicon'/>}
|
||||
<div className='menu-name'>{this.props.children}</div>
|
||||
{this.props.icon}
|
||||
<div className='noicon'/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.Menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 15;
|
||||
min-width: 180px;
|
||||
|
@ -8,11 +10,9 @@
|
|||
border-radius: 3px;
|
||||
box-shadow: rgba(var(--main-fg), 0.05) 0px 0px 0px 1px, rgba(var(--main-fg), 0.1) 0px 3px 6px, rgba(var(--main-fg), 0.2) 0px 9px 24px;
|
||||
|
||||
&.top {
|
||||
bottom: 100%;
|
||||
}
|
||||
&.left {
|
||||
right: 0;
|
||||
.menu-contents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-options {
|
||||
|
@ -20,47 +20,118 @@
|
|||
flex-direction: column;
|
||||
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
color: rgb(var(--main-fg));
|
||||
|
||||
>.menu-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
font-weight: 400;
|
||||
padding: 2px 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(90, 90, 90, 0.1);
|
||||
}
|
||||
>* {
|
||||
margin-left: 5px;
|
||||
}
|
||||
>*:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
>.menu-name {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
>.SubmenuTriangleIcon {
|
||||
fill: rgba(var(--main-fg), 0.7);
|
||||
}
|
||||
|
||||
>.Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
>.IconButton .Icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.menu-spacer {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
font-weight: 400;
|
||||
padding: 2px 10px;
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
.Menu, .SubMenuOption .SubMenu {
|
||||
@media screen and (max-width: 430px) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 0;
|
||||
background-color: rgba(var(--main-fg), 0.5);
|
||||
border-radius: 0;
|
||||
padding: 10px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(90, 90, 90, 0.1);
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
|
||||
.menu-contents {
|
||||
justify-content: flex-end;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.menu-name {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
margin-right: 20px;
|
||||
.menu-options {
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
flex: 0 0 auto;
|
||||
|
||||
>.menu-option {
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
background-color: rgb(var(--main-bg));
|
||||
|
||||
>* {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
>.noicon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
>.menu-name {
|
||||
font-size: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media not screen and (max-width: 430px) {
|
||||
&.top {
|
||||
bottom: 100%;
|
||||
}
|
||||
&.left {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.SubmenuTriangleIcon {
|
||||
fill: rgba(var(--main-fg), 0.7);
|
||||
}
|
||||
|
||||
.Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.IconButton .Icon {
|
||||
margin-right: 0;
|
||||
.hideOnWidescreen {
|
||||
/* Hide controls (e.g. close button) on larger screens */
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@ import LabelOption from './labelOption'
|
|||
|
||||
import './menu.scss'
|
||||
|
||||
type MenuProps = {
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
position?: 'top'|'bottom'|'left'
|
||||
}
|
||||
|
||||
export default class Menu extends React.PureComponent<MenuProps> {
|
||||
export default class Menu extends React.PureComponent<Props> {
|
||||
static Color = ColorOption
|
||||
static SubMenu = SubMenuOption
|
||||
static Switch = SwitchOption
|
||||
|
@ -28,10 +28,27 @@ export default class Menu extends React.PureComponent<MenuProps> {
|
|||
const {position, children} = this.props
|
||||
return (
|
||||
<div className={'Menu noselect ' + (position || 'bottom')}>
|
||||
<div className='menu-options'>
|
||||
{children}
|
||||
<div className='menu-contents'>
|
||||
<div className='menu-options'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className='menu-spacer hideOnWidescreen'/>
|
||||
|
||||
<div className='menu-options hideOnWidescreen'>
|
||||
<Menu.Text
|
||||
id='menu-cancel'
|
||||
name={'Cancel'}
|
||||
className='menu-cancel'
|
||||
onClick={this.onCancel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private onCancel = () => {
|
||||
// No need to do anything, as click bubbled up to MenuWrapper, which closes
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
.SubMenu {
|
||||
position: absolute;
|
||||
z-index: 15;
|
||||
z-index: 16;
|
||||
min-width: 180px;
|
||||
background-color: rgb(var(--main-bg));
|
||||
color: rgb(var(--main-fg));
|
||||
margin: 0 !important;
|
||||
|
||||
border-radius: 3px;
|
||||
box-shadow: rgba(var(--main-fg), 0.05) 0px 0px 0px 1px, rgba(var(--main-fg), 0.1) 0px 3px 6px, rgba(var(--main-fg), 0.2) 0px 9px 24px;
|
||||
|
@ -19,7 +20,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.SubmenuTriangleIcon {
|
||||
float: 'right'
|
||||
@media screen and (max-width: 430px) {
|
||||
.SubMenu {
|
||||
background-color: rgba(var(--main-fg), 0.8) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import React from 'react'
|
|||
|
||||
import SubmenuTriangleIcon from '../icons/submenuTriangle'
|
||||
|
||||
import Menu from '.'
|
||||
|
||||
import './subMenuOption.scss'
|
||||
|
||||
type SubMenuOptionProps = {
|
||||
|
@ -23,7 +25,9 @@ export default class SubMenuOption extends React.PureComponent<SubMenuOptionProp
|
|||
}
|
||||
|
||||
private handleMouseEnter = (): void => {
|
||||
this.setState({isOpen: true})
|
||||
setTimeout(() => {
|
||||
this.setState({isOpen: true})
|
||||
}, 50)
|
||||
}
|
||||
|
||||
private close = (): void => {
|
||||
|
@ -37,17 +41,34 @@ export default class SubMenuOption extends React.PureComponent<SubMenuOptionProp
|
|||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.close}
|
||||
>
|
||||
{this.props.icon}
|
||||
{this.props.icon ?? <div className='noicon'/>}
|
||||
<div className='menu-name'>{this.props.name}</div>
|
||||
<SubmenuTriangleIcon/>
|
||||
{this.state.isOpen &&
|
||||
<div className={'SubMenu menu noselect ' + (this.props.position || 'bottom')}>
|
||||
<div className='menu-options'>
|
||||
{this.props.children}
|
||||
<div className={'SubMenu Menu noselect ' + (this.props.position || 'bottom')}>
|
||||
<div className='menu-contents'>
|
||||
<div className='menu-options'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div className='menu-spacer hideOnWidescreen'/>
|
||||
|
||||
<div className='menu-options hideOnWidescreen'>
|
||||
<Menu.Text
|
||||
id='menu-cancel'
|
||||
name={'Cancel'}
|
||||
className='menu-cancel'
|
||||
onClick={this.onCancel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private onCancel = () => {
|
||||
// No need to do anything, as click bubbled up to MenuWrapper, which closes
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ export default class SwitchOption extends React.PureComponent<SwitchOptionProps>
|
|||
className='MenuOption SwitchOption menu-option'
|
||||
onClick={this.handleOnClick}
|
||||
>
|
||||
{icon ?? <div className='noicon'/>}
|
||||
<div className='menu-name'>{name}</div>
|
||||
{icon}
|
||||
<Switch
|
||||
isOn={isOn}
|
||||
onChanged={() => {}}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {MenuOptionProps} from './menuItem'
|
|||
type TextOptionProps = MenuOptionProps & {
|
||||
icon?: React.ReactNode,
|
||||
rightIcon?: React.ReactNode,
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default class TextOption extends React.PureComponent<TextOptionProps> {
|
||||
|
@ -17,14 +18,18 @@ export default class TextOption extends React.PureComponent<TextOptionProps> {
|
|||
|
||||
public render(): JSX.Element {
|
||||
const {name, icon, rightIcon} = this.props
|
||||
let className = 'MenuOption TextOption menu-option'
|
||||
if (this.props.className) {
|
||||
className += ' ' + this.props.className
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className='MenuOption TextOption menu-option'
|
||||
className={className}
|
||||
onClick={this.handleOnClick}
|
||||
>
|
||||
{icon}
|
||||
{icon ?? <div className='noicon'/>}
|
||||
<div className='menu-name'>{name}</div>
|
||||
{rightIcon}
|
||||
{rightIcon ?? <div className='noicon'/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue