Color input.
This commit is contained in:
parent
3316df73e5
commit
77872ffa77
@ -10,7 +10,7 @@ export default class App extends PureComponent {
|
||||
return (
|
||||
<UrlStateProvider history={ this.props.history }>
|
||||
<Color>
|
||||
{ getColor => (
|
||||
{ ({ getColor }) => (
|
||||
<div className="app" style={{ backgroundColor: getColor('shade0') }}>
|
||||
<div className="container">
|
||||
<h1 style={{ color: getColor('shade7') }}>themer</h1>
|
||||
|
@ -1,20 +1,31 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { UrlStateConsumer } from './UrlState';
|
||||
import { get } from 'lodash';
|
||||
|
||||
export default class Color extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<UrlStateConsumer>
|
||||
{ ({ getValueOrFallback }) => this.props.children(
|
||||
(...args) => getValueOrFallback(
|
||||
{ ({ getValueOrFallback, mergeState, rawState }) => this.props.children({
|
||||
getColor: (...args) => getValueOrFallback(
|
||||
[...args].map(colorKey => [
|
||||
'colors',
|
||||
getValueOrFallback([['activeColorSet']]),
|
||||
colorKey
|
||||
]),
|
||||
true,
|
||||
)
|
||||
) }
|
||||
),
|
||||
setColor: (key, value) => {
|
||||
mergeState({
|
||||
colors: {
|
||||
[getValueOrFallback([['activeColorSet']])]: {
|
||||
[key]: value,
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getRawColor: key => get(rawState, ['colors', getValueOrFallback([['activeColorSet']]), key], ''),
|
||||
}) }
|
||||
</UrlStateConsumer>
|
||||
);
|
||||
}
|
||||
|
52
web/src/ColorInput.css
Normal file
52
web/src/ColorInput.css
Normal file
@ -0,0 +1,52 @@
|
||||
.color-input {
|
||||
--input-size: 1.5rem;
|
||||
}
|
||||
|
||||
.inputs-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
width: 8ch;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
font-family: "Fira Code", monospace;
|
||||
font-size: 1rem;
|
||||
line-height: 1rem;
|
||||
border: none;
|
||||
border-bottom-width: 0.125rem;
|
||||
border-bottom-style: solid;
|
||||
padding: 0.125rem 0.25rem;
|
||||
background: none;
|
||||
height: var(--input-size);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
height: var(--input-size);
|
||||
width: var(--input-size);
|
||||
border-top-left-radius: 0.125rem;
|
||||
border-top-right-radius: 0.125rem;
|
||||
border-bottom-right-radius: 0.125rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 0.75rem;
|
||||
}
|
53
web/src/ColorInput.js
Normal file
53
web/src/ColorInput.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import Color from './Color';
|
||||
import { Droplet } from './Icons';
|
||||
import './ColorInput.css';
|
||||
import getBestForeground from './getBestForeground';
|
||||
|
||||
export default class ColorInput extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<Color>
|
||||
{ ({ getColor, getRawColor, setColor }) => (
|
||||
<div className="color-input">
|
||||
<div className="inputs-wrapper">
|
||||
<label style={{ color: getColor('shade7') }}>
|
||||
<span className="label">{ this.props.colorKey }</span>
|
||||
<input
|
||||
type="text"
|
||||
style={{
|
||||
color: getColor('shade7'),
|
||||
borderBottomColor: getColor(this.props.colorKey, 'shade7'),
|
||||
}}
|
||||
value={ getRawColor(this.props.colorKey) }
|
||||
onChange={ evt => setColor(this.props.colorKey, evt.target.value) }
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
className="swatch"
|
||||
style={{
|
||||
color: getBestForeground(
|
||||
getColor('shade7'),
|
||||
getColor('shade0'),
|
||||
getColor(this.props.colorKey, 'shade7'),
|
||||
),
|
||||
backgroundColor: getColor(this.props.colorKey, 'shade7'),
|
||||
}}
|
||||
tabIndex="0"
|
||||
>
|
||||
<Droplet />
|
||||
<input
|
||||
type="color"
|
||||
value={ getRawColor(this.props.colorKey) }
|
||||
onChange={ evt => setColor(this.props.colorKey, evt.target.value) }
|
||||
tabIndex="-1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="help" style={{ color: getColor('shade4', 'shade7') }}>{ this.props.help }</div>
|
||||
</div>
|
||||
) }
|
||||
</Color>
|
||||
)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
font-size: 1rem;
|
||||
font-family: 'Fira Code', monospace;
|
||||
padding: 0.25em 0.5em;
|
||||
border-width: .0625rem;
|
||||
border-width: 0.0625rem;
|
||||
border-style: solid;
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-top-right-radius: 0.25rem;
|
||||
@ -11,3 +11,13 @@
|
||||
.tab-container button + button {
|
||||
margin-left: -0.0625rem;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
padding: 1rem;
|
||||
border-width: 0.0625rem;
|
||||
border-style: solid;
|
||||
margin-top: -0.0625rem;
|
||||
border-top-right-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ import React, { PureComponent } from 'react';
|
||||
import { UrlStateConsumer } from './UrlState';
|
||||
import './ColorSetInputs.css';
|
||||
import Color from './Color';
|
||||
import ColorInput from './ColorInput';
|
||||
|
||||
export default class ColorSetInputs extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<section>
|
||||
<UrlStateConsumer>
|
||||
{ ({ getValueOrFallback, mergeState }) => {
|
||||
const isDark = getValueOrFallback(['activeColorSet']) === 'dark';
|
||||
return (
|
||||
<Color>
|
||||
{ getColor => {
|
||||
{ ({ getColor }) => {
|
||||
const getTabStyle = active => ({
|
||||
backgroundColor: active ? getColor('shade0') : getColor('shade2', 'shade0'),
|
||||
color: getColor('shade7'),
|
||||
@ -22,6 +22,7 @@ export default class ColorSetInputs extends PureComponent {
|
||||
borderLeftColor: getColor('shade7'),
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div className="tab-container">
|
||||
<button
|
||||
style={ getTabStyle(isDark) }
|
||||
@ -32,13 +33,25 @@ export default class ColorSetInputs extends PureComponent {
|
||||
onClick={ () => mergeState({ activeColorSet: 'light' }) }
|
||||
>Light Variant</button>
|
||||
</div>
|
||||
<div className="input-container" style={{ borderColor: getColor('shade7') }}>
|
||||
<ColorInput colorKey="shade0" help="background color" />
|
||||
<ColorInput colorKey="shade7" help="foreground text" />
|
||||
<ColorInput colorKey="accent0" help="error, vcs deletion" />
|
||||
<ColorInput colorKey="accent1" help="syntax" />
|
||||
<ColorInput colorKey="accent2" help="warning, vcs modification" />
|
||||
<ColorInput colorKey="accent3" help="success, vcs addition" />
|
||||
<ColorInput colorKey="accent4" help="syntax" />
|
||||
<ColorInput colorKey="accent5" help="syntax" />
|
||||
<ColorInput colorKey="accent6" help="syntax, caret/cursor" />
|
||||
<ColorInput colorKey="accent7" help="syntax, special" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} }
|
||||
</Color>
|
||||
);
|
||||
} }
|
||||
</UrlStateConsumer>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
22
web/src/Icons.js
Normal file
22
web/src/Icons.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Droplet = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 3.004c.25.386.525.772.827 1.172.297.395 1.308 1.664 1.495 1.91C11.522 7.663 12 8.802 12 10.5c0 2.002-1.613 3.5-4 3.5s-4-1.498-4-3.5c0-1.698.48-2.837 1.678-4.414.187-.246 1.198-1.515 1.495-1.91.302-.4.576-.786.827-1.172z" stroke="currentColor" strokeWidth="2" fill="none" fillRule="evenodd"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Check = ({ backgroundColor, outlineColor, checkColor }) => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" style={{ backgroundColor, boxShadow: `inset 0 0 0 1px ${outlineColor}`, borderRadius: '2px' }}>
|
||||
<path d="M4 9l2 2M6 11l6-6" stroke={ checkColor } strokeWidth="2" fill="none" fillRule="evenodd" strokeLinecap="square"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Radio = ({ selected }) => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<circle stroke="currentColor" cx="8" cy="8" r="7.5"/>
|
||||
<circle fill={ selected ? 'currentColor' : 'transparent' } cx="8" cy="8" r="4"/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -67,6 +67,7 @@ export class UrlStateProvider extends Component {
|
||||
render() {
|
||||
return (
|
||||
<UrlStateContext.Provider value={{
|
||||
rawState: this.state,
|
||||
getValueOrFallback: this.getValueOrFallback,
|
||||
mergeState: this.mergeState,
|
||||
}}>
|
||||
|
13
web/src/getBestForeground.js
Normal file
13
web/src/getBestForeground.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Color from 'color';
|
||||
|
||||
export default (option1, option2, background) => {
|
||||
const op1 = Color(option1);
|
||||
const op2 = Color(option2);
|
||||
const bg = Color(background);
|
||||
if (op1.contrast(bg) > op2.contrast(bg)) {
|
||||
return op1.hex();
|
||||
}
|
||||
else {
|
||||
return op2.hex();
|
||||
}
|
||||
};
|
8
web/src/getBestForeground.test.js
Normal file
8
web/src/getBestForeground.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import getBestForeground from './getBestForeground';
|
||||
|
||||
describe('getBestForeground', () => {
|
||||
test('getBestForeground', () => {
|
||||
expect(getBestForeground('#ccc', '#333', '#000')).toBe('#CCCCCC');
|
||||
expect(getBestForeground('#ccc', '#333', '#fff')).toBe('#333333');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user