Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2bbd99c
extend icons with information from theseaurus
nielslyngsoe Apr 10, 2026
c167993
implement new icon search logic
nielslyngsoe Apr 10, 2026
fbc5d5c
clean-up data
nielslyngsoe Apr 11, 2026
31487c4
icon manager
nielslyngsoe Apr 11, 2026
5b926af
sorting with a backup of the name
nielslyngsoe Apr 13, 2026
4eb7330
refactor into a controller
nielslyngsoe Apr 13, 2026
ddd9269
Merge branch 'main' into v17/feature/extend-icon-data
nielslyngsoe Apr 13, 2026
b477e12
improve multi word group search
nielslyngsoe Apr 13, 2026
4fda3c9
embed lucide data
nielslyngsoe Apr 13, 2026
7f4ca19
rename tech into technology
nielslyngsoe Apr 13, 2026
15d0b0e
remove paper from dollar
nielslyngsoe Apr 13, 2026
a6aff34
Merge branch 'v17/feature/extend-icon-data' into v17/feature/dev-icon…
nielslyngsoe Apr 13, 2026
27ff596
Potential fix for pull request finding
nielslyngsoe Apr 13, 2026
a33f548
Potential fix for pull request finding
nielslyngsoe Apr 13, 2026
c356564
improve search
nielslyngsoe Apr 14, 2026
22bdb20
related should not show up in search
nielslyngsoe Apr 15, 2026
ead0a7c
update threshold
nielslyngsoe Apr 15, 2026
602c020
Merge branch 'main' into v17/feature/extend-icon-data
nielslyngsoe Apr 16, 2026
cb40398
separate name words
nielslyngsoe Apr 16, 2026
bf30984
also consider full icon name match
nielslyngsoe Apr 16, 2026
e5c5130
better comment
nielslyngsoe Apr 16, 2026
c134a15
other approach for full name matches
nielslyngsoe Apr 16, 2026
253cc0e
full icon name search if query contains a -
nielslyngsoe Apr 16, 2026
b983ddc
Merge branch 'main' into v17/feature/extend-icon-data
nielslyngsoe Apr 16, 2026
e412a28
Merge branch 'main' into v17/feature/extend-icon-data
nielslyngsoe Apr 17, 2026
96f6df2
fix test
nielslyngsoe Apr 17, 2026
2a7fdc3
Merge branch 'v17/feature/extend-icon-data' into v17/feature/dev-icon…
nielslyngsoe Apr 17, 2026
3f19772
Merge branch 'main' into v17/feature/dev-icon-manager
nielslyngsoe May 7, 2026
16dbe4e
remove related code
nielslyngsoe May 7, 2026
0881ddb
updates to related
nielslyngsoe May 7, 2026
b77d20e
make its own package
nielslyngsoe May 7, 2026
f0f91c5
Merge branch 'main' into v17/feature/dev-icon-manager
nielslyngsoe May 7, 2026
1b72f82
revert changes
nielslyngsoe May 7, 2026
993a6b2
update tsconfig
nielslyngsoe May 7, 2026
80e3f23
package-lock
nielslyngsoe May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/devops/icon-manager/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
43 changes: 43 additions & 0 deletions src/Umbraco.Web.UI.Client/devops/icon-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Icon Manager

> **Experimental**: This tool is experimental and may change or be removed without notice.

A browser-based curation UI for the Umbraco backoffice icon dictionary
(`src/packages/core/icon-registry/icon-dictionary.json`). It loads the
dictionary, lets you edit metadata (keywords, groups, names), and exports a
new JSON blob to download.

The tool is its own self-contained Vite project — it has its own
`package.json`, lockfile, and `node_modules`. It only reads the parent
backoffice source tree; it does not write back automatically.

## Setup

```bash
npm install
```

## Usage

From this folder:

```bash
npm run dev
```

Or from the backoffice client root (`src/Umbraco.Web.UI.Client/`):

```bash
npm run dev:icon-manager
```

Both open the tool at `/icon-manager.html` in a browser.

## Applying changes

