Skip to content

Commit 067b92d

Browse files
committed
Add support for color groups
1 parent a1606cf commit 067b92d

File tree

7 files changed

+290
-159
lines changed

7 files changed

+290
-159
lines changed

.materialcolorsapp.json.example

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,38 @@
1212
"format": "$alpha($color$HUE$VALUE, '$ALPHA');",
1313
"transform": "dXx"
1414
}
15-
]
15+
],
16+
"extraColors": {
17+
"brand": {
18+
"_selectorDark": "#1A73E8",
19+
"_selectorLight": "#1967D2",
20+
"_groups": [
21+
{
22+
"title": "Primary",
23+
"colors": [
24+
{ "name": "Default", "hex": "#1A73E8" },
25+
{ "name": "Dark", "hex": "#1967D2" }
26+
]
27+
}
28+
]
29+
},
30+
"navy": {
31+
"_selectorDark": "#6B829D",
32+
"_selectorLight": "#476282",
33+
"10": {"hex": "#F6F7F9"},
34+
"20": {"hex": "#E5EAF0"},
35+
"30": {"hex": "#D4DCE7"},
36+
"40": {"hex": "#C3CFDD"},
37+
"50": {"hex": "#B2C1D4"},
38+
"100": {"hex": "#8EA1B9"},
39+
"200": {"hex": "#6B829D"},
40+
"300": {"hex": "#476282"},
41+
"400": {"hex": "#385574"},
42+
"500": {"hex": "#2A4865"},
43+
"600": {"hex": "#1B3A57"},
44+
"700": {"hex": "#0C2D48"},
45+
"800": {"hex": "#051E34"},
46+
"900": {"hex": "#031525"}
47+
}
48+
}
1649
}

app/app.js

