...
 
Commits (2)
......@@ -39,5 +39,6 @@ module.exports = {
'key-spacing': 0,
'no-multi-spaces': 0,
'no-named-as-default-member': 0,
'arrow-parens': 0,
}
}
......@@ -14,6 +14,7 @@
},
"dependencies": {
"chroma-js": "^1.2.1",
"color-blind": "^0.1.1",
"color2": "^1.0.8",
"d3": "^4.3.0",
"file-saver": "^1.3.3",
......
<template>
<div class="accessibility-sample">
<div
v-if="title"
class="accessibility-sample__title"
>{{title}}</div>
<contrast-sample
:color-a="colorA"
:color-b="colorB"
text="three
two
one
..."
></contrast-sample>
<contrast-sample
:color-a="colorA"
:color-b="colorB"
>
<svg width="128" height="128" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="transform: rotate(20deg)">
<path d="M256 421.6c-18.1 0-33.2-6.8-42.9-10.9-5.4-2.3-11.3 1.8-10.9 7.6l3.5 51c.2 3.1 3.8 4.7 6.3 2.8l14.5-11c1.8-1.4 4.5-.9 5.7 1l20.5 32.1c1.5 2.4 5.1 2.4 6.6 0l20.5-32.1c1.2-1.9 3.9-2.4 5.7-1l14.5 11c2.5 1.9 6.1.3 6.3-2.8l3.5-51c.4-5.8-5.5-10-10.9-7.6-9.8 4.1-24.8 10.9-42.9 10.9z"/>
<path d="M397.7 293.1l-48-49.1c0-158-93.2-228-93.2-228s-94.1 70-94.1 228l-48 49.1c-1.8 1.8-2.6 4.5-2.2 7.1L130.6 412c.9 5.7 7.1 8.5 11.8 5.4l67.1-45.4s20.7 20 47.1 20c26.4 0 46.1-20 46.1-20l67.1 45.4c4.6 3.1 10.8.3 11.8-5.4l18.5-111.9c.2-2.6-.6-5.2-2.4-7zM256.5 192c-17 0-30.7-14.3-30.7-32s13.8-32 30.7-32c17 0 30.7 14.3 30.7 32s-13.7 32-30.7 32z"/>
</svg>
</contrast-sample>
<contrast-sample
:color-a="colorA"
:color-b="colorB"
>
<svg width="128" height="128" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="transform: rotate(20deg)">
<path d="M256 421.6c-18.1 0-33.2-6.8-42.9-10.9-5.4-2.3-11.3 1.8-10.9 7.6l3.5 51c.2 3.1 3.8 4.7 6.3 2.8l14.5-11c1.8-1.4 4.5-.9 5.7 1l20.5 32.1c1.5 2.4 5.1 2.4 6.6 0l20.5-32.1c1.2-1.9 3.9-2.4 5.7-1l14.5 11c2.5 1.9 6.1.3 6.3-2.8l3.5-51c.4-5.8-5.5-10-10.9-7.6-9.8 4.1-24.8 10.9-42.9 10.9z" fill="none" stroke-width="2" vector-effect="non-scaling-stroke"/>
<path d="M397.7 293.1l-48-49.1c0-158-93.2-228-93.2-228s-94.1 70-94.1 228l-48 49.1c-1.8 1.8-2.6 4.5-2.2 7.1L130.6 412c.9 5.7 7.1 8.5 11.8 5.4l67.1-45.4s20.7 20 47.1 20c26.4 0 46.1-20 46.1-20l67.1 45.4c4.6 3.1 10.8.3 11.8-5.4l18.5-111.9c.2-2.6-.6-5.2-2.4-7zM256.5 192c-17 0-30.7-14.3-30.7-32s13.8-32 30.7-32c17 0 30.7 14.3 30.7 32s-13.7 32-30.7 32z" fill="none" stroke-width="2" vector-effect="non-scaling-stroke"/>
</svg>
</contrast-sample>
</div>
</template>
<script>
import Color from '../scripts/Color';
import ContrastSample from './ContrastSample';
export default {
name: 'accessibility-sample',
props: {
title: String,
colorA: Color,
colorB: Color,
},
components: {
ContrastSample,
},
};
</script>
<style lang="scss">
.accessibility-sample {
display: flex;
flex-wrap: wrap;
justify-content: stretch;
margin: 1rem;
.contrast-sample {
flex: 1 1 auto;
}
}
.accessibility-sample__title {
flex-basis: 100%;
font-size: 1em;
font-weight: bold;
margin-bottom: 1rem;
font-family: Monaco;
}
</style>
......@@ -6,23 +6,25 @@
</template>
<script>
export default {
name: 'color-swatch',
props: {
value: String,
},
computed: {
style() {
return {
backgroundColor: this.value,
};
},
import Color from '../scripts/Color';
export default {
name: 'color-swatch',
props: {
color: Color,
},
computed: {
style() {
return {
backgroundColor: this.color.toRgbString(),
};
},
};
},
};
</script>
<style lang="scss">
.color-swatch {
height: 6rem;
}
.color-swatch {
height: 6rem;
}
</style>
<template>
<pre class="contrast-sample"
:style="style"
><slot>{{text}}</slot></pre>
</template>
<script>
import Color from '../scripts/Color';
export default {
name: 'contrast-sample',
props: {
value: Object,
text: {
type: String,
default: 'one\ntwo\nthree\nfour',
},
colorA: Color,
colorB: Color,
},
computed: {
style() {
const a = this.colorA.toRgbString();
const b = this.colorB.toRgbString();
return {
backgroundColor: a,
color: b,
fill: b,
stroke: b,
};
},
},
};
</script>
<style lang="scss">
.contrast-sample {
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
font-size: 2em;
max-width: 240px;
font-weight: bold;
margin: 0;
}
</style>
......@@ -42,121 +42,115 @@
</template>
<script>
import DataMixin from '../mixins/DataMixin';
import DataMixin from '../mixins/DataMixin';
import Palette from './Palette';
import ColorSwatch from './ColorSwatch';
import ColorInput from './ColorInput';
import ShadesItemConfig from './ShadesItemConfig';
import Palette from './Palette';
import ColorInput from './ColorInput';
import ShadesItemConfig from './ShadesItemConfig';
import ColorScale from '../scripts/ColorScale';
import ColorScale from '../scripts/ColorScale';
export default {
name: 'shades-item',
mixins: [
DataMixin,
],
props: {
id: undefined,
selected: {
type: Boolean,
default: false,
},
},
data() {
return {
newColorValue: undefined,
hover: false,
};
},
computed: {
color() {
return this.localData.value;
},
config() {
return this.localData.config;
},
scale() {
return new ColorScale(this.color, this.config);
},
shades() {
return this.scale.shadesRgb;
},
},
methods: {
emitColorSelect(color) {
this.$emit('color-select', color);
},
emitDelete() {
this.$emit('delete', this.data.id);
},
emitSelect() {
this.$emit('select', this.data.id);
},
emitShowConfig() {
this.$emit('show-config', this.data.id);
},
onColorSelect(d) {
this.emitColorSelect(d);
},
onColorChange(d) {
this.localData.value = d;
this.emitDataChange();
},
onNameChange(v) {
this.localData.name = v;
this.emitDataChange();
},
onSelect() {
this.emitSelect(this.data.id);
},
onConfig() {
this.emitShowConfig();
},
onDelete() {
this.emitDelete();
},
onMouseEnter() {
this.hover = true;
},
onMouseLeave() {
if (this.selected) { return; }
this.hover = false;
},
handleSelectedChange(v) {
this.hover = v;
},
},
watch: {
'selected': 'handleSelectedChange',
},
components: {
Palette,
ColorInput,
ColorSwatch,
ShadesItemConfig,
},
};
export default {
name: 'shades-item',
mixins: [DataMixin],
props: {
id: undefined,
selected: {
type: Boolean,
default: false,
},
},
data() {
return {
newColorValue: undefined,
hover: false,
};
},
computed: {
color() {
return this.localData.value;
},
config() {
return this.localData.config;
},
scale() {
return new ColorScale(this.color, this.config);
},
shades() {
return this.scale.shadesRgb;
},
},
methods: {
emitColorSelect(color) {
this.$emit('color-select', color);
},
emitDelete() {
this.$emit('delete', this.data.id);
},
emitSelect() {
this.$emit('select', this.data.id);
},
emitShowConfig() {
this.$emit('show-config', this.data.id);
},
onColorSelect(d) {
this.emitColorSelect(d);
},
onColorChange(d) {
this.localData.value = d;
this.emitDataChange();
},
onNameChange(v) {
this.localData.name = v;
this.emitDataChange();
},
onSelect() {
this.emitSelect(this.data.id);
},
onConfig() {
this.emitShowConfig();
},
onDelete() {
this.emitDelete();
},
onMouseEnter() {
this.hover = true;
},
onMouseLeave() {
if (this.selected) {
return;
}
this.hover = false;
},
handleSelectedChange(v) {
this.hover = v;
},
},
watch: {
selected: 'handleSelectedChange',
},
components: {
Palette,
ColorInput,
ShadesItemConfig,
},
};
</script>
<style lang="scss">
@import "../styles/core";
.shades-item {
margin: 0.5rem;
@import "../styles/core";
.palette {
min-width: 140px;
}
.shades-item {
margin: 0.5rem;
.color-swatch {
margin-bottom: 0.5rem;
}
}
.shades-item__name {
margin-bottom: 8px;
}
.shades-item__admin {
padding-top: 8px;
.palette {
min-width: 140px;
}
}
.shades-item__name {
margin-bottom: 8px;
}
.shades-item__admin {
padding-top: 8px;
}
</style>
......@@ -24,4 +24,9 @@ export default [
path: '/mixer',
component: views.MixerView,
},
{
name: 'accessibility',
path: '/accessibility',
component: views.AccessibilityView,
},
];
import Tinycolor from 'tinycolor2';
import blinder from 'color-blind';
class Color {
constructor(value) {
this._color = new Tinycolor(value);
}
toRgbString() {
return this._color.toRgbString();
}
toRgb() {
return this._color.toRgb();
}
deuteranomaly() {
return new Color(
blinder.deuteranomaly(this.toRgbString())
);
}
}
export default Color;
import Color from '../../../scripts/Color';
export default {
colorA: (state) => new Color(state.colorA),
colorB: (state) => new Color(state.colorB),
};
import state from './state';
import mutations from './mutations';
import actions from './actions';
import getters from './getters';
export default {
state,
mutations,
actions,
getters,
};
import types from './types';
export default {
[types.SET_COLOR_A](state, color) {
console.log(color);
state.colorA = color;
},
[types.SET_COLOR_B](state, color) {
state.colorB = color;
},
};
export default {
colorA: { r: 0, g: 0, b: 0, a: 1 },
colorB: { r: 255, g: 255, b: 255, a: 1 },
};
export default {
SET_COLOR_A: 'accessibility/setColorA',
SET_COLOR_B: 'accessibility/setColorB',
};
import gradients from './gradients';
import palettes from './palettes';
import accessibility from './accessibility';
export default {
gradients,
palettes,
accessibility,
};
import gradients from './modules/gradients/types';
import palettes from './modules/palettes/types';
import accessibility from './modules/accessibility/types';
// TODO: move all mutations to namespaced and add ...
export default {
gradients,
palettes,
...accessibility,
ADD_NOTIFICATION: 'core/addNotification',
DELETE_NOTIFICATION: 'core/deleteNotification',
UPDATE_CURRENT_COLOR: 'core/udpateCurrentColor',
......
......@@ -113,7 +113,7 @@
const w = this.width;
const h = this.height;
return (this.vertical) ?
return this.vertical ?
{
width: `${h}px`,
height: `${w}px`,
......@@ -175,19 +175,11 @@
let rgb;
for (let pos = 0; pos < width; ++pos) {
if (
(pos === _pos - halfHandleWidth) ||
(pos === _pos + halfHandleWidth)
) {
if (pos === _pos - halfHandleWidth || pos === _pos + halfHandleWidth) {
alpha = 1;
rgb = [0, 0, 0];
}
else {
const p =
(pos > _pos - halfHandleWidth) &&
(pos < _pos + halfHandleWidth)
? _pos
: pos;
} else {
const p = pos > _pos - halfHandleWidth && pos < _pos + halfHandleWidth ? _pos : pos;
alpha = this.alpha;
rgb = this.chromaScale(scale.invert(p)).rgb();
......@@ -203,7 +195,7 @@
},
onSwatchClick(variant) {
this.active = variant;
this.value = (variant === 'B') ? 1 : 0;
this.value = variant === 'B' ? 1 : 0;
},
onWidthChange() {
this.$options.scale.range([0, this.width]);
......@@ -211,7 +203,7 @@
},
onValueChange(v) {
const { min, max } = this.domain.sort();
const value = this.value = clamp(v, min, max);
const value = (this.value = clamp(v, min, max));
this.handlePos = Math.round(this.$options.scale(value));
this.render();
......@@ -259,7 +251,8 @@
this.$options.context = canvas.getContext('2d');
this.$options.scale = d3.scaleLinear().domain(this.domain);
d3.select(canvas)
d3
.select(canvas)
.call(d3.drag().on('drag', this.dragged))
.on('click', this.dragged);
......@@ -271,7 +264,7 @@
</script>
<style lang="scss">
@import './styles/core';
@import "./styles/core";
.ui-color-gradient {
display: flex;
......@@ -297,7 +290,7 @@
&::after {
@include ui-fit();
@include ui-alpha-background();
content: '';
content: "";
z-index: -1;
}
......@@ -330,7 +323,7 @@
color: white;
}
// remove stepper in chrome
// remove stepper in chrome
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
appearance: none;
......
import GradientView from './views/GradientView';
import MixerView from './views/MixerView';
import AccessibilityView from './views/AccessibilityView';
export default {
GradientView,
MixerView,
AccessibilityView,
};
<template>
<div class="accessibility-view">
<div class="accessibility-view__content">
<div class="accessibility-view__left">
<div
:class="{
'accessibility-view__color': true,
'accessibility-view__color--selected': (selected === 'A'),
}"
@click="onColorAClick"
>
<color-swatch
:color="colorA"
></color-swatch>
<color-input
:value="colorA.toRgb()"
@color-change="onColorAChange"
></color-input>
</div>
<div
:class="{
'accessibility-view__color': true,
'accessibility-view__color--selected': (selected === 'B'),
}"
@click="onColorBClick"
>
<color-swatch
:color="colorB"
></color-swatch>
<color-input
:value="colorB.toRgb()"
@color-change="onColorBChange"
></color-input>
</div>
<div class="accessibility-view__ratio">{{ratio}}</div>
</div>
<div class="accessibility-view__right">
<accessibility-sample
title="Normal vision"
:color-a="colorA"
:color-b="colorB"
></accessibility-sample>
<accessibility-sample
title="Deuteranomaly (~2.7%)"
:color-a="colorA.deuteranomaly()"
:color-b="colorB.deuteranomaly()"
></accessibility-sample>
</div>
</div>
<div class="accessibility-view__sidebar">
<mixer-bar
:value="currentColor"
@color-change="onMixerColorChange"
></mixer-bar>
</div>
</div>
</template>
<script>
import chroma from 'chroma-js';
import { round } from 'lodash';
import { mapMutations, mapGetters } from 'vuex';
import ColorInput from '../components/ColorInput';
import ColorSwatch from '../components/ColorSwatch';
import AccessibilitySample from '../components/AccessibilitySample';
import _ from '../utils';
import types from '../store/types';
import Color from '../scripts/Color';
import MixerBar from '../modules/MixerBar';
export default {
name: 'accessibility-view',
data() {
return {
currentColor: {
r: 0,
g: 0,
b: 0,
a: 1,
},
selected: 'A',
};
},
computed: {
...mapGetters([
'colorA',
'colorB',
]),
ratio() {
const a = this.colorA.toRgbString();
const b = this.colorB.toRgbString();
const ratio = chroma.contrast(a, b);
return round(ratio, 2);
},
},
methods: {
...mapMutations({
setColorA: types.SET_COLOR_A,
setColorB: types.SET_COLOR_B,
updateCurrentColor: types.UPDATE_CURRENT_COLOR,
}),
setCurrentColor(d) {
this.currentColor = d;
this.saveCurrentColor(d);
},
onMixerColorChange(d) {
this.setCurrentColor(d);
this[`setColor${this.selected}`](d);
},
onColorAChange(d) {
this.setColorA(d);
this.setCurrentColor(d);
},
onColorBChange(d) {
this.setColorB(d);
this.setCurrentColor(d);
},
onColorAClick() {
this.selected = 'A';
this.setCurrentColor(this.colorA.toRgb());
},
onColorBClick() {
this.selected = 'B';
this.setCurrentColor(this.colorB.toRgb());
},
},
components: {
MixerBar,
ColorInput,
ColorSwatch,
AccessibilitySample,
},
watch: {
gradient: {
handler: 'handleGradientChange',
deep: true,
},
},
created() {
this.saveCurrentColor = _.debounce(d => {
this.updateCurrentColor(d);
}, 500);
this.currentColor = { ...this.$store.state.currentColor };
},
};
</script>
<style lang="scss">
@import "../styles/core";
.accessibility-view {
display: flex;
max-width: 100%;
height: 100%;
z-index: 0;
}
.accessibility-view__content {
flex: 1 1 auto;
overflow: auto;
display: flex;
justify-content: center;
align-items: center;
}
.accessibility-view__left {
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
max-width: 480px;
.color-input {
margin-bottom: 0;
}
}
.accessibility-view__right {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 480px;
.ratio-sample {
width: 200px;
height: 200px;
}
}
.accessibility-view__sidebar {
flex: 0 0 480px;
overflow-x: hidden;
overflow-y: auto;
border-left: 1px solid black;
max-height: 100vh;
}
.accessibility-view__color {
min-width: 120px;
max-width: 240px;
margin: 2rem;
&.accessibility-view__color--selected {
.color-swatch {
box-shadow: inset 0 0 0 8px rgba(0, 0, 0, 0.4);
}
}
}
.accessibility-view__ratio {
font-size: 2em;
font-weight: bold;
font-family: Monaco;
margin-left: 20px;
width: 200px;
text-align: center;
margin: 2rem;
border: 1px solid black;
padding: 1rem;
}
</style>
This diff is collapsed.
......@@ -22,7 +22,8 @@
</div>
<div slot="palettes2">
<ui-color-palette
v-for="name in smartPalettes"
v-for="(name, index) in smartPalettes"
:key="index"
:name="name"
:value="value"
:colors="getSmartPaletteColors(name)"
......@@ -69,116 +70,115 @@
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import { mapState, mapMutations } from 'vuex';
import _ from '../utils';
import _ from '../utils';
import types from '../store/types';
import types from '../store/types';
import UiColor from '../ui/core/UiColor';
import UiColor from '../ui/core/UiColor';
export default {
name: 'mixer-view',
data() {
return {
value: {
r: 0,
g: 0,
b: 0,
a: 1,
},
spaces: ['rgb', 'hsv', 'lchab'],
segments: 16,
smartPalettes: ['analogous', 'monochromatic', 'triad', 'tetrad', 'splitcomplement'],
};
},
computed: {
...mapState({
palettes: state => _.utils.copy(state.palettes.items),
}),
color() {
return new UiColor(this.value);
export default {
name: 'mixer-view',
data() {
return {
value: {
r: 0,
g: 0,
b: 0,
a: 1,
},
spaces: ['rgb', 'hsv', 'lchab'],
segments: 16,
smartPalettes: ['analogous', 'monochromatic', 'triad', 'tetrad', 'splitcomplement'],
};
},
computed: {
...mapState({
palettes: state => _.utils.copy(state.palettes.items),
}),
color() {
return new UiColor(this.value);
},
methods: {
...mapMutations({
newPalette : types.palettes.NEW_PALETTE,
savePalette : types.palettes.SAVE_PALETTE,
deletePalette : types.palettes.DELETE_PALETTE,
}),
getPalette(id) {
return this.palettes.find(d => d.id === id);
},
getSmartPaletteColors(name) {
const colors = this.color[name]();
},
methods: {
...mapMutations({
newPalette: types.palettes.NEW_PALETTE,
savePalette: types.palettes.SAVE_PALETTE,
deletePalette: types.palettes.DELETE_PALETTE,
}),
getPalette(id) {
return this.palettes.find(d => d.id === id);
},
getSmartPaletteColors(name) {
const colors = this.color[name]();
return colors
.map(d => ({
value: d.toRgb(),
}));
},
onPaletteNew() {
this.newPalette({
name: '',
});
},
onPaletteDelete(d) {
this.deletePalette(d.id);
},
onPaletteChange(d) {
this.savePalette(d);
},
onPaletteAddColor(d) {
const palette = this.getPalette(d.id);
const value = d.value;
palette.colors.push({ value });
this.savePalette(palette);
},
onPaletteRemoveColor(d) {
const palette = this.getPalette(d.id);
const colorId = d.value.id;
const index = palette.colors.findIndex(d => d.id === colorId);
palette.colors.splice(index, 1);
this.savePalette(palette);
},
onColorChange(v) {
this.value = v;
},
onSegmentsChange(v) {
this.segments = v;
},
return colors.map(d => ({
value: d.toRgb(),
}));
},
};
onPaletteNew() {
this.newPalette({
name: '',
});
},
onPaletteDelete(d) {
this.deletePalette(d.id);
},
onPaletteChange(d) {
this.savePalette(d);
},
onPaletteAddColor(d) {
const palette = this.getPalette(d.id);
const value = d.value;
palette.colors.push({ value });
this.savePalette(palette);
},
onPaletteRemoveColor(d) {
const palette = this.getPalette(d.id);
const colorId = d.value.id;
const index = palette.colors.findIndex(d => d.id === colorId);
palette.colors.splice(index, 1);
this.savePalette(palette);
},
onColorChange(v) {
this.value = v;
},
onSegmentsChange(v) {
this.segments = v;
},
},
};
</script>
<style lang="scss">
@import '../styles/core';
@import "../styles/core";
.mixer-view {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.mixer-view {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.mixer-view__segments {
margin-top: 24px;
background-color: #F0F0F0;
box-shadow: 0 0 0 6px #F0F0F0;
.mixer-view__segments {
margin-top: 24px;
background-color: #f0f0f0;
box-shadow: 0 0 0 6px #f0f0f0;
.ui-stepper {
width: calc(100% + 4px);
.ui-stepper {
width: calc(100% + 4px);
> .ui-icon {
fill: #808080;
}
> .ui-icon {
fill: #808080;
}
.ui-stepper__input {
input {
border: none;
background-color: rgba(white, 0.7);
}
.ui-stepper__input {
input {
border: none;
background-color: rgba(white, 0.7);
}
}
}
}
</style>
......@@ -1297,6 +1297,12 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
color-blind@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/color-blind/-/color-blind-0.1.1.tgz#d6cf97b635fb6605c9dcc48efb58d5eb972a371e"
dependencies:
onecolor "^2.5.0"
color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.2"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147"
......@@ -4837,6 +4843,10 @@ once@1.x, once@^1.3.0:
dependencies:
wrappy "1"
onecolor@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-2.5.0.tgz#2256b651dc807c101f00aedbd49925c57a4431c1"
onetime@^1.0.0:
version "1.1.0"
resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
......