feat: contrast levels and custom color picker in appearance menu
Generate medium/high contrast schemes, add a custom swatch that builds a scheme in the browser via lazy-loaded scheme.js, allow Theme Builder exports as palettes. Drop the teal palette and spread the swatches across the menu width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b4eba7ae63
commit
6c271ffffe
@@ -10,12 +10,13 @@
|
||||
var PALETTES = [
|
||||
['purple', 'Purple', '#6750A4'],
|
||||
['blue', 'Blue', '#0061A4'],
|
||||
['teal', 'Teal', '#006A6A'],
|
||||
['green', 'Green', '#386A20'],
|
||||
['orange', 'Orange', '#8B5000']
|
||||
];
|
||||
var MODES = [['auto', 'Auto'], ['light', 'Light'], ['dark', 'Dark']];
|
||||
var CONTRASTS = [['standard', 'Standard'], ['medium', 'Medium'], ['high', 'High']];
|
||||
var KEY = 'tonalmine';
|
||||
var CUSTOM_SEL = ':root[data-palette="custom"]';
|
||||
var root = document.documentElement;
|
||||
|
||||
function load() {
|
||||
@@ -31,6 +32,33 @@
|
||||
else root.removeAttribute('data-palette');
|
||||
if (prefs.mode === 'light' || prefs.mode === 'dark') root.setAttribute('data-mode', prefs.mode);
|
||||
else root.removeAttribute('data-mode');
|
||||
if (prefs.contrast === 'medium' || prefs.contrast === 'high') root.setAttribute('data-contrast', prefs.contrast);
|
||||
else root.removeAttribute('data-contrast');
|
||||
// The custom palette's rules are generated once (see withScheme) and kept with the prefs.
|
||||
if (prefs.palette === 'custom' && prefs.css) {
|
||||
var style = document.getElementById('nm-custom-palette');
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
style.id = 'nm-custom-palette';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
style.textContent = prefs.css;
|
||||
}
|
||||
}
|
||||
|
||||
// The scheme generator (scheme.js, ~90 KB) is only loaded when someone picks a custom color.
|
||||
// Redmine serves theme assets under digested names only, so its URL comes from the CSS
|
||||
// variable --nm-scheme-js (Redmine rewrites url() in CSS to the digested path).
|
||||
var schemeScript;
|
||||
function withScheme(done) {
|
||||
if (window.tonalmineScheme) return done(window.tonalmineScheme);
|
||||
if (!schemeScript) {
|
||||
var url = getComputedStyle(root).getPropertyValue('--nm-scheme-js').match(/url\(["']?([^"')]+)/)[1];
|
||||
schemeScript = document.createElement('script');
|
||||
schemeScript.src = url;
|
||||
document.head.appendChild(schemeScript);
|
||||
}
|
||||
schemeScript.addEventListener('load', function () { done(window.tonalmineScheme); });
|
||||
}
|
||||
|
||||
var prefs = load();
|
||||
@@ -42,17 +70,25 @@
|
||||
|
||||
function pickerHtml() {
|
||||
var mode = prefs.mode || 'auto';
|
||||
var contrast = prefs.contrast || 'standard';
|
||||
var palette = prefs.palette || PALETTES[0][0];
|
||||
return '<div class="nm-appearance">' +
|
||||
'<div class="nm-appearance__title">Appearance</div>' +
|
||||
'<div class="nm-segmented" role="group" aria-label="Mode">' +
|
||||
MODES.map(function (m) { return button('data-nm-mode', m[0], m[1], m[0] === mode); }).join('') +
|
||||
'</div>' +
|
||||
'<div class="nm-appearance__title">Contrast</div>' +
|
||||
'<div class="nm-segmented" role="group" aria-label="Contrast">' +
|
||||
CONTRASTS.map(function (c) { return button('data-nm-contrast', c[0], c[1], c[0] === contrast); }).join('') +
|
||||
'</div>' +
|
||||
'<div class="nm-swatches" role="group" aria-label="Color">' +
|
||||
PALETTES.map(function (p) {
|
||||
return button('data-nm-palette', p[0], '', p[0] === palette,
|
||||
' class="nm-swatch" title="' + p[1] + '" aria-label="' + p[1] + '" style="--nm-swatch: ' + p[2] + '"');
|
||||
}).join('') +
|
||||
'<label class="nm-swatch nm-swatch--custom" data-nm-palette="custom" aria-pressed="' + (palette === 'custom') + '"' +
|
||||
(prefs.seed ? ' style="--nm-swatch: ' + prefs.seed + '"' : '') + ' title="Custom color">' +
|
||||
'<input type="color" data-nm-custom value="' + (prefs.seed || PALETTES[0][2].toLowerCase()) + '" aria-label="Custom color"></label>' +
|
||||
'</div></div>';
|
||||
}
|
||||
|
||||
@@ -60,21 +96,43 @@
|
||||
document.querySelectorAll('[data-nm-mode]').forEach(function (b) {
|
||||
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-mode') === (prefs.mode || 'auto')));
|
||||
});
|
||||
document.querySelectorAll('[data-nm-contrast]').forEach(function (b) {
|
||||
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-contrast') === (prefs.contrast || 'standard')));
|
||||
});
|
||||
document.querySelectorAll('[data-nm-palette]').forEach(function (b) {
|
||||
b.setAttribute('aria-pressed', String(b.getAttribute('data-nm-palette') === (prefs.palette || PALETTES[0][0])));
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var target = event.target.closest('[data-nm-mode], [data-nm-palette]');
|
||||
var target = event.target.closest('[data-nm-mode], [data-nm-contrast], [data-nm-palette]');
|
||||
if (!target) return;
|
||||
if (target.hasAttribute('data-nm-mode')) prefs.mode = target.getAttribute('data-nm-mode');
|
||||
else prefs.palette = target.getAttribute('data-nm-palette');
|
||||
else if (target.hasAttribute('data-nm-contrast')) prefs.contrast = target.getAttribute('data-nm-contrast');
|
||||
// The custom swatch opens the color dialog; it only selects the palette once a color was picked.
|
||||
else if (target.getAttribute('data-nm-palette') !== 'custom' || prefs.css) prefs.palette = target.getAttribute('data-nm-palette');
|
||||
save(prefs);
|
||||
apply(prefs);
|
||||
sync();
|
||||
});
|
||||
|
||||
document.addEventListener('input', function (event) {
|
||||
if (!event.target.matches('[data-nm-custom]')) return;
|
||||
var seed = event.target.value;
|
||||
withScheme(function (scheme) {
|
||||
prefs.palette = 'custom';
|
||||
prefs.seed = seed;
|
||||
prefs.css = scheme.paletteCss(CUSTOM_SEL, seed);
|
||||
save(prefs);
|
||||
apply(prefs);
|
||||
sync();
|
||||
document.querySelectorAll('[data-nm-custom]').forEach(function (input) {
|
||||
input.value = seed;
|
||||
input.parentNode.style.setProperty('--nm-swatch', seed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var menu = document.querySelector('#account .dropdown-content .dropdown-divider');
|
||||
if (menu) menu.insertAdjacentHTML('afterend', pickerHtml() + '<div class="dropdown-divider"></div>');
|
||||
|
||||
Reference in New Issue
Block a user