1. Edit icons in the UI.
2. Click **Export JSON** — the browser downloads an updated dictionary.
3. Replace `src/packages/core/icon-registry/icon-dictionary.json` with the
downloaded file.
4. Re-run `npm run generate:icons` from the backoffice client root to
regenerate the icon modules consumed at runtime.
172 changes: 172 additions & 0 deletions src/Umbraco.Web.UI.Client/devops/icon-manager/icon-card.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { LitElement, html, css, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
import type { ManagedIcon } from './types.js';
import '@umbraco-ui/uui';

@customElement('icon-card')
export class IconCardElement extends LitElement {
@property({ type: Object })
icon!: ManagedIcon;

@property({ type: String })
mode: 'picked' | 'unpicked' = 'picked';

#onAdd(e: Event) {
e.stopPropagation();
this.dispatchEvent(new CustomEvent('icon-add', { detail: this.icon, bubbles: true, composed: true }));
}

#onSelect() {
this.dispatchEvent(new CustomEvent('icon-select', { detail: this.icon, bubbles: true, composed: true }));
}

override render() {
if (!this.icon) return nothing;

const hasMetadata = this.icon.keywords.length > 0 || this.icon.groups.length > 0;

return html`
<div
class="card ${this.icon.isNew ? 'new' : ''} ${this.icon.isDirty ? 'dirty' : ''}"
@click=${this.#onSelect}
tabindex="0"
@keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.#onSelect()}>
<div class="icon-preview">
${this.icon.svgMarkup ? unsafeSVG(this.icon.svgMarkup) : html`<span class="placeholder">?</span>`}
</div>
<div class="icon-name" title=${this.icon.name}>${this.icon.name}</div>
<div class="icon-file" title=${this.icon.file}>${this.icon.file}</div>
${this.mode === 'picked' && hasMetadata ? html`<div class="metadata-dot" title="Has metadata"></div>` : nothing}
${this.icon.isNew ? html`<div class="badge new-badge">NEW</div>` : nothing}
${this.icon.isDirty ? html`<div class="badge dirty-badge">EDITED</div>` : nothing}
${this.mode === 'unpicked'
? html`<uui-button class="add-btn" look="primary" compact @click=${this.#onAdd}>+</uui-button>`
: nothing}
</div>
`;
}

static override styles = css`
:host {
display: block;
}

.card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px 8px;
border: 1px solid var(--uui-color-border, #d8d7d9);
border-radius: 6px;
background: var(--uui-color-surface, #fff);
cursor: pointer;
transition: border-color 0.15s, box-shadow 0.15s;
min-height: 100px;
}

.card:hover {
border-color: var(--uui-color-interactive, #3544b1);
box-shadow: 0 0 0 1px var(--uui-color-interactive, #3544b1);
}

.card:focus-visible {
outline: 2px solid var(--uui-color-interactive, #3544b1);
outline-offset: 2px;
}

.card.new {
border-color: var(--uui-color-positive, #2bc37c);
}

.card.dirty {
border-color: var(--uui-color-warning, #f5c142);
}

.icon-preview {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: var(--uui-color-text, #1b264f);
}

.icon-preview svg {
width: 100%;
height: 100%;
}

.placeholder {
font-size: 20px;
opacity: 0.3;
}

.icon-name {
margin-top: 6px;
font-size: 10px;
font-weight: 600;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}

.icon-file {
font-size: 9px;
opacity: 0.5;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}

.metadata-dot {
position: absolute;
top: 4px;
right: 4px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--uui-color-interactive, #3544b1);
}

.badge {
position: absolute;
top: 4px;
left: 4px;
font-size: 8px;
font-weight: 700;
padding: 1px 4px;
border-radius: 3px;
text-transform: uppercase;
}

.new-badge {
background: var(--uui-color-positive, #2bc37c);
color: white;
}

.dirty-badge {
background: var(--uui-color-warning, #f5c142);
color: #1b264f;
}

.add-btn {
position: absolute;
top: 4px;
right: 4px;
--uui-button-height: 24px;
font-size: 16px;
}
`;
}

declare global {
interface HTMLElementTagNameMap {
'icon-card': IconCardElement;
}
}
Loading
Loading