Merge pull request #29002 from backstage/canon-icon-provider

CanonProvider → IconProvider
This commit is contained in:
Charles de Dreuille
2025-02-28 11:40:24 +00:00
committed by GitHub
14 changed files with 103 additions and 92 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
We are renaming CanonProvider to IconProvider to improve clarity on how to override icons.
+2 -14
View File
@@ -17,7 +17,7 @@ building it incrementally with not conflict with the existing theming system.
Install Canon using a package manager.
<CodeBlock lang="shell" code={`npm install @backstage/canon`} />
<CodeBlock lang="shell" code={`yarn add @backstage/canon`} />
### 2. Import the css files
@@ -28,19 +28,7 @@ import '@backstage/canon/css/core.css';
import '@backstage/canon/css/components.css';
```
### 3. Add the theme provider
Add the theme provider to your application.
```tsx
import { ThemeProvider } from '@backstage/canon';
<ThemeProvider>
<App />
</ThemeProvider>;
```
### 4. Start building ✨
### 3. Start building ✨
Now you can start building your plugin using the new design system.
@@ -9,7 +9,15 @@ 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.
<CodeBlock title="Icon component" code={`<Icon name="heart" />`} />
<CodeBlock code={`<Icon name="heart" />`} />
## Icon overrides
You can override any icons in our library by using the `IconProvider` at the root of your application.
<CodeBlock
code={`<IconProvider overrides={{ heart: () => <div>Custom Icon</div> }} />`}
/>
## Icon library
@@ -105,19 +105,3 @@ the value, you add an object with the value and the breakpoint prefix.
// Responsive value
<Button size={{ xs: 'small', md: 'medium' }}>Button</Button>`} />
## How to update breakpoints
The set of keys are not to be changed, but you can update the minimum width of
each breakpoint in the theme provider.
<CodeBlock
code={`<CanonProvider breakpoints={{
xs: 0,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536,
}} />`}
/>
+1 -6
View File
@@ -1,13 +1,8 @@
'use client';
import { ReactNode } from 'react';
import { CanonProvider } from '../../../packages/canon/src/contexts/canon';
import { PlaygroundProvider } from '@/utils/playground-context';
export const Providers = ({ children }: { children: ReactNode }) => {
return (
<CanonProvider>
<PlaygroundProvider>{children}</PlaygroundProvider>
</CanonProvider>
);
return <PlaygroundProvider>{children}</PlaygroundProvider>;
};
+1 -6
View File
@@ -1,7 +1,6 @@
import React from 'react';
import type { Preview, ReactRenderer } from '@storybook/react';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
import { CanonProvider } from '../src/contexts/canon';
// Canon specific styles
import '../src/css/core.css';
@@ -87,11 +86,7 @@ const preview: Preview = {
(element as HTMLElement).style.backgroundColor = 'var(--canon-bg)';
});
return (
<CanonProvider>
<Story />
</CanonProvider>
);
return <Story />;
},
],
};
+22 -18
View File
@@ -5,6 +5,7 @@
```ts
/// <reference types="react" />
import { Context } from 'react';
import type { CSSProperties } from 'react';
import { Field as Field_2 } from '@base-ui-components/react/field';
import { ForwardRefExoticComponent } from 'react';
@@ -162,23 +163,6 @@ export interface ButtonProps {
variant?: ButtonOwnProps['variant'];
}
// @public (undocumented)
export interface CanonContextProps {
// (undocumented)
icons: IconMap;
}
// @public (undocumented)
export const CanonProvider: (props: CanonProviderProps) => React_2.JSX.Element;
// @public (undocumented)
export interface CanonProviderProps {
// (undocumented)
children?: ReactNode;
// (undocumented)
overrides?: Partial<Record<IconNames, React_2.ComponentType>>;
}
// @public (undocumented)
export const Checkbox: React_2.ForwardRefExoticComponent<
CheckboxProps & React_2.RefAttributes<HTMLButtonElement>
@@ -617,6 +601,15 @@ export type HeightProps = GetPropDefTypes<typeof heightPropDefs>;
// @public (undocumented)
export const Icon: (props: IconProps) => React_2.JSX.Element;
// @public (undocumented)
export const IconContext: Context<IconContextProps>;
// @public (undocumented)
export interface IconContextProps {
// (undocumented)
icons: IconMap;
}
// @public (undocumented)
export type IconMap = Partial<Record<IconNames, React.ComponentType>>;
@@ -651,6 +644,17 @@ export type IconProps = {
style?: React.CSSProperties;
};
// @public (undocumented)
export const IconProvider: (props: IconProviderProps) => React_2.JSX.Element;
// @public (undocumented)
export interface IconProviderProps {
// (undocumented)
children?: ReactNode;
// (undocumented)
overrides?: Partial<Record<IconNames, React.ComponentType>>;
}
// @public (undocumented)
export const icons: IconMap;
@@ -968,7 +972,7 @@ export interface TextProps {
}
// @public (undocumented)
export const useCanon: () => CanonContextProps;
export const useIcons: () => IconContextProps;
// @public (undocumented)
export interface UtilityProps extends SpaceProps {
@@ -17,7 +17,7 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Icon } from './Icon';
import { CanonProvider } from '../../contexts/canon';
import { IconProvider } from './provider';
import { icons } from './icons';
const meta = {
@@ -52,9 +52,9 @@ export const WithCustomIcon: Story = {
},
decorators: [
Story => (
<CanonProvider overrides={{ arrowDown: () => <div>Custom Icon</div> }}>
<IconProvider overrides={{ arrowDown: () => <div>Custom Icon</div> }}>
<Story />
</CanonProvider>
</IconProvider>
),
],
};
+2 -2
View File
@@ -15,14 +15,14 @@
*/
import React from 'react';
import { useCanon } from '../../contexts/canon';
import { useIcons } from './context';
import type { IconProps } from './types';
import clsx from 'clsx';
/** @public */
export const Icon = (props: IconProps) => {
const { name, size, className, style, ...restProps } = props;
const { icons } = useCanon();
const { icons } = useIcons();
const CanonIcon = icons[name] as React.ComponentType<Omit<IconProps, 'name'>>;
@@ -0,0 +1,27 @@
/*
* 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 { createContext, useContext } from 'react';
import { icons } from './icons';
import type { IconContextProps } from './types';
/** @public */
export const IconContext = createContext<IconContextProps>({
icons,
});
/** @public */
export const useIcons = () => useContext(IconContext);
+10 -1
View File
@@ -14,6 +14,15 @@
* limitations under the License.
*/
export * from './Icon';
// Icons
export * from './icons';
// Icon component
export * from './Icon';
// Context and Provider
export * from './context';
export * from './provider';
// Types
export type * from './types';
@@ -14,38 +14,21 @@
* limitations under the License.
*/
import React, { createContext, ReactNode, useContext } from 'react';
import { icons } from '../components/Icon/icons';
import { IconMap, IconNames } from '../components/Icon/types';
import React from 'react';
import { icons } from './icons';
import { IconContext } from './context';
import type { IconProviderProps } from './types';
/** @public */
export interface CanonContextProps {
icons: IconMap;
}
/** @public */
export interface CanonProviderProps {
children?: ReactNode;
overrides?: Partial<Record<IconNames, React.ComponentType>>;
}
const CanonContext = createContext<CanonContextProps>({
icons,
});
/** @public */
export const CanonProvider = (props: CanonProviderProps) => {
export const IconProvider = (props: IconProviderProps) => {
const { children, overrides } = props;
// Merge provided overrides with default icons
const combinedIcons = { ...icons, ...overrides };
return (
<CanonContext.Provider value={{ icons: combinedIcons }}>
<IconContext.Provider value={{ icons: combinedIcons }}>
{children}
</CanonContext.Provider>
</IconContext.Provider>
);
};
/** @public */
export const useCanon = () => useContext(CanonContext);
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { ReactNode } from 'react';
/** @public */
export type IconNames =
| 'arrowDown'
@@ -47,3 +49,14 @@ export type IconProps = {
className?: string;
style?: React.CSSProperties;
};
/** @public */
export interface IconContextProps {
icons: IconMap;
}
/** @public */
export interface IconProviderProps {
children?: ReactNode;
overrides?: Partial<Record<IconNames, React.ComponentType>>;
}
+1 -1
View File
@@ -21,7 +21,7 @@
*/
// Providers
export * from './contexts/canon';
export * from './components/Icon/context';
// Layout components
export * from './components/Box';