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); +}