mirror of
https://github.com/penpot/penpot-plugins.git
synced 2025-01-21 06:02:34 -05:00
feat(icons-plugin): add styles and theme support
This commit is contained in:
parent
ec729e2e59
commit
66ebaffb06
11 changed files with 180 additions and 44 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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: `<div>
|
||||
<div>
|
||||
template: `<div class="icons-plugin">
|
||||
<div class="icons-search">
|
||||
<app-icon-search
|
||||
(searchIcons)="this.searchIcons($event)"
|
||||
></app-icon-search>
|
||||
</div>
|
||||
@for (key of iconKeys(); track key) {
|
||||
<app-icon-button
|
||||
[icon]="icons()[key]"
|
||||
(insertIcon)="this.insertIcon(key)"
|
||||
></app-icon-button>
|
||||
@if (iconKeys().length === 0) {
|
||||
<div class="no-icons-found">No icons found</div>
|
||||
} @else {
|
||||
<div class="icons-list">
|
||||
@for (key of iconKeys(); track key) {
|
||||
<app-icon-button
|
||||
[icon]="icons()[key]"
|
||||
(insertIcon)="this.insertIcon(key)"
|
||||
></app-icon-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>`,
|
||||
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<MessageEvent<PluginMessageEvent>>(
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
6
apps/icons-plugin/src/app/app.config.ts
Normal file
6
apps/icons-plugin/src/app/app.config.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { ApplicationConfig } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideRouter([])],
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" class="main-grid-item-icon" fill="none" stroke="%238f9da3" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /></svg>')
|
||||
var(--spacing-8) no-repeat;
|
||||
color: var(--foreground-secondary);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-regular);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
|
@ -6,11 +6,14 @@ import { icons } from 'feather-icons';
|
|||
standalone: true,
|
||||
imports: [],
|
||||
styleUrl: './icon-search.component.css',
|
||||
template: `<input
|
||||
type="search"
|
||||
[placeholder]="'Search ' + iconsCount() + ' icons'"
|
||||
(input)="onSearchIcons($event)"
|
||||
/>`,
|
||||
template: `
|
||||
<input
|
||||
class="search-icon"
|
||||
type="search"
|
||||
placeholder="Search an icon"
|
||||
(input)="onSearchIcons($event)"
|
||||
/>
|
||||
`,
|
||||
})
|
||||
export class IconSearchComponent {
|
||||
public searchIcons = output<string>();
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
27
apps/icons-plugin/src/model.ts
Normal file
27
apps/icons-plugin/src/model.ts
Normal file
|
@ -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;
|
|
@ -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<PluginUIEvent>((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);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue