Skip to content

Commit

Permalink
Allow to register icons using the react component
Browse files Browse the repository at this point in the history
  • Loading branch information
fcollonval committed Aug 5, 2024
1 parent c466cd0 commit 7a517db
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 28 deletions.
16 changes: 11 additions & 5 deletions packages/components/src/icon/icon.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Icon } from './index';
// Register the icon with proper SVG formatting
Icon.register({
name: 'search',
svgStr: `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="currentColor"/>
svgStr: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
fill="currentColor"
/>
</svg>`
});

Expand All @@ -25,8 +27,12 @@ const Template: StoryFn = (args, context): string => {
setTimeout(() => {
Icon.register({
name: 'search',
svgStr: `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 576 512"><path d="M575.8 255.5c0 18-15 32.1-32 32.1l-32 0 .7 160.2c0 2.7-.2 5.4-.5 8.1l0 16.2c0 22.1-17.9 40-40 40l-16 0c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1L416 512l-24 0c-22.1 0-40-17.9-40-40l0-24 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 64 0 24c0 22.1-17.9 40-40 40l-24 0-31.9 0c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2l-16 0c-22.1 0-40-17.9-40-40l0-112c0-.9 0-1.9 .1-2.8l0-69.7-32 0c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z" fill="currentColor"/></svg>`
svgStr: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1l-32 0 .7 160.2c0 2.7-.2 5.4-.5 8.1l0 16.2c0 22.1-17.9 40-40 40l-16 0c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1L416 512l-24 0c-22.1 0-40-17.9-40-40l0-24 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 64 0 24c0 22.1-17.9 40-40 40l-24 0-31.9 0c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2l-16 0c-22.1 0-40-17.9-40-40l0-112c0-.9 0-1.9 .1-2.8l0-69.7-32 0c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
fill="currentColor"
/>
</svg>`
});
}, args.delay);
}
Expand Down
20 changes: 17 additions & 3 deletions packages/components/src/icon/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
import { iconStyles as styles } from './icon.styles';

/**
* Icon definition
*/
export interface IconDefinition {
/**
* Icon unique name
*/
name: string;
/**
* Icon SVG as string
*/
svgStr: string;
}

/**
* Icon component
*
Expand All @@ -23,9 +37,9 @@ export class Icon extends FASTElement {
/**
* Register a new icon.
*
* @param options { name: Icon unique name, svgStr: Icon SVG as string }
* @param options Icon definition
*/
static register(options: { name: string; svgStr: string }): void {
static register(options: IconDefinition): void {
if (Icon.iconsMap.has(options.name)) {
console.warn(
`Redefining previously loaded icon svgStr. name: ${
Expand All @@ -47,7 +61,7 @@ export class Icon extends FASTElement {
}

/**
* Set a new default icon.
* Set the default icon.
*
* @param svgStr The SVG string to be used as the default icon.
*/
Expand Down
31 changes: 28 additions & 3 deletions packages/lab-example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DateField,
Disclosure,
Divider,
Icon,
Listbox,
Menu,
MenuItem,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
addJupyterLabThemeChangeListener,
allComponents,
DataGrid as WebDataGrid,
Icon as WebIcon,
provideJupyterDesignSystem
} from '@jupyter/web-components';
import {
Expand All @@ -65,6 +67,23 @@ import {
provideJupyterDesignSystem().register(allComponents);
addJupyterLabThemeChangeListener();

WebIcon.register({
name: 'search',
svgStr: `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="currentColor"/>
</svg>`
});
Icon.register({
name: 'home',
svgStr: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1l-32 0 .7 160.2c0 2.7-.2 5.4-.5 8.1l0 16.2c0 22.1-17.9 40-40 40l-16 0c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1L416 512l-24 0c-22.1 0-40-17.9-40-40l0-24 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 64 0 24c0 22.1-17.9 40-40 40l-24 0-31.9 0c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2l-16 0c-22.1 0-40-17.9-40-40l0-112c0-.9 0-1.9 .1-2.8l0-69.7-32 0c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
fill="currentColor"
/>
</svg>`
});

const TABLE_DATA = [
{
Header1: 'Data 1 1',
Expand Down Expand Up @@ -193,7 +212,7 @@ const plugin: JupyterFrontEndPlugin<void> = {

app.restored.then(() => {
app.shell.add(widget, 'main');
app.shell.add(reactWidget, 'main', { mode: 'split-right' });
app.shell.add(reactWidget, 'main', { mode: 'split-bottom' });
app.shell.activateById(widget.id);

const dataGrid: WebDataGrid | null =
Expand Down Expand Up @@ -356,7 +375,10 @@ function Artwork(props: { dataRef: React.Ref<WebDataGrid> }): JSX.Element {
<Avatar shape="circle">JS</Avatar>
<Badge>22</Badge>
<Breadcrumb>
<BreadcrumbItem href="#">Item 1</BreadcrumbItem>
<BreadcrumbItem href="#">
<Icon slot="start" name="home" />
Item 1
</BreadcrumbItem>
<BreadcrumbItem href="#">Item 2</BreadcrumbItem>
<BreadcrumbItem href="#">Item 3</BreadcrumbItem>
</Breadcrumb>
Expand Down Expand Up @@ -572,7 +594,10 @@ function createNode(): HTMLElement {
<jp-badge>18</jp-badge>
<jp-avatar shape="circle">JS</jp-avatar>
<jp-breadcrumb>
<jp-breadcrumb-item href="#">Item 1</jp-breadcrumb-item>
<jp-breadcrumb-item href="#">
Item 1
<jp-icon slot="end" name="search"></jp-icon>
</jp-breadcrumb-item>
<jp-breadcrumb-item href="#">Item 2</jp-breadcrumb-item>
<jp-breadcrumb-item href="#">Item 3</jp-breadcrumb-item>
</jp-breadcrumb>
Expand Down
19 changes: 16 additions & 3 deletions packages/react-components/lib/Icon.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Icon as IconElement } from '@jupyter/web-components';
import { Icon as IconElement, type IconDefinition } from '@jupyter/web-components';

export type { IconElement as BadgeElement };
export type { IconElement, IconDefinition };

export interface IconProps extends React.HTMLAttributes<HTMLElement> {
/**
Expand All @@ -16,4 +16,17 @@ export interface IconProps extends React.HTMLAttributes<HTMLElement> {
* Icon class
* ---
*/
export const Icon: React.ForwardRefExoticComponent<IconProps>;
export const Icon: React.ForwardRefExoticComponent<IconProps> & {
/**
* Register a new icon.
*
* @param options Icon definition
*/
register(options: IconDefinition): void;
/**
* Set the default icon.
*
* @param svgStr The SVG string to be used as the default icon.
*/
setDefaultIcon(svgStr: string): void;
};
50 changes: 36 additions & 14 deletions packages/react-components/lib/Icon.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import React, {
forwardRef,
useImperativeHandle,
useRef
} from 'react';
import { Icon as IconElement } from '@jupyter/web-components';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import { useProperties } from './react-utils.js';

/**
* Icon component
*
* Icon must first be registered: `Icon.register({ name, svgStr });`
*
* Then you can use it with `<Icon name="{name}" />` .
*
* 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.
*/
export const Icon = forwardRef((props, forwardedRef) => {
const ref = useRef(null);
const { className, name, ...filteredProps } = props;
Expand All @@ -15,13 +22,28 @@ export const Icon = forwardRef((props, forwardedRef) => {
/** Methods - uses `useImperativeHandle` hook to pass ref to component */
useImperativeHandle(forwardedRef, () => ref.current, [ref.current]);

return React.createElement(
'jp-icon',
{
ref,
...filteredProps,
class: props.className,
style: { ...props.style }
}
);
return React.createElement('jp-icon', {
ref,
...filteredProps,
class: props.className,
style: { ...props.style }
});
});

/**
* Register a new icon.
*
* @param {IconDefinition} options Icon definition
*/
Icon.register = options => {
IconElement.register(options);
};

/**
* Set the default icon.
*
* @param svgStr The SVG string to be used as the default icon.
*/
Icon.setDefaultIcon = svgStr => {
IconElement.setDefaultIcon(svgStr);
};

0 comments on commit 7a517db

Please sign in to comment.