New settings menu (#588)

* New settings menu

* remove showPresets

* clean

* use React.memo for pure function components
main
Sean 6 years ago committed by Michael Fix
parent 07bfe4ca43
commit 070b7f8aaf

@ -151,7 +151,7 @@ class BackgroundSelect extends React.Component {
.picker-tab { .picker-tab {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
background: rgba(255, 255, 255, 0.165); background: ${COLORS.DARK_GRAY};
width: 50%; width: 50%;
text-align: center; text-align: center;
padding: 8px 0; padding: 8px 0;
@ -191,7 +191,7 @@ class BackgroundSelect extends React.Component {
box-shadow: none; box-shadow: none;
outline: none; outline: none;
border-radius: 2px; border-radius: 2px;
background: rgba(255, 255, 255, 0.165); background: ${COLORS.DARK_GRAY};
color: #fff !important; color: #fff !important;
} }

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import Downshift from 'downshift' import Downshift from 'downshift'
import matchSorter from 'match-sorter' import matchSorter from 'match-sorter'
import ArrowDown from './svg/Arrowdown' import { Down as ArrowDown } from './svg/Arrows'
import CheckMark from './svg/Checkmark' import CheckMark from './svg/Checkmark'
import { COLORS } from '../lib/constants' import { COLORS } from '../lib/constants'
@ -197,7 +197,7 @@ const SelectedItem = ({
className="dropdown-display-text" className="dropdown-display-text"
/> />
<div className="dropdown-arrow"> <div className="dropdown-arrow">
<ArrowDown fill={itemColor} /> <ArrowDown color={itemColor} />
</div> </div>
<style jsx> <style jsx>
{` {`

@ -6,7 +6,7 @@ const Font = font => <span style={{ fontFamily: font.id }}>{font.name}</span>
function FontSelect(props) { function FontSelect(props) {
return ( return (
<ListSetting title="Font family" items={FONTS} {...props}> <ListSetting title="Font" items={FONTS} {...props}>
{Font} {Font}
</ListSetting> </ListSetting>
) )

@ -35,7 +35,7 @@ class ListSetting extends React.Component {
user-select: none; user-select: none;
padding: 8px 16px; padding: 8px 16px;
border-bottom: 1px solid ${COLORS.SECONDARY}; border-bottom: 1px solid ${COLORS.SECONDARY};
background: rgba(255, 255, 255, 0.165); background: ${COLORS.DARK_GRAY};
} }
.list-item:first-of-type { .list-item:first-of-type {
border-top: 1px solid ${COLORS.SECONDARY}; border-top: 1px solid ${COLORS.SECONDARY};

@ -1,137 +1,354 @@
import React from 'react' import React from 'react'
import enhanceWithClickOutside from 'react-click-outside' import enhanceWithClickOutside from 'react-click-outside'
import SettingsIcon from './svg/Settings'
import ThemeSelect from './ThemeSelect' import ThemeSelect from './ThemeSelect'
import FontSelect from './FontSelect' import FontSelect from './FontSelect'
import Slider from './Slider' import Slider from './Slider'
import Toggle from './Toggle' import Toggle from './Toggle'
import WindowPointer from './WindowPointer' import WindowPointer from './WindowPointer'
import Collapse from './Collapse'
import { COLORS } from '../lib/constants' import { COLORS } from '../lib/constants'
import { toggle, formatCode } from '../lib/util' import { toggle, formatCode } from '../lib/util'
import SettingsIcon from './svg/Settings'
import * as Arrows from './svg/Arrows'
//import Remove from './svg/Remove'
class Settings extends React.PureComponent { const WindowSettings = React.memo(
constructor(props) { ({
super(props) onChange,
this.state = { windowTheme,
isVisible: false paddingHorizontal,
paddingVertical,
dropShadow,
dropShadowBlurRadius,
dropShadowOffsetY,
windowControls
}) => {
return (
<div className="settings-content">
<ThemeSelect
selected={windowTheme || 'none'}
onChange={onChange.bind(null, 'windowTheme')}
/>
<div className="row">
<Slider
label="Padding (vert)"
value={paddingVertical}
maxValue={200}
onChange={onChange.bind(null, 'paddingVertical')}
/>
<Slider
label="Padding (horiz)"
value={paddingHorizontal}
onChange={onChange.bind(null, 'paddingHorizontal')}
/>
</div>
<Toggle
label="Drop shadow"
enabled={dropShadow}
onChange={onChange.bind(null, 'dropShadow')}
/>
{dropShadow && (
<div className="row drop-shadow-options">
<Slider
label="(offset-y)"
value={dropShadowOffsetY}
onChange={onChange.bind(null, 'dropShadowOffsetY')}
/>
<Slider
label="(blur-radius)"
value={dropShadowBlurRadius}
onChange={onChange.bind(null, 'dropShadowBlurRadius')}
/>
</div>
)}
<Toggle
label="Window controls"
enabled={windowControls}
onChange={onChange.bind(null, 'windowControls')}
/>
<style jsx>
{`
.row {
display: flex;
}
.row > :global(div:first-child) {
border-right: 1px solid ${COLORS.SECONDARY};
}
.drop-shadow-options {
opacity: 0.5;
}
`}
</style>
</div>
)
}
)
const TypeSettings = React.memo(({ onChange, font, size, lineHeight }) => {
return (
<div className="settings-content">
<FontSelect selected={font} onChange={onChange.bind(null, 'fontFamily')} />
<Slider
label="Size"
value={size}
minValue={10}
maxValue={18}
step={0.5}
onChange={onChange.bind(null, 'fontSize')}
/>
<Slider
label="Line height"
value={lineHeight}
minValue={90}
maxValue={250}
usePercentage={true}
onChange={onChange.bind(null, 'lineHeight')}
/>
</div>
)
})
const MiscSettings = React.memo(({ format, reset }) => {
return (
<div className="settings-content">
<button onClick={format}>Prettify code</button>
<button onClick={reset} className="reset-button">
Reset settings
</button>
<style jsx>
{`
button {
outline: none;
border: none;
background: transparent;
cursor: pointer;
flex: 1;
color: ${COLORS.SECONDARY};
font-size: 12px;
padding: 0;
}
.settings-content {
display: flex;
flex-direction: column;
}
.reset-button {
border-top: 1px solid ${COLORS.SECONDARY};
color: ${COLORS.RED};
}
`}
</style>
</div>
)
})
const MenuButton = React.memo(({ name, select, selected }) => {
return (
<button onClick={select(name)} className={selected === name ? 'selected' : ''}>
{name}
<div className="arrow-icon">
<Arrows.Right />
</div>
<style jsx>
{`
button {
outline: none;
border: none;
background: transparent;
color: ${COLORS.SECONDARY};
display: flex;
padding: 8px;
height: 33px;
cursor: pointer;
font-size: 12px;
border-bottom: 1px solid ${COLORS.SECONDARY};
position: relative;
}
button:last-child {
${selected === 'Window' ? '' : 'border-bottom: none;'};
}
button.selected {
background-color: ${COLORS.BLACK};
}
.arrow-icon {
position: absolute;
right: 16px;
}
`}
</style>
</button>
)
})
/*const Presets = React.memo(({ show, presets, toggle, create, remove }) => {
return (
<div className="settings-presets">
<div className="settings-presets-header">
<span>Presets</span>
{show && <button className="settings-presets-create" onClick={create}>create +</button>}
<button className="settings-presets-arrow" onClick={toggle}>
{show ? <Arrows.Up /> : <Arrows.Down />}
</button>
</div>
{show && (
<div className="settings-presets-content">
{presets.map(({ id, backgroundColor, userCreated }) => (
<div key={id} className="settings-presets-preset" style={{
backgroundColor
}}>
{
userCreated ? <button className="settings-presets-remove" onClick={() => remove(id)}><Remove /></button> : null
} }
this.toggle = this.toggle.bind(this) </div>
this.format = this.format.bind(this) ))}
</div>
)}
<style jsx>
{`
.settings-presets {
border-bottom: 1px solid ${COLORS.SECONDARY};
}
.settings-presets-header {
display: flex;
padding: 10px 8px;
position: relative;
color: ${COLORS.SECONDARY};
width: 100%;
align-items: center;
}
.settings-presets-arrow, .settings-presets-create, .settings-presets-remove {
cursor: pointer;
background: transparent;
outline: none;
border: none;
font-size: 12px;
}
.settings-presets-create {
color: ${COLORS.GRAY};
padding: 0 8px;
}
.settings-presets-arrow {
position: absolute;
right: 16px;
}
.settings-presets-content {
display: flex;
overflow-x: scroll;
margin: 12px 8px;
}
.settings-presets-preset {
border-radius: 3px;
height: 96px;
margin-right: 8px;
flex: 0 0 96px;
position: relative;
} }
toggle() { .settings-presets-remove {
this.setState(toggle('isVisible')) display: flex;
align-items: center;
justify-content: center;
position: absolute;
padding: 0;
top: 6px;
right: 6px;
width: 11px;
height: 11px;
border-radius: 999px;
background-color: ${COLORS.SECONDARY};
} }
`}
</style>
</div>
)
})*/
handleClickOutside() { class Settings extends React.PureComponent {
this.setState({ isVisible: false }) state = {
isVisible: false,
selectedMenu: 'Window',
showPresets: false
} }
format() { toggleVisible = () => this.setState(toggle('isVisible'))
return formatCode(this.props.code)
togglePresets = () => this.setState(toggle('showPresets'))
handleClickOutside = () => this.setState({ isVisible: false })
format = () =>
formatCode(this.props.code)
.then(this.props.onChange.bind(this, 'code')) .then(this.props.onChange.bind(this, 'code'))
.catch(() => { .catch(() => {
// create toast here in the future // create toast here in the future
}) })
selectMenu = selectedMenu => () => this.setState({ selectedMenu })
renderContent = () => {
switch (this.state.selectedMenu) {
case 'Window':
return (
<WindowSettings
onChange={this.props.onChange}
windowTheme={this.props.windowTheme}
paddingHorizontal={this.props.paddingHorizontal}
paddingVertical={this.props.paddingVertical}
dropShadow={this.props.dropShadow}
dropShadowBlurRadius={this.props.dropShadowBlurRadius}
dropShadowOffsetY={this.props.dropShadowOffsetY}
windowControls={this.props.windowControls}
/>
)
case 'Type':
return (
<TypeSettings
onChange={this.props.onChange}
font={this.props.fontFamily}
size={this.props.fontSize}
lineHeight={this.props.lineHeight}
/>
)
case 'Misc':
return <MiscSettings format={this.format} reset={this.props.resetDefaultSettings} />
default:
return null
}
} }
render() { render() {
const { isVisible, selectedMenu } = this.state
return ( return (
<div className="settings-container"> <div className="settings-container">
<div <div
className={`settings-display ${this.state.isVisible ? 'is-visible' : ''}`} className={`settings-display ${isVisible ? 'is-visible' : ''}`}
onClick={this.toggle} onClick={this.toggleVisible}
> >
<SettingsIcon /> <SettingsIcon />
</div> </div>
<div className="settings-settings"> <div className="settings-settings">
<WindowPointer fromLeft="15px" /> <WindowPointer fromLeft="15px" />
<ThemeSelect <div className="settings-bottom">
selected={this.props.windowTheme || 'none'} <div className="settings-menu">
onChange={this.props.onChange.bind(null, 'windowTheme')} <MenuButton name="Window" select={this.selectMenu} selected={selectedMenu} />
/> <MenuButton name="Type" select={this.selectMenu} selected={selectedMenu} />
<FontSelect <MenuButton name="Misc" select={this.selectMenu} selected={selectedMenu} />
selected={this.props.fontFamily || 'Hack'} </div>
onChange={this.props.onChange.bind(null, 'fontFamily')} {this.renderContent()}
/> </div>
<Slider
label="Font size"
value={this.props.fontSize || 13}
minValue={10}
maxValue={18}
step={0.5}
onChange={this.props.onChange.bind(null, 'fontSize')}
/>
<Toggle
label="Window controls"
enabled={this.props.windowControls}
onChange={this.props.onChange.bind(null, 'windowControls')}
/>
<Toggle
label="Line numbers"
enabled={this.props.lineNumbers}
onChange={this.props.onChange.bind(null, 'lineNumbers')}
/>
<Toggle
label="Auto-adjust width"
enabled={this.props.widthAdjustment}
onChange={this.props.onChange.bind(null, 'widthAdjustment')}
/>
<Collapse label="Advanced">
<Slider
label="Line height"
value={this.props.lineHeight}
minValue={90}
maxValue={250}
usePercentage={true}
onChange={this.props.onChange.bind(null, 'lineHeight')}
/>
<Slider
label="Padding (vertical)"
value={this.props.paddingVertical || 16}
maxValue={200}
onChange={this.props.onChange.bind(null, 'paddingVertical')}
/>
<Slider
label="Padding (horizontal)"
value={this.props.paddingHorizontal || 32}
onChange={this.props.onChange.bind(null, 'paddingHorizontal')}
/>
<Toggle
label="Drop shadow"
enabled={this.props.dropShadow}
onChange={this.props.onChange.bind(null, 'dropShadow')}
/>
<Slider
label="Drop shadow (offset-y)"
value={this.props.dropShadowOffsetY || 20}
onChange={this.props.onChange.bind(null, 'dropShadowOffsetY')}
/>
<Slider
label="Drop shadow (blur-radius)"
value={this.props.dropShadowBlurRadius || 68}
onChange={this.props.onChange.bind(null, 'dropShadowBlurRadius')}
/>
<Toggle
label="Squared image"
enabled={this.props.squaredImage}
onChange={this.props.onChange.bind(null, 'squaredImage')}
/>
<Toggle
label="Watermark"
enabled={this.props.watermark}
onChange={this.props.onChange.bind(null, 'watermark')}
/>
<Toggle label="Prettify code" center={true} enabled={false} onChange={this.format} />
<Toggle
label={<center className="red">Reset settings</center>}
center={true}
enabled={false}
onChange={this.props.resetDefaultSettings}
/>
</Collapse>
</div> </div>
<style jsx> <style jsx>
{` {`
@ -179,23 +396,32 @@ class Settings extends React.PureComponent {
top: 52px; top: 52px;
left: 0; left: 0;
border: 2px solid ${COLORS.SECONDARY}; border: 2px solid ${COLORS.SECONDARY};
width: 184px; width: 320px;
border-radius: 3px; border-radius: 3px;
background: ${COLORS.BLACK}; background: ${COLORS.BLACK};
} }
.settings-settings > :global(div) { .settings-bottom {
border-bottom: solid 1px ${COLORS.SECONDARY}; display: flex;
} }
.settings-settings > :global(div):first-child, .settings-menu {
.settings-settings > :global(div):last-child, display: flex;
.settings-settings > :global(.collapse) { flex-direction: column;
border-bottom: none; flex: 0 0 96px;
background-color: ${COLORS.DARK_GRAY};
}
`}
</style>
<style jsx global>
{`
.settings-content {
width: 100%;
border-left: 1px solid ${COLORS.SECONDARY};
} }
.red { .settings-content > div:not(:first-child) {
color: red; border-top: solid 1px ${COLORS.SECONDARY};
} }
`} `}
</style> </style>

@ -1,5 +1,7 @@
import React from 'react' import React from 'react'
import { COLORS } from '../lib/constants'
class Slider extends React.Component { class Slider extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -17,6 +19,14 @@ class Slider extends React.Component {
return ( return (
<div className="slider"> <div className="slider">
<div
className="slider-bg"
style={{
transform: `translate3d(${(((parseFloat(this.props.value) - minValue) * 1.0) /
(maxValue - minValue)) *
100}%, 0px, 0px)`
}}
/>
<span className="label">{this.props.label}</span> <span className="label">{this.props.label}</span>
<input <input
type="range" type="range"
@ -26,19 +36,11 @@ class Slider extends React.Component {
max={maxValue} max={maxValue}
step={step} step={step}
/> />
<div
className="slider-bg"
style={{
transform: `translate3d(${(((parseFloat(this.props.value) - minValue) * 1.0) /
(maxValue - minValue)) *
100}%, 0px, 0px)`
}}
/>
<style jsx> <style jsx>
{` {`
.slider { .slider {
position: relative; position: relative;
height: 32px; height: 33px;
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
} }
@ -50,8 +52,8 @@ class Slider extends React.Component {
.label { .label {
position: absolute; position: absolute;
left: 8px; left: 8px;
height: 32px; height: 33px;
line-height: 32px; line-height: 33px;
} }
.slider input { .slider input {
@ -67,9 +69,9 @@ class Slider extends React.Component {
top: 0; top: 0;
bottom: 0; bottom: 0;
pointer-events: none; pointer-events: none;
height: 32px; height: 33px;
width: 100%; width: 100%;
background: rgba(255, 255, 255, 0.165); background: ${COLORS.DARK_GRAY};
} }
`} `}
</style> </style>

@ -47,7 +47,7 @@ class ThemeSelect extends React.Component {
render() { render() {
return ( return (
<div className="window-theme"> <div className="window-theme">
<span className="label">Window theme</span> <span className="label">Theme</span>
<div className="themes">{this.renderThemes()}</div> <div className="themes">{this.renderThemes()}</div>
<style jsx> <style jsx>
{` {`

@ -1,14 +1,20 @@
import React from 'react' import React from 'react'
import Checkmark from './svg/Checkmark' import Checkmark from './svg/Checkmark'
import { COLORS } from '../lib/constants'
class Toggle extends React.PureComponent { class Toggle extends React.PureComponent {
static defaultProps = {
className: ''
}
toggle = () => this.props.onChange(!this.props.enabled) toggle = () => this.props.onChange(!this.props.enabled)
render() { render() {
return ( return (
<div className={`toggle ${this.props.className}`} onClick={this.toggle}> <div className={`toggle ${this.props.className}`} onClick={this.toggle}>
<span className="label">{this.props.label}</span> <span className="label">{this.props.label}</span>
{this.props.enabled ? <Checkmark /> : null} {this.props.enabled ? <Checkmark /> : <div className="checkmark-disabled" />}
<style jsx> <style jsx>
{` {`
.toggle { .toggle {
@ -19,6 +25,13 @@ class Toggle extends React.PureComponent {
user-select: none; user-select: none;
padding: 8px; padding: 8px;
} }
.checkmark-disabled {
width: 18px;
height: 18px;
border-radius: 36px;
background-color: ${COLORS.DARK_GRAY};
}
`} `}
</style> </style>
</div> </div>

@ -1,12 +0,0 @@
import React from 'react'
export default ({ fill }) => (
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" viewBox="0 0 10 6">
<path
fill={fill}
fillRule="evenodd"
d="M93.7890633,17.6396882 L97.4926052,14.0851393 C97.6061243,13.9716202 97.7196417,13.9716202 97.8331608,14.0851393 L98.4929872,14.723681 C98.6065063,14.8372001 98.6065063,14.9507175 98.4929872,15.0642366 L93.9593411,19.4063203 C93.9167714,19.4488899 93.8600127,19.4701744 93.7890633,19.4701744 C93.7181138,19.4701744 93.6613552,19.4488899 93.6187855,19.4063203 L89.0851393,15.0642366 C88.9716202,14.9507175 88.9716202,14.8372001 89.0851393,14.723681 L89.7449658,14.0851393 C89.8584849,13.9716202 89.9720022,13.9716202 90.0855213,14.0851393 L93.7890633,17.6396882 Z"
transform="translate(-89 -14)"
/>
</svg>
)

@ -0,0 +1,21 @@
import React from 'react'
const Up = ({ color = 'white' }) => (
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 5L5 1L9 5" stroke={color} />
</svg>
)
const Down = ({ color = 'white' }) => (
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1L5 5L1 1" stroke={color} />
</svg>
)
const Right = ({ color = 'white' }) => (
<svg width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 9L5 5L1 1" stroke={color} />
</svg>
)
export { Up, Down, Right }

@ -0,0 +1,10 @@
import React from 'react'
export default ({ color = 'black' }) => (
<svg width="5" height="5" viewBox="0 0 5 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.5 3.08725L4.41704 5L5 4.41834L3.08857 2.5L5 0.581656L4.41704 0L2.5 1.91275L0.58296 0L0 0.581656L1.91144 2.5L0 4.41834L0.58296 5L2.5 3.08725Z"
fill={color}
/>
</svg>
)

@ -478,8 +478,10 @@ export const COLORS = {
PRIMARY: '#F8E81C', PRIMARY: '#F8E81C',
SECONDARY: '#fff', SECONDARY: '#fff',
GRAY: '#858585', GRAY: '#858585',
DARK_GRAY: '#393939',
HOVER: '#1F1F1F', HOVER: '#1F1F1F',
PURPLE: '#C198FB' PURPLE: '#C198FB',
RED: 'red'
} }
export const DEFAULT_CODE = `const pluckDeep = key => obj => key.split('.').reduce((accum, key) => accum[key], obj) export const DEFAULT_CODE = `const pluckDeep = key => obj => key.split('.').reduce((accum, key) => accum[key], obj)

Loading…
Cancel
Save