diff --git a/README.md b/README.md index 9d60fa8..77a0016 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,59 @@ settings: */ ``` +## `class-multi-toggle` + +`class-multi-toggle` creates a dropdown of predefined options for a CSS variable. The `id` of the setting will be used as the variable name. + +- When `allowEmpty` is `false`, a `default` option **must** be specified. +- When `allowEmpty` is `true`, the `default` attribute is optional, and may be set to `none`. + +```css +/* @settings + +name: Your Section Name Here +id: a-unique-id +settings: + - + id: theme-variant + title: Theme variant + description: Variations on a theme + type: class-multi-toggle + allowEmpty: false + default: my-class + options: + - my-class + - my-other-class + - and-yet-another + +*/ +``` + +Options may also be given a label: + +```css +/* @settings + +name: Your Section Name Here +id: a-unique-id +settings: + - + id: theme-variant + title: Theme variant + description: Variations on a theme + type: class-multi-toggle + allowEmpty: false + default: my-class + options: + - + label: My Class + value: my-class + - + label: My Other Class + value: my-other-class +*/ +``` + ## `variable-text` `variable-text` represents any text based CSS value. The `id` of the setting will be used as the variable name. `variable-text` settings require a `default` attribute. @@ -207,6 +260,30 @@ settings: */ ``` +Options can optionally be given a label: + +```css +/* @settings + +name: Your Section Name Here +id: a-unique-id +settings: + - + id: text + title: UI font + description: Font used for the user interface + type: variable-select + default: Roboto + options: + - + label: The best font + value: Roboto + - + label: The next best font + value: Helvetica Neue +*/ +``` + This will output the variable: ``` diff --git a/src/SettingsManager.ts b/src/SettingsManager.ts index 8f538a1..2538e0f 100644 --- a/src/SettingsManager.ts +++ b/src/SettingsManager.ts @@ -10,6 +10,7 @@ import { VariableText, ColorFormat, AltFormatList, + ClassMultiToggle, } from "./settingHandlers"; import chroma from "chroma-js"; import { @@ -269,6 +270,19 @@ export class CSSSettingsManager { if (this.getSetting(section, settingId)) { document.body.classList.add(setting.id); } + } else if (setting.type === "class-multi-toggle") { + const multiToggle = setting as ClassMultiToggle; + let value = this.getSetting(section, settingId) as string | undefined; + + if (value === undefined && !!multiToggle.default) { + value = multiToggle.default; + } else if (value === undefined) { + value = "none"; + } + + if (value !== "none") { + document.body.classList.add(value); + } } }); }); @@ -495,7 +509,9 @@ export class ImportModal extends Modal { new Setting(contentEl) .setName("Import style setting") - .setDesc("Import an entire or partial configuration. Warning: this may override existing settings"); + .setDesc( + "Import an entire or partial configuration. Warning: this may override existing settings" + ); new Setting(contentEl).then((setting) => { // Build an error message container diff --git a/src/settingHandlers.ts b/src/settingHandlers.ts index e38cd47..6e08392 100644 --- a/src/settingHandlers.ts +++ b/src/settingHandlers.ts @@ -149,6 +149,97 @@ export function createClassToggle(opts: { }); } +interface SelectOption { + label: string; + value: string; +} + +export interface ClassMultiToggle extends Meta { + default?: string; + allowEmpty: boolean; + options: Array; +} + +export function createClassMultiToggle(opts: { + sectionId: string; + config: ClassMultiToggle; + containerEl: HTMLElement; + settingsManager: CSSSettingsManager; +}): CleanupFunction { + const { sectionId, config, containerEl, settingsManager } = opts; + + let dropdownComponent: DropdownComponent; + + if (typeof config.default !== "string") { + return console.error(`Error: ${config.title} missing default value`); + } + + let prevValue = settingsManager.getSetting(sectionId, config.id) as + | string + | undefined; + + if (prevValue === undefined && !!config.default) { + prevValue = config.default; + } else if (prevValue === undefined) { + prevValue = "none"; + } + + new Setting(containerEl) + .setName(config.title) + .setDesc(createDescription(config.description, config.default)) + .addDropdown((dropdown) => { + if (config.allowEmpty) { + dropdown.addOption("none", ""); + } + + config.options.forEach((o) => { + if (typeof o === "string") { + dropdown.addOption(o, o); + } else { + dropdown.addOption(o.value, o.label); + } + }); + + dropdown.setValue(prevValue).onChange((value) => { + settingsManager.setSetting(sectionId, config.id, value); + + if (value !== "none") { + document.body.classList.add(value); + } + + if (prevValue) { + document.body.classList.remove(prevValue); + } + + prevValue = value; + }); + + dropdownComponent = dropdown; + }) + .addExtraButton((b) => { + b.setIcon("reset"); + b.onClick(() => { + const value = config.default || "none"; + + dropdownComponent.setValue(config.default || "none"); + + if (value !== "none") { + document.body.classList.add(value); + } + + if (prevValue) { + document.body.classList.remove(prevValue); + } + + settingsManager.clearSetting(sectionId, config.id); + }); + b.setTooltip(resetTooltip); + }) + .then((setting) => { + setting.settingEl.dataset.id = opts.config.id; + }); +} + export interface VariableText extends Meta { default: string; } @@ -312,7 +403,7 @@ export function createVariableNumberSlider(opts: { export interface VariableSelect extends Meta { default: string; - options: string[]; + options: Array; } export function createVariableSelect(opts: { @@ -334,7 +425,13 @@ export function createVariableSelect(opts: { .addDropdown((dropdown) => { const value = settingsManager.getSetting(sectionId, config.id); - config.options.forEach((o) => dropdown.addOption(o, o)); + config.options.forEach((o) => { + if (typeof o === "string") { + dropdown.addOption(o, o); + } else { + dropdown.addOption(o.value, o.label); + } + }); dropdown .setValue(value !== undefined ? (value as string) : config.default) @@ -796,6 +893,16 @@ export function createSettings(opts: { }); break; } + case "class-multi-toggle": { + pushId(setting.id); + createClassMultiToggle({ + sectionId, + config: setting as ClassMultiToggle, + containerEl: getTargetContainer(containerStack), + settingsManager, + }); + break; + } case "variable-text": { pushId(setting.id); createVariableText({ diff --git a/styles.css b/styles.css index 434279c..e44c3d0 100644 --- a/styles.css +++ b/styles.css @@ -1,36 +1,3 @@ -.pcr-app .pcr-swatches > button { - padding: 0; -} - -.pickr .pcr-button { - margin-right: 0; -} - -.themed-color-wrapper > div { - background: var(--background-primary); - padding: 10px; - display: flex; - align-items: center; - border-radius: 4px; -} - -.themed-color-wrapper > div + div { - margin-top: 6px; -} - -.themed-color-wrapper button { - display: block; -} - -.themed-color-wrapper .pickr-reset > button { - margin: 0 0 0 10px; - padding: 9px; - line-height: 1; -} - -.themed-color-wrapper .pickr-reset > button > svg { - display: block; -} .style-settings-heading { cursor: pointer; margin-bottom: 18px; @@ -197,4 +164,37 @@ .style-settings-import-error.active { display: block; } +.pcr-app .pcr-swatches > button { + padding: 0; +} + +.pickr .pcr-button { + margin-right: 0; +} + +.themed-color-wrapper > div { + background: var(--background-primary); + padding: 10px; + display: flex; + align-items: center; + border-radius: 4px; +} + +.themed-color-wrapper > div + div { + margin-top: 6px; +} + +.themed-color-wrapper button { + display: block; +} + +.themed-color-wrapper .pickr-reset > button { + margin: 0 0 0 10px; + padding: 9px; + line-height: 1; +} + +.themed-color-wrapper .pickr-reset > button > svg { + display: block; +} /*! Pickr 1.8.0 MIT | https://github.com/Simonwep/pickr */.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;border-radius:.15em;background:url('data:image/svg+xml;utf8, ') no-repeat 50%;background-size:0;transition:all .3s}.pickr .pcr-button:before{background:url('data:image/svg+xml;utf8, ');background-size:.5em;z-index:-1;z-index:auto}.pickr .pcr-button:after,.pickr .pcr-button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;border-radius:.15em}.pickr .pcr-button:after{transition:background .3s;background:currentColor}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear:before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pickr .pcr-button.disabled{cursor:not-allowed}.pcr-app *,.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pcr-app button.pcr-active,.pcr-app button:focus,.pcr-app input.pcr-active,.pcr-app input:focus,.pickr button.pcr-active,.pickr button:focus,.pickr input.pcr-active,.pickr input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pcr-app .pcr-palette,.pcr-app .pcr-slider,.pickr .pcr-palette,.pickr .pcr-slider{transition:box-shadow .3s}.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus,.pickr .pcr-palette:focus,.pickr .pcr-slider:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(0,0,0,.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity .3s,visibility 0s .3s;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;box-shadow:0 .15em 1.5em 0 rgba(0,0,0,.1),0 0 1em 0 rgba(0,0,0,.03);left:0;top:0}.pcr-app.visible{transition:opacity .3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports (display:grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit,1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all .15s;overflow:hidden;background:transparent;z-index:1}.pcr-app .pcr-swatches>button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:currentColor;border:1px solid rgba(0,0,0,.05);border-radius:.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -.2em}.pcr-app .pcr-interaction>*{margin:0 .2em}.pcr-app .pcr-interaction input{letter-spacing:.07em;font-size:.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all .15s;padding:.45em .5em;margin-top:.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(66,133,244,.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all .2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-save{width:auto;color:#fff}.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover,.pcr-app .pcr-interaction .pcr-save:hover{filter:brightness(.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{background:#f44250}.pcr-app .pcr-interaction .pcr-cancel:focus,.pcr-app .pcr-interaction .pcr-clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(244,66,80,.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity,.pcr-app .pcr-selection .pcr-color-palette{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active,.pcr-app .pcr-selection .pcr-color-palette:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme=nano]{width:14.25em;max-width:95vw}.pcr-app[data-theme=nano] .pcr-swatches{margin-top:.6em;padding:0 .6em}.pcr-app[data-theme=nano] .pcr-interaction{padding:0 .6em .6em}.pcr-app[data-theme=nano] .pcr-selection{display:grid;grid-gap:.6em;grid-template-columns:1fr 4fr;grid-template-rows:5fr auto auto;align-items:center;height:10.5em;width:100%;align-self:flex-start}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview{grid-area:2/1/4/1;height:100%;width:100%;display:flex;flex-direction:row;justify-content:center;margin-left:.6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-last-color{display:none}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color{position:relative;background:currentColor;width:2em;height:2em;border-radius:50em;overflow:hidden}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette{grid-area:1/1/2/3;width:100%;height:100%;z-index:1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette{border-radius:.15em;width:100%;height:100%}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser{grid-area:2/2/2/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{grid-area:3/2/3/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{height:.5em;margin:0 .6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-picker{top:50%;transform:translateY(-50%)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{flex-grow:1;border-radius:50em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(90deg,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(90deg,transparent,#000),url('data:image/svg+xml;utf8, ');background-size:100%,.25em} \ No newline at end of file