diff --git a/packages/components/src/icon/icon.stories.ts b/packages/components/src/icon/icon.stories.ts new file mode 100644 index 0000000..e7e7eb9 --- /dev/null +++ b/packages/components/src/icon/icon.stories.ts @@ -0,0 +1,43 @@ +import type { StoryFn, Meta, StoryObj } from '@storybook/html'; +import { Icon } from './index'; + +// Register the icon with proper SVG formatting +Icon.register({ + name: 'search', + svgStr: ` + + + ` +}); + +export default { + title: 'Components/Icon', + argTypes: { + name: { control: 'select', options: ['search', 'default'] } + }, + parameters: { + actions: { disabled: true } + } +} as Meta; + +const Template: StoryFn = (args, context): string => { + if (args.delay !== undefined) { + setTimeout(() => { + Icon.register({ + name: 'search', + svgStr: ` + ` + }); + }, args.delay); + } + + return ``; +}; + +export const Default: StoryObj = { render: Template.bind({}) }; +Default.args = { name: 'search' }; +export const ChangeIcon: StoryObj = { render: Template.bind({}) }; +ChangeIcon.args = { + ...Default.args, + delay: 2000 // Two seconds delay +}; diff --git a/packages/components/src/icon/icon.test.ts b/packages/components/src/icon/icon.test.ts new file mode 100644 index 0000000..ff6c481 --- /dev/null +++ b/packages/components/src/icon/icon.test.ts @@ -0,0 +1,18 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=components-icon--default'); + expect(await page.locator('jp-icon').screenshot()).toMatchSnapshot( + 'icon-default.png' + ); +}); +test('ChangeIcon', async ({ page }) => { + await page.goto('/iframe.html?id=components-icon--change-icon'); + await page.waitForTimeout(2000); + expect(await page.locator('jp-icon').screenshot()).toMatchSnapshot( + 'icon-change-icon.png' + ); +}); diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-chromium-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-chromium-linux.png new file mode 100644 index 0000000..8ed077c Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-chromium-linux.png differ diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-firefox-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-firefox-linux.png new file mode 100644 index 0000000..05bfb87 Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-firefox-linux.png differ diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-webkit-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-webkit-linux.png new file mode 100644 index 0000000..3298f09 Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-change-icon-webkit-linux.png differ diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-default-chromium-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-chromium-linux.png new file mode 100644 index 0000000..662d255 Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-chromium-linux.png differ diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-default-firefox-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-firefox-linux.png new file mode 100644 index 0000000..49f57c6 Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-firefox-linux.png differ diff --git a/packages/components/src/icon/icon.test.ts-snapshots/icon-default-webkit-linux.png b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-webkit-linux.png new file mode 100644 index 0000000..e464b65 Binary files /dev/null and b/packages/components/src/icon/icon.test.ts-snapshots/icon-default-webkit-linux.png differ diff --git a/packages/components/src/icon/index.ts b/packages/components/src/icon/index.ts new file mode 100644 index 0000000..41f75db --- /dev/null +++ b/packages/components/src/icon/index.ts @@ -0,0 +1,85 @@ +import { + FASTElement, + customElement, + attr, + html +} from '@microsoft/fast-element'; + +const template = html`
`; + +/** + * Icon component + * + * Icon must first be registered: `Icon.register({ name, svgStr });` + * + * Then you can use it with `` . + * + * To style your icon, you should set `fill` and/or `stroke` attributes to `currentColor`. + * Then the icon will be colored with the active text color. + */ +@customElement({ + name: 'jp-icon', + template +}) +export class Icon extends FASTElement { + /** + * Name of the icon to display. + */ + @attr name: string; + + private static iconsMap = new Map(); + private static _defaultIcon = + ''; + + /** + * Register a new icon. + * + * @param options { name: Icon unique name, svgStr: Icon SVG as string } + */ + static register(options: { name: string; svgStr: string }): void { + if (Icon.iconsMap.has(options.name)) { + console.warn( + `Redefining previously loaded icon svgStr. name: ${ + options.name + }, svgStrOld: ${Icon.iconsMap.get(options.name)}, svgStr: ${ + options.svgStr + }` + ); + } + Icon.iconsMap.set(options.name, options.svgStr); + + // Rerender all existing icons with the same name + document + .querySelectorAll(`jp-icon[name="${options.name}"]`) + .forEach((node: HTMLElement) => { + node.setAttribute('name', ''); + node.setAttribute('name', options.name); + }); + } + + /** + * Set a new default icon. + * + * @param svgStr The SVG string to be used as the default icon. + */ + static setDefaultIcon(svgStr: string): void { + Icon._defaultIcon = svgStr; + } + + /** + * Default icon + * + * This icon will be displayed if the {@link name} does not match any known + * icon names. + */ + static defaultIcon(): string { + return Icon._defaultIcon; + } + + /** + * Get the icon SVG + */ + getSvg(): string { + return Icon.iconsMap.get(this.name) ?? Icon.defaultIcon(); + } +}