Lines changed: 105 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,38 @@ class MaterialColors {
4343

4444
this.COLORS = require('./colors.js');
4545
if (this._config.extraColors) {
46-
this.COLORS[Object.keys(this.COLORS)[0]].startGroup = true;
46+
this.COLORS[Object.keys(this.COLORS)[0]]._startGroup = true;
4747
this.COLORS = {
4848
...this._config.extraColors,
4949
...this.COLORS,
5050
};
5151
};
5252

53-
this._allMaterialValues = [];
54-
Object.keys(this.COLORS).map(hueName =>
55-
Object.keys(this.COLORS[hueName]).map(valueName =>
56-
this._allMaterialValues.push(
57-
Object.assign({ hueName, valueName }, this.COLORS[hueName][valueName]))));
53+
this._searchableValues = [];
54+
Object.keys(this.COLORS).forEach(hueName => {
55+
let colorObj = this.COLORS[hueName];
56+
57+
(colorObj._groups || []).forEach(group => {
58+
(group.colors || []).forEach(color => {
59+
this._searchableValues.push({
60+
hueName,
61+
groupName: group.title || null,
62+
valueName: color.name,
63+
...color
64+
});
65+
});
66+
});
67+
68+
Object.keys(colorObj)
69+
.filter(k => !k.startsWith('_'))
70+
.forEach(valueName => {
71+
this._searchableValues.push({
72+
hueName,
73+
valueName,
74+
...this.COLORS[hueName][valueName]
75+
});
76+
});
77+
});
5878

5979
this.CLASS_NAMES = {
6080
closeButton: 'close-button',
@@ -84,6 +104,8 @@ class MaterialColors {
84104
separator: 'separator',
85105
sidebar: 'sidebar',
86106
updateBanner: 'update-banner',
107+
valueGroup: 'value-group',
108+
valueGroupHeading: 'value-group-heading',
87109
valueHeading: 'value-heading',
88110
valueList: 'value-list',
89111
notFoundIcon: 'not-found-icon',
@@ -166,7 +188,7 @@ class MaterialColors {
166188
firstHueName = hueName;
167189
}
168190

169-
if (color.startGroup) {
191+
if (color._startGroup) {
170192
$('<div>')
171193
.addClass(`${this.CLASS_NAMES.separator}`)
172194
.appendTo(this.$sidebar);
@@ -177,7 +199,9 @@ class MaterialColors {
177199
.click(() => this._selectHue(hueName))
178200
.appendTo(this.$sidebar);
179201

180-
let keyColor = color[this.isDarkMode ? '300' : '500'].hex;
202+
let keyColor = this.isDarkMode
203+
? color._selectorDark || color['300'].hex
204+
: color._selectorLight || color['500'].hex;
181205

182206
let $hueIcon = $('<div>')
183207
.addClass(this.CLASS_NAMES.hueIcon)
@@ -270,12 +294,41 @@ class MaterialColors {
270294
// for each value in the hue
271295
let color = this.COLORS[hueName];
272296
for (let valueName in this.COLORS[hueName]) {
297+
if (valueName.startsWith('_')) {
298+
continue;
299+
}
300+
273301
color[valueName].valueName = valueName;
274302
color[valueName].hueName = hueName;
275303
this._buildValueTile(color[valueName])
276304
.appendTo(this.$valueList);
277305
}
278306

307+
// build grouped items
308+
for (let group of color._groups || []) {
309+
let $valueGroup = $('<div>')
310+
.addClass(this.CLASS_NAMES.valueGroup)
311+
.appendTo(this.$valueList);
312+
313+
if (group.title) {
314+
$('<div>')
315+
.addClass(this.CLASS_NAMES.valueGroupHeading)
316+
.text(group.title)
317+
.appendTo($valueGroup);
318+
}
319+
320+
// for each value in the hue
321+
for (let color of group.colors) {
322+
color.valueName = color.name;
323+
if (group.title) {
324+
color.groupName = group.title;
325+
}
326+
color.hueName = hueName;
327+
this._buildValueTile(color)
328+
.appendTo($valueGroup);
329+
}
330+
}
331+
279332
// TODO(abhiomkar): use this dom cache instead of re-rendering.
280333
this.$_cache[hueName] = this.$valueList.children();
281334
}
@@ -293,13 +346,13 @@ class MaterialColors {
293346
// search input is valid.
294347
let hex = inputColor.toHexString();
295348
let alpha = inputColor.getAlpha();
296-
let materialValues = this._getMaterialValuesByHex(hex);
349+
let searchResults = this._getSearchableValuesByHex(hex);
297350
let $colorTile;
298351
this.$searchResults.empty();
299352

300-
if (materialValues.length) {
353+
if (searchResults.length) {
301354
// material color
302-
materialValues.forEach(value => {
355+
searchResults.forEach(value => {
303356
// update material color with alpha.
304357
if (alpha) {
305358
value = Object.assign({alpha}, value);
@@ -309,16 +362,16 @@ class MaterialColors {
309362
});
310363
} else {
311364
// Non-material color.
312-
this._buildValueTile({ hex, alpha, white: inputColor.isDark() }, true)
365+
this._buildValueTile({ hex, alpha }, true)
313366
.appendTo(this.$searchResults);
314367

315368
$('<div>')
316369
.addClass(this.CLASS_NAMES.matchingMaterialLabel)
317-
.text('Nearest Material colors')
370+
.text('Similar colors')
318371
.appendTo(this.$searchResults);
319372

320373
// suggest a closest material color
321-
this._getCloseMaterialValues(inputColor)
374+
this._getCloseSearchableValues(inputColor)
322375
.forEach(val => this._buildValueTile(val, true).appendTo(this.$searchResults));
323376
}
324377
} else {
@@ -341,7 +394,7 @@ class MaterialColors {
341394
}
342395
}
343396

344-
_showValueContextMenu(hexValue, hueName, valueName, alpha) {
397+
_showValueContextMenu(hexValue, hueName, groupName, valueName, alpha) {
345398
let withHash = hexValue;
346399
let noHash = hexValue.replace(/#/g, '');
347400

@@ -364,11 +417,12 @@ class MaterialColors {
364417
let valueFormats = [];
365418
if (this._config.copyFormats && this._config.copyFormats.length) {
366419
this._config.copyFormats.forEach(format => {
367-
valueFormats.push(this._renderCustomColorFormatString(format, {hueName, valueName, alpha}));
420+
valueFormats.push(this._renderCustomColorFormatString(format,
421+
{hueName, groupName, valueName, alpha}));
368422
});
369423
} else {
370424
valueFormats.push(this._renderCustomColorFormatString(
371-
DEFAULT_VALUE_COPY_FORMAT, {hueName, valueName, alpha}));
425+
DEFAULT_VALUE_COPY_FORMAT, {hueName, groupName, valueName, alpha}));
372426
}
373427

374428
let formatToMenuItemTemplate_ = format => ({
@@ -389,17 +443,18 @@ class MaterialColors {
389443
_buildValueTile(value, largeTile) {
390444
let tileBackground;
391445
let isWhite;
446+
let tc = tinycolor(value.hex);
392447

393448
if (value.alpha) {
394-
tileBackground = tinycolor(value.hex).setAlpha(value.alpha).toString();
449+
tileBackground = tc.setAlpha(value.alpha).toString();
395450
} else {
396451
tileBackground = value.hex;
397452
}
398453

399454
if (value.alpha && value.alpha < 0.5) {
400455
isWhite = false;
401456
} else {
402-
isWhite = value.white;
457+
isWhite = tc.isDark();
403458
}
404459

405460
let $colorTile = $('<div>')
@@ -409,7 +464,8 @@ class MaterialColors {
409464
.css('background-color', tileBackground)
410465
.contextmenu(event => {
411466
event.preventDefault();
412-
this._showValueContextMenu(value.hex, value.hueName, value.valueName, value.alpha);
467+
this._showValueContextMenu(
468+
value.hex, value.hueName, value.groupName, value.valueName, value.alpha);
413469
});
414470

415471
let $hex = $('<div>')
@@ -424,17 +480,18 @@ class MaterialColors {
424480
$colorTile.on('refresh-tile', (event, opts) =>
425481
$hex.text(value.hex.toUpperCase().substring((opts && opts.hideHash) ? 1 : 0)));
426482

427-
if (value.valueName) {
483+
if (value.name || value.valueName) {
428484
$('<div>')
429485
.addClass(this.CLASS_NAMES.colorTileValueName)
430-
.text(value.valueName.toUpperCase())
486+
.text(value.name || value.valueName.toUpperCase())
431487
.click(() => {
432488
let valueCopyFormat = (this._config.copyFormats && this._config.copyFormats.length)
433489
? this._config.copyFormats[0]
434490
: DEFAULT_VALUE_COPY_FORMAT;
435491
let copyText = this._renderCustomColorFormatString(valueCopyFormat, {
436492
hueName: value.hueName,
437-
valueName: value.valueName,
493+
groupName: value.groupName || null,
494+
valueName: value.name || value.valueName,
438495
alpha: value.alpha,
439496
});
440497
electron.clipboard.writeText(copyText);
@@ -446,7 +503,8 @@ class MaterialColors {
446503
if (value.hueName && largeTile) {
447504
$('<span>')
448505
.addClass(this.CLASS_NAMES.colorTileHueName)
449-
.text(this._getDisplayLabelForHue(value.hueName))
506+
.text(this._getDisplayLabelForHue(value.hueName)
507+
+ (value.groupName ? ` – ${value.groupName}` : ''))
450508
.click(() => this._selectHue(value.hueName))
451509
.appendTo($colorTile);
452510
}
@@ -479,13 +537,13 @@ class MaterialColors {
479537
Math.pow(colorA._b - colorB._b, 2)); // blue
480538
}
481539

482-
_getMaterialValuesByHex(hex) {
483-
return this._allMaterialValues
540+
_getSearchableValuesByHex(hex) {
541+
return this._searchableValues
484542
.filter(value => value.hex.toLowerCase() === hex.toLowerCase());
485543
}
486544

487-
_getCloseMaterialValues(inputColor) {
488-
return this._allMaterialValues
545+
_getCloseSearchableValues(inputColor) {
546+
return this._searchableValues
489547
.map(value => ({ value, difference: this._getColorDifference(inputColor, value.hex) }))
490548
.sort((a, b) => (a.difference - b.difference))
491549
.slice(0, 3)
@@ -525,6 +583,10 @@ class MaterialColors {
525583
data.hueName = data.hueName || '';
526584
data.valueName = data.valueName || '';
527585

586+
if (data.groupName) {
587+
data.valueName = data.groupName + '-' + data.valueName;
588+
}
589+
528590
if (data.alpha) {
529591
data.alpha = (data.alpha * 100).toFixed(0);
530592
} else {
@@ -544,29 +606,30 @@ class MaterialColors {
544606
}
545607

546608
// text transform, lower, upper or capitalize
609+
let transformers = [];
547610
if (textTransform === 'x') {
548-
data.hueName = data.hueName.toLowerCase();
549-
data.valueName = data.valueName.toLowerCase();
611+
transformers.push(s => s.toLowerCase());
550612
} else if (textTransform === 'X') {
551-
data.hueName = data.hueName.toUpperCase();
552-
data.valueName = data.valueName.toUpperCase();
613+
transformers.push(s => s.toUpperCase());
553614
} else if (textTransform === 'Xx') {
554-
// capitalize text
555-
data.hueName = this._sentenceCase(data.hueName);
556-
data.valueName = this._sentenceCase(data.valueName);
615+
transformers.push(s => this._sentenceCase(s));
557616
}
558617

559618
// Replacer
560619
// d - delete spaces between the hue name (eg: LightBlue)
561620
// * - replace spaces between hue name with any character (eg: LIGHT_BLUE)
562621
// if no replacer found add a space between hue name if any (eg: Light Blue)
563-
if (replacer === 'd') {
564-
data.hueName = data.hueName.replace('-', '');
565-
} else if (replacer) {
566-
data.hueName = data.hueName.replace('-', replacer);
567-
} else {
568-
data.hueName = data.hueName.replace('-', ' ');
569-
}
622+
replacer = replacer
623+
? (replacer === 'd'
624+
? ''
625+
: replacer)
626+
: ' ';
627+
628+
transformers.push(s => s.replace(/[- ]/g, replacer));
629+
630+
let applyAllTransformers = src => transformers.reduce((s, t) => t(s), src);
631+
data.hueName = applyAllTransformers(data.hueName);
632+
data.valueName = applyAllTransformers(data.valueName);
570633
}
571634

572635
string = string.replace(/\$HUE/g, data.hueName)

0 commit comments

Comments
 (0)