diff --git a/apps/icons-plugin/src/app/app.component.css b/apps/icons-plugin/src/app/app.component.css
index e69de29..ae05e41 100644
--- a/apps/icons-plugin/src/app/app.component.css
+++ b/apps/icons-plugin/src/app/app.component.css
@@ -0,0 +1,29 @@
+.icons-plugin {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ gap: 5px;
+ overflow: hidden;
+
+ .icons-search,
+ .icons-list {
+ width: 100%;
+ }
+
+ .icons-search {
+ padding-top: var(--spacing-20);
+ }
+
+ .icons-list {
+ flex-grow: 1;
+ overflow: auto;
+ display: grid;
+ padding: 3px 3px 0;
+ background-color: var(--app-white);
+ border-radius: var(--spacing-8);
+ justify-content: start;
+ grid-template-columns: repeat(6, 1fr);
+ grid-auto-rows: max-content;
+ justify-items: center;
+ }
+}
diff --git a/apps/icons-plugin/src/app/app.component.ts b/apps/icons-plugin/src/app/app.component.ts
index 6de3c76..3781424 100644
--- a/apps/icons-plugin/src/app/app.component.ts
+++ b/apps/icons-plugin/src/app/app.component.ts
@@ -1,9 +1,12 @@
-import { Component, signal } from '@angular/core';
-import { RouterModule } from '@angular/router';
+import { Component, inject, signal } from '@angular/core';
+import { ActivatedRoute, RouterModule } from '@angular/router';
import { FeatherIconNames, icons } from 'feather-icons';
import { SafeHtmlPipe } from './pipes/safe-html.pipe';
import { IconButtonComponent } from './components/icon-button/icon-button.component';
import { IconSearchComponent } from './components/icon-search/icon-search.component';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { filter, fromEvent, map, merge, take } from 'rxjs';
+import { PluginMessageEvent } from '../model';
@Component({
selector: 'app-root',
@@ -15,29 +18,70 @@ import { IconSearchComponent } from './components/icon-search/icon-search.compon
IconSearchComponent,
],
styleUrl: './app.component.css',
- template: `
-
+ template: `
+
- @for (key of iconKeys(); track key) {
-
+ @if (iconKeys().length === 0) {
+
No icons found
+ } @else {
+
+ @for (key of iconKeys(); track key) {
+
+ }
+
}
`,
+ host: {
+ '[attr.data-theme]': 'theme()',
+ },
})
export class AppComponent {
+ public route = inject(ActivatedRoute);
public icons = signal(icons);
public iconKeys = signal(Object.keys(icons) as FeatherIconNames[]);
+ public messages$ = fromEvent
>(
+ window,
+ 'message'
+ );
+
+ public initialTheme$ = this.route.queryParamMap.pipe(
+ map((params) => params.get('theme')),
+ filter((theme) => !!theme),
+ take(1)
+ );
+
+ public theme = toSignal(
+ merge(
+ this.initialTheme$,
+ this.messages$.pipe(
+ filter((event) => event.data.type === 'theme'),
+ map((event) => {
+ return event.data.content;
+ })
+ )
+ )
+ );
public insertIcon(key: FeatherIconNames): void {
- if (key && this.icons()[key] && this.icons()[key].toSvg()) {
+ if (
+ key &&
+ this.icons()[key] &&
+ this.icons()[key].toSvg({
+ 'stroke-width': '3',
+ })
+ ) {
this.sendMessage({
- content: this.icons()[key].toSvg(),
- name: this.icons()[key].name || key,
+ type: 'insert-icon',
+ content: {
+ svg: this.icons()[key].toSvg(),
+ name: this.icons()[key].name || key,
+ },
});
}
}
diff --git a/apps/icons-plugin/src/app/app.config.ts b/apps/icons-plugin/src/app/app.config.ts
new file mode 100644
index 0000000..1b3e4af
--- /dev/null
+++ b/apps/icons-plugin/src/app/app.config.ts
@@ -0,0 +1,6 @@
+import { ApplicationConfig } from '@angular/core';
+import { provideRouter } from '@angular/router';
+
+export const appConfig: ApplicationConfig = {
+ providers: [provideRouter([])],
+};
diff --git a/apps/icons-plugin/src/app/components/icon-button/icon-button.component.css b/apps/icons-plugin/src/app/components/icon-button/icon-button.component.css
index aba847f..88c6145 100644
--- a/apps/icons-plugin/src/app/components/icon-button/icon-button.component.css
+++ b/apps/icons-plugin/src/app/components/icon-button/icon-button.component.css
@@ -1,16 +1,17 @@
.icon-button {
- padding: 0.25rem;
- color: #000;
+ padding: var(--spacing-4);
background: transparent;
- border-radius: 0.5rem;
- cursor: pointer;
+ border-radius: var(--spacing-8);
+ color: var(--app-black);
- &:hover {
- background: rgba(0, 0, 0, 0.1);
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: var(--lb-tertiary);
}
&:focus,
&:active {
- box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 0 0 2px var(--da-primary);
}
}
diff --git a/apps/icons-plugin/src/app/components/icon-button/icon-button.component.ts b/apps/icons-plugin/src/app/components/icon-button/icon-button.component.ts
index 68ee3b3..8054697 100644
--- a/apps/icons-plugin/src/app/components/icon-button/icon-button.component.ts
+++ b/apps/icons-plugin/src/app/components/icon-button/icon-button.component.ts
@@ -21,7 +21,7 @@ import { FeatherIcon } from 'feather-icons';
view-box="0 0 24 24"
fill="none"
stroke="currentColor"
- stroke-width="2"
+ stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
[innerHtml]="icon().contents | safeHtml"
diff --git a/apps/icons-plugin/src/app/components/icon-search/icon-search.component.css b/apps/icons-plugin/src/app/components/icon-search/icon-search.component.css
index e69de29..1b0496a 100644
--- a/apps/icons-plugin/src/app/components/icon-search/icon-search.component.css
+++ b/apps/icons-plugin/src/app/components/icon-search/icon-search.component.css
@@ -0,0 +1,16 @@
+.search-icon {
+ width: 100%;
+ border-radius: var(--spacing-8);
+ padding: var(--spacing-8);
+ padding-left: var(--spacing-28);
+ background: var(--background-tertiary)
+ url('data:image/svg+xml,')
+ var(--spacing-8) no-repeat;
+ color: var(--foreground-secondary);
+ font-size: var(--font-size-s);
+ font-weight: var(--font-weight-regular);
+
+ &:focus {
+ outline: none;
+ }
+}
diff --git a/apps/icons-plugin/src/app/components/icon-search/icon-search.component.ts b/apps/icons-plugin/src/app/components/icon-search/icon-search.component.ts
index a4fa234..8bac4e3 100644
--- a/apps/icons-plugin/src/app/components/icon-search/icon-search.component.ts
+++ b/apps/icons-plugin/src/app/components/icon-search/icon-search.component.ts
@@ -6,11 +6,14 @@ import { icons } from 'feather-icons';
standalone: true,
imports: [],
styleUrl: './icon-search.component.css',
- template: ``,
+ template: `
+
+ `,
})
export class IconSearchComponent {
public searchIcons = output();
diff --git a/apps/icons-plugin/src/assets/manifest.json b/apps/icons-plugin/src/assets/manifest.json
index a29992d..725b2b2 100644
--- a/apps/icons-plugin/src/assets/manifest.json
+++ b/apps/icons-plugin/src/assets/manifest.json
@@ -2,9 +2,5 @@
"name": "Icons plugin",
"host": "http://localhost:4303",
"code": "/assets/plugin.js",
- "permissions": [
- "page:read",
- "file:read",
- "selection:read"
- ]
+ "permissions": ["page:read", "file:read", "selection:read"]
}
diff --git a/apps/icons-plugin/src/main.ts b/apps/icons-plugin/src/main.ts
index 31c5da4..514c89a 100644
--- a/apps/icons-plugin/src/main.ts
+++ b/apps/icons-plugin/src/main.ts
@@ -1,4 +1,7 @@
import { bootstrapApplication } from '@angular/platform-browser';
+import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
-bootstrapApplication(AppComponent).catch((err) => console.error(err));
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err)
+);
diff --git a/apps/icons-plugin/src/model.ts b/apps/icons-plugin/src/model.ts
new file mode 100644
index 0000000..e3e834d
--- /dev/null
+++ b/apps/icons-plugin/src/model.ts
@@ -0,0 +1,27 @@
+export interface InitPluginEvent {
+ type: 'init';
+ content: {
+ theme: string;
+ };
+}
+
+export interface InsertIconEvent {
+ type: 'insert-icon';
+ content: {
+ svg: string;
+ name: string;
+ };
+}
+
+export interface InitPluginUIEvent {
+ type: 'ready';
+}
+
+export type PluginUIEvent = InitPluginUIEvent | InsertIconEvent;
+
+export interface ThemePluginEvent {
+ type: 'theme';
+ content: string;
+}
+
+export type PluginMessageEvent = InitPluginEvent | ThemePluginEvent;
diff --git a/apps/icons-plugin/src/plugin.ts b/apps/icons-plugin/src/plugin.ts
index ce1d992..f9e6dc5 100644
--- a/apps/icons-plugin/src/plugin.ts
+++ b/apps/icons-plugin/src/plugin.ts
@@ -1,17 +1,28 @@
-penpot.ui.open('Icons plugin', '', {
- width: 500,
- height: 600,
+import type { PluginMessageEvent, PluginUIEvent } from './model.js';
+
+penpot.ui.open('FEATHER ICONS PLUGIN', `?theme=${penpot.getTheme()}`, {
+ width: 292,
+ height: 540,
});
-penpot.ui.onMessage<{ content: string; name: string }>((message) => {
- if (!message.content || !message.name) {
- return;
+penpot.ui.onMessage((message) => {
+ if (message.type === 'insert-icon') {
+ const { name, svg } = message.content;
+
+ if (!svg || !name) {
+ return;
+ }
+ const icon = penpot.createShapeFromSvg(svg);
+ icon.name = name;
+ icon.x = penpot.viewport.center.x;
+ icon.y = penpot.viewport.center.y;
}
-
- const svgIcon = message.content;
- const iconName = message.name;
- const icon = penpot.createShapeFromSvg(svgIcon);
- icon.name = iconName;
- icon.x = penpot.viewport.center.x;
- icon.y = penpot.viewport.center.y;
});
+
+penpot.on('themechange', (theme) => {
+ sendMessage({ type: 'theme', content: theme });
+});
+
+function sendMessage(message: PluginMessageEvent) {
+ penpot.ui.sendMessage(message);
+}