diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx
index fc9736a6ed..bb0c497d2d 100644
--- a/packages/canon/.storybook/preview.tsx
+++ b/packages/canon/.storybook/preview.tsx
@@ -2,6 +2,10 @@ import React from 'react';
import type { Preview, ReactRenderer } from '@storybook/react';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
+// Storybook specific styles
+import '../docs/components/styles.css';
+
+// Canon specific styles
import '../src/theme/styles.css';
const preview: Preview = {
diff --git a/packages/canon/docs/Iconography.mdx b/packages/canon/docs/Iconography.mdx
new file mode 100644
index 0000000000..48cbd2e431
--- /dev/null
+++ b/packages/canon/docs/Iconography.mdx
@@ -0,0 +1,20 @@
+import { Unstyled, Source } from '@storybook/blocks';
+import { Title, Text, IconLibrary } from './components';
+
+
+
+Iconography
+
+
+ All our default icons are provided by [Remix Icon](https://remixicon.com/). We
+ don't import all icons to reduce the bundle size but we cherry pick a nice
+ selection for you to use in your application. The list of names is set down
+ below. To use an icon, you can use the `Icon` component and pass the name of
+ the icon you want to use.
+
+
+ `} language="tsx" dark />
+
+
+
+
diff --git a/packages/canon/docs/components/IconLibrary/index.tsx b/packages/canon/docs/components/IconLibrary/index.tsx
new file mode 100644
index 0000000000..8485da176e
--- /dev/null
+++ b/packages/canon/docs/components/IconLibrary/index.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+import { Icon } from '../../../src/components/Icon';
+import type { IconNames } from '../../../src/components/Icon/types';
+import { defaultIcons } from '../../../src/components/Icon/icons';
+import { Text } from '../Text';
+export const IconLibrary = () => {
+ const icons = Object.keys(defaultIcons);
+
+ return (
+
+ {icons.map(icon => (
+
+ ))}
+
+ );
+};
diff --git a/packages/canon/docs/components/IconLibrary/styles.css b/packages/canon/docs/components/IconLibrary/styles.css
new file mode 100644
index 0000000000..1a6f63ca79
--- /dev/null
+++ b/packages/canon/docs/components/IconLibrary/styles.css
@@ -0,0 +1,22 @@
+.icon-library {
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ gap: 1rem;
+}
+
+.icon-library-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.icon-library-item-icon {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ height: 80px;
+ border: 1px solid #d3d3d3;
+ border-radius: 0.5rem;
+}
diff --git a/packages/canon/docs/components/index.ts b/packages/canon/docs/components/index.ts
index 6acc4910cb..88a8cb0391 100644
--- a/packages/canon/docs/components/index.ts
+++ b/packages/canon/docs/components/index.ts
@@ -22,3 +22,4 @@ export * from './Columns';
export * from './ComponentStatus';
export * from './LayoutComponents';
export * from './Banner';
+export * from './IconLibrary';
diff --git a/packages/canon/docs/components/styles.css b/packages/canon/docs/components/styles.css
new file mode 100644
index 0000000000..63df601609
--- /dev/null
+++ b/packages/canon/docs/components/styles.css
@@ -0,0 +1 @@
+@import './IconLibrary/styles.css';
diff --git a/packages/canon/package.json b/packages/canon/package.json
index f1e2e5d759..4ec11b31fb 100644
--- a/packages/canon/package.json
+++ b/packages/canon/package.json
@@ -38,11 +38,11 @@
},
"dependencies": {
"@base_ui/react": "^1.0.0-alpha.3",
+ "@remixicon/react": "^4.5.0",
"@vanilla-extract/css": "^1.16.0",
"@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/recipes": "^0.5.5",
- "@vanilla-extract/sprinkles": "^1.6.3",
- "lucide-react": "^0.460.0"
+ "@vanilla-extract/sprinkles": "^1.6.3"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md
index 3878443bcd..723e08859b 100644
--- a/packages/canon/report.api.md
+++ b/packages/canon/report.api.md
@@ -9,7 +9,6 @@ import { CSSProperties } from 'react';
import { JSXElementConstructor } from 'react';
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
-import { ReactNode } from 'react';
// @public (undocumented)
export type AlignItems =
@@ -190,22 +189,18 @@ export type Gap = Space | Partial>;
export const Icon: ({ name }: { name: IconNames }) => React_2.JSX.Element;
// @public (undocumented)
-export type IconNames =
- | 'ArrowDown'
- | 'ArrowLeft'
- | 'ArrowRight'
- | 'ArrowUp'
- | 'Cloud'
- | 'CustomIcon';
+export type IconMap = Partial>;
// @public (undocumented)
-export const IconProvider: ({
- children,
- overrides,
-}: {
- children: ReactNode;
- overrides: Partial>;
-}) => React_2.JSX.Element;
+export type IconNames =
+ | 'arrowDown'
+ | 'arrowLeft'
+ | 'arrowRight'
+ | 'arrowUp'
+ | 'cloud'
+ | 'heart'
+ | 'plus'
+ | 'trash';
// @public (undocumented)
export type JustifyContent =
diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx
index 7359d36f10..7dddbd033b 100644
--- a/packages/canon/src/components/Button/Button.stories.tsx
+++ b/packages/canon/src/components/Button/Button.stories.tsx
@@ -71,9 +71,9 @@ export const WithIcons: Story = {
},
render: args => (
-
-
-
+
+
+
),
};
@@ -84,9 +84,9 @@ export const FullWidth: Story = {
},
render: args => (
-
-
-
+
+
+
),
};
diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx
index 50600c0b30..020d0adce4 100644
--- a/packages/canon/src/components/Button/Button.tsx
+++ b/packages/canon/src/components/Button/Button.tsx
@@ -17,7 +17,7 @@
import React from 'react';
import { button } from './button.css';
import { Icon } from '../Icon/Icon';
-import { IconNames } from '../Icon/context';
+import type { IconNames } from '../Icon/types';
/**
* Properties for {@link Button}
diff --git a/packages/canon/src/components/Icon/Icon.stories.tsx b/packages/canon/src/components/Icon/Icon.stories.tsx
index 6b53ef66b2..61e837c5ed 100644
--- a/packages/canon/src/components/Icon/Icon.stories.tsx
+++ b/packages/canon/src/components/Icon/Icon.stories.tsx
@@ -17,8 +17,8 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Icon } from './Icon';
-import { IconProvider } from './context';
-import * as LucideIcons from 'lucide-react';
+import { ThemeProvider } from '../../theme/context';
+import { defaultIcons } from './icons';
const meta = {
title: 'Components/Icon',
@@ -26,15 +26,14 @@ const meta = {
parameters: {
layout: 'centered',
},
- tags: ['autodocs'],
argTypes: {
name: {
control: 'select',
- options: Object.keys(LucideIcons),
+ options: Object.keys(defaultIcons),
},
},
args: {
- name: 'ArrowDown',
+ name: 'heart',
},
} satisfies Meta;
@@ -43,40 +42,19 @@ type Story = StoryObj;
export const Primary: Story = {
args: {
- name: 'ArrowDown',
- },
-};
-
-export const CustomIcon: Story = {
- args: {
- name: 'CustomIcon',
+ name: 'heart',
},
};
export const WithCustomIcon: Story = {
args: {
- name: 'ArrowDown',
+ name: 'arrowDown',
},
decorators: [
Story => (
- Custom Icon
}}>
+ Custom Icon
}}>
-
- ),
- ],
-};
-
-export const WithCustomIconOverride: Story = {
- args: {
- name: 'CustomIcon',
- },
- decorators: [
- Story => (
- Custom Super Icon
}}
- >
-
-
+
),
],
};
diff --git a/packages/canon/src/components/Icon/Icon.tsx b/packages/canon/src/components/Icon/Icon.tsx
index 9bed7f1055..d7891c23cf 100644
--- a/packages/canon/src/components/Icon/Icon.tsx
+++ b/packages/canon/src/components/Icon/Icon.tsx
@@ -15,18 +15,19 @@
*/
import React from 'react';
-import { useIcons, IconNames } from './context';
+import { useTheme } from '../../theme/context';
+import type { IconNames } from './types';
/** @public */
export const Icon = ({ name }: { name: IconNames }) => {
- const { icons } = useIcons();
+ const { icons } = useTheme();
- const LucideIcon = icons[name];
+ const RemixIcon = icons[name];
- if (!LucideIcon) {
+ if (!RemixIcon) {
console.error(`Icon "${name}" not found.`);
return ; // Return default icon perhaps?
}
- return ;
+ return ;
};
diff --git a/packages/canon/src/components/Icon/icons.ts b/packages/canon/src/components/Icon/icons.ts
new file mode 100644
index 0000000000..8c6a1248cb
--- /dev/null
+++ b/packages/canon/src/components/Icon/icons.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// We can add custom icons to the list outside of Remix
+
+import type { IconMap } from './types';
+import {
+ RiHeartFill,
+ RiArrowDownFill,
+ RiCloudFill,
+ RiArrowLeftFill,
+ RiArrowRightFill,
+ RiArrowUpFill,
+ RiDeleteBin6Line,
+ RiAddLine,
+} from '@remixicon/react';
+
+// List of default icons
+export const defaultIcons: IconMap = {
+ arrowDown: RiArrowDownFill,
+ arrowLeft: RiArrowLeftFill,
+ arrowRight: RiArrowRightFill,
+ arrowUp: RiArrowUpFill,
+ cloud: RiCloudFill,
+ heart: RiHeartFill,
+ plus: RiAddLine,
+ trash: RiDeleteBin6Line,
+};
diff --git a/packages/canon/src/components/Icon/index.tsx b/packages/canon/src/components/Icon/index.tsx
index 441dbe0c3b..3e7564c77e 100644
--- a/packages/canon/src/components/Icon/index.tsx
+++ b/packages/canon/src/components/Icon/index.tsx
@@ -15,5 +15,4 @@
*/
export * from './Icon';
-export { IconProvider } from './context';
-export type { IconNames } from './context';
+export type * from './types';
diff --git a/packages/canon/src/components/Icon/custom-icon.tsx b/packages/canon/src/components/Icon/types.ts
similarity index 68%
rename from packages/canon/src/components/Icon/custom-icon.tsx
rename to packages/canon/src/components/Icon/types.ts
index 27c3fd4517..6e16e627d4 100644
--- a/packages/canon/src/components/Icon/custom-icon.tsx
+++ b/packages/canon/src/components/Icon/types.ts
@@ -14,10 +14,16 @@
* limitations under the License.
*/
-import React from 'react';
+/** @public */
+export type IconNames =
+ | 'arrowDown'
+ | 'arrowLeft'
+ | 'arrowRight'
+ | 'arrowUp'
+ | 'cloud'
+ | 'heart'
+ | 'plus'
+ | 'trash';
-export const CustomIcon = () => (
-
-
-
-);
+/** @public */
+export type IconMap = Partial>;
diff --git a/packages/canon/src/components/Icon/context.tsx b/packages/canon/src/theme/context.tsx
similarity index 54%
rename from packages/canon/src/components/Icon/context.tsx
rename to packages/canon/src/theme/context.tsx
index 0cfc6252d7..77f43927f2 100644
--- a/packages/canon/src/components/Icon/context.tsx
+++ b/packages/canon/src/theme/context.tsx
@@ -15,39 +15,19 @@
*/
import React, { createContext, useContext, ReactNode } from 'react';
-import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Cloud } from 'lucide-react';
-import { CustomIcon } from './custom-icon';
+import { IconMap, IconNames } from '../components/Icon/types';
+import { defaultIcons } from '../components/Icon/icons';
-// List of icons available that can also be overridden.
-/** @public */
-export type IconNames =
- | 'ArrowDown'
- | 'ArrowLeft'
- | 'ArrowRight'
- | 'ArrowUp'
- | 'Cloud'
- | 'CustomIcon';
-
-type IconMap = Partial>;
-
-interface IconContextProps {
+interface ThemeContextProps {
icons: IconMap;
}
-// Create a default icon map with only the necessary icons
-const defaultIcons: IconMap = {
- ArrowDown,
- ArrowLeft,
- ArrowRight,
- ArrowUp,
- Cloud,
- CustomIcon,
-};
-
-const IconContext = createContext({ icons: defaultIcons });
+const ThemeContext = createContext({
+ icons: defaultIcons,
+});
/** @public */
-export const IconProvider = ({
+export const ThemeProvider = ({
children,
overrides,
}: {
@@ -58,11 +38,11 @@ export const IconProvider = ({
const combinedIcons = { ...defaultIcons, ...overrides };
return (
-
+
{children}
-
+
);
};
/** @public */
-export const useIcons = () => useContext(IconContext);
+export const useTheme = () => useContext(ThemeContext);
diff --git a/yarn.lock b/yarn.lock
index 132103a0be..3075fcd983 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3823,6 +3823,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@base_ui/react": ^1.0.0-alpha.3
"@chromatic-com/storybook": ^3.2.2
+ "@remixicon/react": ^4.5.0
"@storybook/addon-essentials": ^8.4.5
"@storybook/addon-interactions": ^8.4.5
"@storybook/addon-styling-webpack": ^1.0.1
@@ -3843,7 +3844,6 @@ __metadata:
"@vanilla-extract/webpack-plugin": ^2.3.14
eslint-plugin-storybook: ^0.11.1
globals: ^15.11.0
- lucide-react: ^0.460.0
mini-css-extract-plugin: ^2.9.2
react: ^18.0.2
react-dom: ^18.0.2
@@ -15458,6 +15458,15 @@ __metadata:
languageName: node
linkType: hard
+"@remixicon/react@npm:^4.5.0":
+ version: 4.5.0
+ resolution: "@remixicon/react@npm:4.5.0"
+ peerDependencies:
+ react: ">=18.2.0"
+ checksum: e37b61090954954601d35367a740b7be30c105a49f67eaa5a697db16d4668d71d9fd94b339da6d449a254736d5af3b567d3694021b79d3c82fe28afb818830bc
+ languageName: node
+ linkType: hard
+
"@repeaterjs/repeater@npm:^3.0.4":
version: 3.0.5
resolution: "@repeaterjs/repeater@npm:3.0.5"
@@ -35193,15 +35202,6 @@ __metadata:
languageName: node
linkType: hard
-"lucide-react@npm:^0.460.0":
- version: 0.460.0
- resolution: "lucide-react@npm:0.460.0"
- peerDependencies:
- react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
- checksum: 6106dc16dd7ce7928d6136e81c9e84e87e1b6b0910a0c78777a387c795c0512755e8bf4c602ab8f09518919999a494ed86ba7190863e8e7aec6c82665147ead3
- languageName: node
- linkType: hard
-
"lunr@npm:^2.3.9":
version: 2.3.9
resolution: "lunr@npm:2.3.9"