Merge pull request #1656 from umbraco/feature/icons-extension

Feature: icons extension
This commit is contained in:
Niels Lyngsø
2024-04-23 08:50:30 +02:00
committed by GitHub
23 changed files with 8463 additions and 137 deletions

View File

@@ -20,6 +20,7 @@ import { UmbIconRegistry } from '../src/packages/core/icon-registry/icon.registr
import { UmbLitElement } from '../src/packages/core/lit-element';
import { umbLocalizationRegistry } from '../src/packages/core/localization';
import customElementManifests from '../dist-cms/custom-elements.json';
import icons from '../src/packages/core/icon-registry/icons/icons';
import '../src/libs/context-api/provide/context-provider.element';
import '../src/packages/core/components';
@@ -36,6 +37,7 @@ class UmbStoryBookElement extends UmbLitElement {
constructor() {
super();
this._umbIconRegistry.setIcons(icons);
this._umbIconRegistry.attach(this);
this._registerExtensions(documentManifests);
new UmbModalManagerContext(this);

View File

@@ -18,11 +18,10 @@ const run = async () => {
var icons = await collectDictionaryIcons();
icons = await collectDiskIcons(icons);
writeIconsToDisk(icons);
generateJSON(icons);
generateJS(icons);
};
const collectDictionaryIcons = async () => {
const rawData = readFileSync(iconMapJson);
const fileRaw = rawData.toString();
const fileJSON = JSON.parse(fileRaw);
@@ -32,11 +31,11 @@ const collectDictionaryIcons = async () => {
// Lucide:
fileJSON.lucide.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = lucideSvgDirectory + "/" + iconDef.file;
const path = lucideSvgDirectory + '/' + iconDef.file;
try {
const rawData = readFileSync(path);
// For Lucide icons specially we adjust the icons a bit for them to work in our case:
// For Lucide icons specially we adjust the icons a bit for them to work in our case: [NL]
let svg = rawData.toString().replace(' width="24"\n', '');
svg = svg.replace(' height="24"\n', '');
svg = svg.replace('stroke-width="2"', 'stroke-width="1.75"');
@@ -60,11 +59,11 @@ const collectDictionaryIcons = async () => {
// SimpleIcons:
fileJSON.simpleIcons.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = simpleIconsSvgDirectory + "/" + iconDef.file;
const path = simpleIconsSvgDirectory + '/' + iconDef.file;
try {
const rawData = readFileSync(path);
let svg = rawData.toString()
let svg = rawData.toString();
const iconFileName = iconDef.name;
// SimpleIcons need to use fill="currentColor"
@@ -91,11 +90,11 @@ const collectDictionaryIcons = async () => {
// Umbraco:
fileJSON.umbraco.forEach((iconDef) => {
if (iconDef.file && iconDef.name) {
const path = umbracoSvgDirectory + "/" + iconDef.file;
const path = umbracoSvgDirectory + '/' + iconDef.file;
try {
const rawData = readFileSync(path);
const svg = rawData.toString()
const svg = rawData.toString();
const iconFileName = iconDef.name;
const icon = {
@@ -136,8 +135,7 @@ const collectDiskIcons = async (icons) => {
const iconName = iconFileName;
// Only append not already defined icons:
if (!icons.find(x => x.name === iconName)) {
if (!icons.find((x) => x.name === iconName)) {
const icon = {
name: iconName,
legacy: true,
@@ -169,20 +167,20 @@ const writeIconsToDisk = (icons) => {
});
};
const generateJSON = (icons) => {
const JSONPath = `${iconsOutputDirectory}/icons.json`;
const generateJS = (icons) => {
const JSPath = `${iconsOutputDirectory}/icons.ts`;
const iconDescriptors = icons.map((icon) => {
return {
name: icon.name,
legacy: icon.legacy,
path: `./icons/${icon.fileName}.js`,
};
return `{
name: "${icon.name}",
${icon.legacy ? 'legacy: true,' : ''}
path: "./icons/${icon.fileName}.js",
}`.replace(/\t/g, ''); // Regex removes white space [NL]
});
const content = `${JSON.stringify(iconDescriptors)}`;
const content = `export default [${iconDescriptors.join(',')}];`;
writeFileWithDir(JSONPath, content, (err) => {
writeFileWithDir(JSPath, content, (err) => {
if (err) {
// eslint-disable-next-line no-undef
console.log(err);

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { Guard, UmbRoute } from '@umbraco-cms/backoffice/router';
import { pathWithoutBasePath } from '@umbraco-cms/backoffice/router';
@@ -85,7 +84,6 @@ export class UmbAppElement extends UmbLitElement {
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
new UmbAppEntryPointExtensionInitializer(this, umbExtensionsRegistry);
new UmbIconRegistry().attach(this);
new UUIIconRegistryEssential().attach(this);
new UmbContextDebugController(this);

View File

@@ -10,33 +10,31 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import './components/index.js';
// TODO: temp solution to load core packages
const CORE_PACKAGES = [
import('../../packages/audit-log/umbraco-package.js'),
import('../../packages/block/umbraco-package.js'),
import('../../packages/data-type/umbraco-package.js'),
import('../../packages/dictionary/umbraco-package.js'),
import('../../packages/umbraco-news/umbraco-package.js'),
import('../../packages/documents/umbraco-package.js'),
import('../../packages/dynamic-root/umbraco-package.js'),
import('../../packages/health-check/umbraco-package.js'),
import('../../packages/language/umbraco-package.js'),
import('../../packages/log-viewer/umbraco-package.js'),
import('../../packages/markdown-editor/umbraco-package.js'),
import('../../packages/data-type/umbraco-package.js'),
import('../../packages/media/umbraco-package.js'),
import('../../packages/members/umbraco-package.js'),
import('../../packages/models-builder/umbraco-package.js'),
//import('../../packages/object-type/umbraco-package.js'),// This had nothing to register.
import('../../packages/packages/umbraco-package.js'),
import('../../packages/relations/umbraco-package.js'),
import('../../packages/search/umbraco-package.js'),
import('../../packages/settings/umbraco-package.js'),
import('../../packages/language/umbraco-package.js'),
import('../../packages/static-file/umbraco-package.js'),
import('../../packages/dynamic-root/umbraco-package.js'),
import('../../packages/block/umbraco-package.js'),
import('../../packages/tags/umbraco-package.js'),
import('../../packages/templating/umbraco-package.js'),
import('../../packages/tiny-mce/umbraco-package.js'),
import('../../packages/umbraco-news/umbraco-package.js'),
import('../../packages/markdown-editor/umbraco-package.js'),
import('../../packages/templating/umbraco-package.js'),
import('../../packages/dictionary/umbraco-package.js'),
import('../../packages/user/umbraco-package.js'),
import('../../packages/health-check/umbraco-package.js'),
import('../../packages/audit-log/umbraco-package.js'),
import('../../packages/webhook/umbraco-package.js'),
import('../../packages/relations/umbraco-package.js'),
import('../../packages/models-builder/umbraco-package.js'),
import('../../packages/log-viewer/umbraco-package.js'),
import('../../packages/packages/umbraco-package.js'),
];
@customElement('umb-backoffice')
@@ -55,13 +53,13 @@ export class UmbBackofficeElement extends UmbLitElement {
new UmbBackofficeEntryPointExtensionInitializer(this, umbExtensionsRegistry);
new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry);
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
// So far local packages are this simple to registerer, so no need for a manager to do that:
CORE_PACKAGES.forEach(async (packageImport) => {
const packageModule = await packageImport;
umbExtensionsRegistry.registerMany(packageModule.extensions);
});
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
}
render() {

View File

@@ -81,7 +81,7 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement {
#tabs {
height: 60px;
flex-basis: 100%;
font-size: 16px;
font-size: 16px; /* specific for the header */
--uui-tab-text: var(--uui-color-header-contrast);
--uui-tab-text-hover: var(--uui-color-header-contrast-emphasis);
--uui-tab-text-active: var(--uui-color-header-contrast-emphasis);

View File

@@ -1,6 +1,6 @@
import type { UmbBackofficeContext } from '../backoffice.context.js';
import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbSectionContext, UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section';
import type { UmbRoute, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router';
import type { ManifestSection, UmbSectionElement } from '@umbraco-cms/backoffice/extension-registry';
@@ -99,17 +99,18 @@ export class UmbBackofficeMainElement extends UmbLitElement {
render() {
return this._routes.length > 0
? html`<umb-router-slot .routes=${this._routes} @change=${this._onRouteChange}></umb-router-slot>`
: '';
: nothing;
}
static styles = [
css`
:host {
background-color: var(--uui-color-background);
display: block;
background-color: var(--uui-color-background);
width: 100%;
height: calc(
100% - 60px
); // 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive.
); /* 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive. */
}
`,
];

View File

@@ -0,0 +1,6 @@
import type { UmbIconDictionary } from '@umbraco-cms/backoffice/icon';
import type { ManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestIcons extends ManifestPlainJs<{ default: UmbIconDictionary }> {
type: 'icons';
}

View File

@@ -27,6 +27,8 @@ import type { ManifestExternalLoginProvider } from './external-login-provider.mo
import type { ManifestGlobalContext } from './global-context.model.js';
import type { ManifestHeaderApp, ManifestHeaderAppButtonKind } from './header-app.model.js';
import type { ManifestHealthCheck } from './health-check.model.js';
import type { ManifestIcons } from './icons.model.js';
import type { ManifestLocalization } from './localization.model.js';
import type { ManifestMenu } from './menu.model.js';
import type { ManifestMenuItem, ManifestMenuItemTreeKind } from './menu-item.model.js';
import type { ManifestModal } from './modal.model.js';
@@ -40,7 +42,6 @@ import type { ManifestSectionView } from './section-view.model.js';
import type { ManifestStore, ManifestTreeStore, ManifestItemStore } from './store.model.js';
import type { ManifestTheme } from './theme.model.js';
import type { ManifestTinyMcePlugin } from './tinymce-plugin.model.js';
import type { ManifestLocalization } from './localization.model.js';
import type { ManifestTree } from './tree.model.js';
import type { ManifestTreeItem } from './tree-item.model.js';
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
@@ -66,6 +67,7 @@ import type { ManifestBackofficeEntryPoint } from './backoffice-entry-point.mode
import type { ManifestEntryPoint } from './entry-point.model.js';
import type { ManifestBase, ManifestBundle, ManifestCondition } from '@umbraco-cms/backoffice/extension-api';
export type * from './app-entry-point.model.js';
export type * from './auth-provider.model.js';
export type * from './backoffice-entry-point.model.js';
export type * from './block-editor-custom-view.model.js';
@@ -84,6 +86,7 @@ export type * from './external-login-provider.model.js';
export type * from './global-context.model.js';
export type * from './header-app.model.js';
export type * from './health-check.model.js';
export type * from './icons.model.js';
export type * from './localization.model.js';
export type * from './menu-item.model.js';
export type * from './menu.model.js';
@@ -109,7 +112,6 @@ export type * from './workspace-context.model.js';
export type * from './workspace-footer-app.model.js';
export type * from './workspace-view.model.js';
export type * from './workspace.model.js';
export type * from './app-entry-point.model.js';
export type ManifestEntityActions =
| ManifestEntityAction
@@ -163,6 +165,7 @@ export type ManifestTypes =
| ManifestHeaderApp
| ManifestHeaderAppButtonKind
| ManifestHealthCheck
| ManifestIcons
| ManifestItemStore
| ManifestMenu
| ManifestMenuItem

View File

@@ -0,0 +1,4 @@
import type { UmbIconRegistryContext } from './icon-registry.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_ICON_REGISTRY_CONTEXT = new UmbContextToken<UmbIconRegistryContext>('UmbIconRegistryContext');

View File

@@ -0,0 +1,49 @@
import { UmbIconRegistry } from './icon.registry.js';
import type { UmbIconDefinition } from './types.js';
import { UMB_ICON_REGISTRY_CONTEXT } from './icon-registry.context-token.js';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { loadManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { type ManifestIcons, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
export class UmbIconRegistryContext extends UmbContextBase<UmbIconRegistryContext> {
#registry: UmbIconRegistry;
#manifestMap = new Map();
#icons = new UmbArrayState<UmbIconDefinition>([], (x) => x.name);
readonly icons = this.#icons.asObservable();
readonly approvedIcons = this.#icons.asObservablePart((icons) => icons.filter((x) => x.legacy !== true));
constructor(host: UmbControllerHost) {
super(host, UMB_ICON_REGISTRY_CONTEXT);
this.#registry = new UmbIconRegistry();
this.#registry.attach(host.getHostElement());
this.observe(this.icons, (icons) => {
//if (icons.length > 0) {
this.#registry.setIcons(icons);
//}
});
this.observe(umbExtensionsRegistry.byType('icons'), (manifests) => {
manifests.forEach((manifest) => {
if (this.#manifestMap.has(manifest.alias)) return;
this.#manifestMap.set(manifest.alias, manifest);
// TODO: Should we unInit a entry point if is removed?
this.instantiateEntryPoint(manifest);
});
});
}
async instantiateEntryPoint(manifest: ManifestIcons) {
if (manifest.js) {
const js = await loadManifestPlainJs<{ default?: any }>(manifest.js);
if (!js || !js.default || !Array.isArray(js.default)) {
throw new Error('Icon manifest JS-file must export an array of icons as the default export.');
}
this.#icons.append(js.default);
}
}
}
export { UmbIconRegistryContext as api };

View File

@@ -1,5 +1,4 @@
import icons from './icons/icons.json' assert { type: 'json' };
import { UUIIconRegistry } from '@umbraco-cms/backoffice/external/uui';
import { type UUIIconHost, UUIIconRegistry } from '@umbraco-cms/backoffice/external/uui';
interface UmbIconDescriptor {
name: string;
@@ -13,26 +12,66 @@ interface UmbIconDescriptor {
* @description - Icon Registry. Provides icons from the icon manifest. Icons are loaded on demand. All icons are prefixed with 'icon-'
*/
export class UmbIconRegistry extends UUIIconRegistry {
#initResolve?: () => void;
#init: Promise<void> = new Promise((resolve) => {
this.#initResolve = resolve;
});
#icons: UmbIconDescriptor[] = [];
#unhandledProviders: Map<string, UUIIconHost> = new Map();
setIcons(icons: UmbIconDescriptor[]) {
const oldIcons = this.#icons;
this.#icons = icons;
if (this.#initResolve) {
this.#initResolve();
this.#initResolve = undefined;
}
// Go figure out which of the icons are new.
const newIcons = this.#icons.filter((i) => !oldIcons.find((o) => o.name === i.name));
newIcons.forEach((icon) => {
// Do we already have a request for this one, then lets initiate the load for those:
const unhandled = this.#unhandledProviders.get(icon.name);
if (unhandled) {
this.#loadIcon(icon.name, unhandled).then(() => {
this.#unhandledProviders.delete(icon.name);
});
}
});
}
appendIcons(icons: UmbIconDescriptor[]) {
this.#icons = [...this.#icons, ...icons];
}
/**
* @param {string} iconName
* @return {*} {boolean}
* @memberof UmbIconStore
*/
acceptIcon(iconName: string): boolean {
const iconManifest = icons.find((i: UmbIconDescriptor) => i.name === iconName);
if (!iconManifest) return false;
const iconProvider = this.provideIcon(iconName);
this.#loadIcon(iconName, iconProvider);
return true;
}
async #loadIcon(iconName: string, iconProvider: UUIIconHost): Promise<boolean> {
await this.#init;
const iconManifest = this.#icons.find((i: UmbIconDescriptor) => i.name === iconName);
// Icon not found, so lets add it to a list of unhandled requests.
if (!iconManifest) {
this.#unhandledProviders.set(iconName, iconProvider);
return false;
}
const icon = this.provideIcon(iconName);
const iconPath = iconManifest.path;
import(/* @vite-ignore */ iconPath)
.then((iconModule) => {
icon.svg = iconModule.default;
iconProvider.svg = iconModule.default;
})
.catch((err) => {
console.error(`Failed to load icon ${iconName} on path ${iconPath}`, err.message);
});
return true;
}
}

View File

@@ -1,5 +1,5 @@
import type { Meta, Story } from '@storybook/web-components';
import icons from './icons/icons.json';
import icons from './icons/icons.js';
import { html, repeat } from '@umbraco-cms/backoffice/external/lit';
export default {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,4 @@
export * from './icon-registry.context-token.js';
export * from './icon-registry.context.js';
export * from './icon.registry.js';
export * from './types.js';

View File

@@ -0,0 +1,14 @@
export const manifests = [
{
type: 'icons',
alias: 'Umb.Icons.Backoffice',
name: 'Backoffice Icons',
js: () => import('./icons/icons.js'),
},
{
type: 'globalContext',
alias: 'Umb.GlobalContext.Icons',
name: 'Icons Context',
api: () => import('./icon-registry.context.js'),
},
];

View File

@@ -0,0 +1,7 @@
export interface UmbIconDefinition {
name: string;
path: string;
legacy?: boolean;
}
export type UmbIconDictionary = Array<UmbIconDefinition>;

View File

@@ -6,6 +6,7 @@ import { manifests as cultureManifests } from './culture/manifests.js';
import { manifests as debugManifests } from './debug/manifests.js';
import { manifests as entityActionManifests } from './entity-action/manifests.js';
import { manifests as extensionManifests } from './extension-registry/manifests.js';
import { manifests as iconRegistryManifests } from './icon-registry/manifests.js';
import { manifests as localizationManifests } from './localization/manifests.js';
import { manifests as modalManifests } from './modal/common/manifests.js';
import { manifests as propertyActionManifests } from './property-action/manifests.js';
@@ -23,6 +24,7 @@ import type { ManifestTypes, UmbBackofficeManifestKind } from './extension-regis
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...authManifests,
...extensionManifests,
...iconRegistryManifests,
...cultureManifests,
...localizationManifests,
...themeManifests,

View File

@@ -1,81 +1,97 @@
import icons from '../../../icon-registry/icons/icons.json' assert { type: 'json' };
import type { UUIColorSwatchesEvent } from '@umbraco-cms/backoffice/external/uui';
import type { UUIColorSwatchesEvent, UUIIconElement } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state, repeat, query, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbIconPickerModalData, UmbIconPickerModalValue } from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { extractUmbColorVariable, umbracoColors } from '@umbraco-cms/backoffice/resources';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { UMB_ICON_REGISTRY_CONTEXT, type UmbIconDefinition } from '@umbraco-cms/backoffice/icon';
// TODO: Make use of UmbPickerLayoutBase
// TODO: to prevent element extension we need to move the Picker logic into a separate class we can reuse across all pickers
@customElement('umb-icon-picker-modal')
export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPickerModalData, UmbIconPickerModalValue> {
private _iconList = icons.filter((icon) => !icon.legacy);
#icons?: Array<UmbIconDefinition>;
@query('#search')
private _searchInput?: HTMLInputElement;
@state()
private _iconListFiltered: Array<(typeof icons)[0]> = [];
private _iconsFiltered?: Array<UmbIconDefinition>;
@state()
private _colorList = umbracoColors;
private _colorList = umbracoColors.filter((color) => !color.legacy);
@state()
private _modalValue?: UmbIconPickerModalValue;
private _currentIcon?: string;
@state()
private _currentAlias = 'text';
private _currentColor = 'text';
#changeIcon(e: { target: HTMLInputElement; type: string; key: unknown }) {
if (e.type == 'click' || (e.type == 'keyup' && e.key == 'Enter')) {
this.modalContext?.updateValue({ icon: e.target.id });
}
constructor() {
super();
this.consumeContext(UMB_ICON_REGISTRY_CONTEXT, (context) => {
this.observe(context.approvedIcons, (icons) => {
this.#icons = icons;
this.#filterIcons();
});
});
}
#filterIcons(e: { target: HTMLInputElement }) {
if (e.target.value) {
this._iconListFiltered = this._iconList.filter((icon) =>
icon.name.toLowerCase().includes(e.target.value.toLowerCase()),
);
#filterIcons() {
if (!this.#icons) return;
const value = this._searchInput?.value;
if (value) {
this._iconsFiltered = this.#icons.filter((icon) => icon.name.toLowerCase().includes(value.toLowerCase()));
} else {
this._iconListFiltered = this._iconList;
this._iconsFiltered = this.#icons;
}
}
#onColorChange(e: UUIColorSwatchesEvent) {
this.modalContext?.updateValue({ color: e.target.value });
this._currentAlias = e.target.value;
}
connectedCallback() {
super.connectedCallback();
this._iconListFiltered = this._iconList;
this._iconsFiltered = this.#icons;
if (this.modalContext) {
this.observe(
this.modalContext?.value,
(newValue) => {
this._modalValue = newValue;
this._currentAlias = newValue?.color ?? 'text';
this._currentIcon = newValue?.icon;
this._currentColor = newValue?.color ?? 'text';
},
'_observeModalContextValue',
);
}
}
#changeIcon(e: InputEvent | KeyboardEvent) {
if (e.type == 'click' || (e.type == 'keyup' && (e as KeyboardEvent).key == 'Enter')) {
const iconName = (e.target as UUIIconElement).name;
if (iconName) {
this.modalContext?.updateValue({ icon: iconName });
}
}
}
#onColorChange(e: UUIColorSwatchesEvent) {
const colorAlias = e.target.value;
this.modalContext?.updateValue({ color: colorAlias });
this._currentColor = colorAlias;
}
render() {
// TODO: Missing localization in general. [NL]
return html`
<umb-body-layout headline="Select Icon">
<div id="container">
${this.renderSearchbar()}
${this.renderSearch()}
<hr />
<uui-color-swatches
.value=${this._currentAlias}
.value=${this._currentColor}
label="Color switcher for icons"
@change=${this.#onColorChange}>
${
// TODO: Missing translation for the color aliases.
// TODO: Missing localization for the color aliases. [NL]
this._colorList.map(
(color) => html`
<uui-color-swatch
@@ -88,7 +104,7 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
}
</uui-color-swatches>
<hr />
<uui-scroll-container id="icon-selection">${this.renderIconSelection()}</uui-scroll-container>
<uui-scroll-container id="icons">${this.renderIcons()}</uui-scroll-container>
</div>
<uui-button
slot="actions"
@@ -104,36 +120,37 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
`;
}
renderSearchbar() {
renderSearch() {
return html` <uui-input
type="search"
placeholder=${this.localize.term('placeholders_filter')}
label=${this.localize.term('placeholders_filter')}
id="searchbar"
id="search"
@keyup="${this.#filterIcons}"
${umbFocus()}>
<uui-icon name="search" slot="prepend" id="searchbar_icon"></uui-icon>
<uui-icon name="search" slot="prepend" id="search_icon"></uui-icon>
</uui-input>`;
}
renderIconSelection() {
return repeat(
this._iconListFiltered,
(icon) => icon.name,
(icon) => html`
<uui-icon
tabindex="0"
style="--uui-icon-color: var(${extractUmbColorVariable(this._currentAlias)})"
class="icon ${icon.name === this._modalValue?.icon ? 'selected' : ''}"
title="${icon.name}"
name="${icon.name}"
label="${icon.name}"
id="${icon.name}"
@click="${this.#changeIcon}"
@keyup="${this.#changeIcon}">
</uui-icon>
`,
);
renderIcons() {
return this._iconsFiltered
? repeat(
this._iconsFiltered,
(icon) => icon.name,
(icon) => html`
<uui-button
label="${icon.name}"
class="${icon.name === this._currentIcon ? 'selected' : ''}"
@click="${this.#changeIcon}"
@keyup="${this.#changeIcon}">
<uui-icon
style="--uui-icon-color: var(${extractUmbColorVariable(this._currentColor)})"
name="${icon.name}">
</uui-icon>
</uui-button>
`,
)
: nothing;
}
static styles = [
@@ -160,15 +177,15 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
margin: 20px 0;
}
#searchbar {
#search {
width: 100%;
align-items: center;
}
#searchbar_icon {
#search_icon {
padding-left: var(--uui-size-space-2);
}
#icon-selection {
#icons {
line-height: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(40px, calc((100% / 12) - 10px)));
@@ -179,27 +196,17 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
padding: 2px;
}
#icon-selection .icon {
display: inline-block;
#icons uui-button {
border-radius: var(--uui-border-radius);
width: 100%;
height: 100%;
padding: var(--uui-size-space-3);
box-sizing: border-box;
cursor: pointer;
font-size: 16px; /* specific for icons */
}
#icon-selection .icon-container {
display: inline-block;
}
#icon-selection .icon:focus,
#icon-selection .icon:hover,
#icon-selection .icon.selected {
#icons uui-button:focus,
#icons uui-button:hover,
#icons uui-button.selected {
outline: 2px solid var(--uui-color-selected);
}
uui-button {
uui-button[slot='actions'] {
margin-left: var(--uui-size-space-4);
}

View File

@@ -1,13 +1,25 @@
export const umbracoColors = [
{ alias: 'text', varName: '--uui-color-text' },
{ alias: 'black', varName: '--uui-color-text' },
{ alias: 'yellow', varName: '--uui-palette-sunglow' },
{ alias: 'pink', varName: '--uui-palette-spanish-pink' },
{ alias: 'dark', varName: '--uui-palette-gunmetal' },
{ alias: 'darkblue', varName: '--uui-palette-space-cadet' },
{ alias: 'blue', varName: '--uui-palette-violet-blue' },
{ alias: 'light-blue', varName: '--uui-palette-malibu' },
{ alias: 'red', varName: '--uui-palette-maroon-flush' },
{ alias: 'green', varName: '--uui-palette-jungle-green' },
{ alias: 'brown', varName: '--uui-palette-chamoisee' },
{ alias: 'grey', varName: '--uui-palette-dusty-grey' },
{ alias: 'blue-grey', legacy: true, varName: '--uui-palette-dusty-grey' },
{ alias: 'indigo', legacy: true, varName: '--uui-palette-malibu' },
{ alias: 'purple', legacy: true, varName: '--uui-palette-space-cadet' },
{ alias: 'deep-purple', legacy: true, varName: '--uui-palette-space-cadet' },
{ alias: 'cyan', legacy: true, varName: '-uui-palette-jungle-green' },
{ alias: 'light-green', legacy: true, varName: '-uui-palette-jungle-green' },
{ alias: 'lime', legacy: true, varName: '-uui-palette-jungle-green' },
{ alias: 'amber', legacy: true, varName: '--uui-palette-chamoisee' },
{ alias: 'orange', legacy: true, varName: '--uui-palette-chamoisee' },
{ alias: 'deep-orange', legacy: true, varName: '--uui-palette-cocoa-brown' },
];
export function extractUmbColorVariable(colorAlias: string): string | undefined {

View File

@@ -86,7 +86,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
(app) => app.component,
)}
</umb-section-sidebar>
`
`
: nothing}
<umb-section-main>
${this._routes && this._routes.length > 0
@@ -105,10 +105,6 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
height: 100%;
display: flex;
}
h3 {
padding: var(--uui-size-4) var(--uui-size-8);
}
`,
];
}

View File

@@ -98,7 +98,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
</umb-router-slot>
</umb-body-layout>
`
: html`${nothing}`;
: nothing;
}
#renderDashboards() {
@@ -117,7 +117,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
})}
</uui-tab-group>
`
: '';
: nothing;
}
#renderViews() {
@@ -140,7 +140,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
})}
</uui-tab-group>
`
: '';
: nothing;
}
static styles = [