@indico-data/design-system 2.45.1 → 2.45.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.45.1",
3
+ "version": "2.45.3",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -1,6 +1,11 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { Button, Col, Row } from '@/components';
3
3
  import { iconNames } from '@/storybook/iconNames';
4
+ import { fas } from '@fortawesome/free-solid-svg-icons';
5
+ import { registerFontAwesomeIcons } from '@/setup/setupIcons';
6
+ import { indiconDefinitions } from '@/components/icons/indicons';
7
+
8
+ registerFontAwesomeIcons(...Object.values(fas), ...indiconDefinitions);
4
9
 
5
10
  const meta: Meta = {
6
11
  title: 'Components/Button',
@@ -6,7 +6,7 @@ import { Col, Row } from '@/components';
6
6
 
7
7
  # Icon
8
8
 
9
- The `Icon` component provides unified rendering of both custom SVG icons (Indicons) and FontAwesome icons.
9
+ The `Icon` component provides unified rendering of both custom SVG icons (Indicons) and Font Awesome icons.
10
10
 
11
11
  <Canvas
12
12
  of={IconStories.DefaultIcon}
@@ -31,17 +31,38 @@ You can use the `className` prop to apply custom styles to the icons.
31
31
 
32
32
  <Story of={IconStories.CustomStyledIcon} />
33
33
  ```tsx // .tsx
34
- <Icon name="fa-mountain-sun" className="color-primary-600" />
34
+ <Icon name="fa-mountain-sun" size="xl" className="color-primary-600" />
35
+ ```
36
+
37
+ ### Duotone Icons
38
+
39
+ Some icons are duotone, meaning they have two non-overlapping layers that can be styled differently. By default, the secondary (top) layer is differentiated by an opacity of 40% (while the primary layer has an opacity of 100%). To apply different styles to the primary and secondary layers, use the `--fa-primary-` and `--fa-secondary-` prefixes to set the `color` and `opacity` styles.
40
+
41
+ <Story of={IconStories.DuotoneIconStory} />
42
+ ```tsx // .tsx
43
+ <Icon
44
+ name="warning-stroke"
45
+ size="xl"
46
+ // in a real scenario, these should be set via CSS rather than inline
47
+ style={{
48
+ '--fa-primary-color': 'gold',
49
+ '--fa-secondary-color': 'black',
50
+ '--fa-secondary-opacity': '1.0',
51
+ }}
52
+ />
35
53
  ```
36
54
 
37
55
  ## Indicons (Custom Icons)
38
56
 
39
- Indicons are custom SVG icons created specifically for our design system. Below is a list of all available custom icons.
57
+ Indicons are custom SVG icons created specifically for our design system. The complete icon set is registered and available by default, unlike Font Awesome icons.
58
+
59
+ Below is a list of all available custom icons.
40
60
 
41
61
  <Story of={IconStories.Indicons} />
62
+
42
63
  ## Font Awesome Icons
43
64
 
44
- All FontAwesome icon names are prefixed with `fa-`. For a FontAwesome icon to be available, it must be registered globally via `registerFontAwesomeIcons`.
65
+ All Font Awesome icon names are prefixed with `fa-`. For a Font Awesome icon to be available, it must be registered globally via `registerFontAwesomeIcons`.
45
66
 
46
67
  For example:
47
68
 
@@ -66,7 +87,7 @@ import { registerFontAwesomeIcons } from '@indico-data/design-system';
66
87
  registerFontAwesomeIcons(...Object.values(fas));
67
88
  ```
68
89
 
69
- Below is a list of FontAwesome icons available in our design system. Use the search bar to find specific icons, and navigate
90
+ Below is a list of Font Awesome icons available in our design system. Use the search bar to find specific icons, and navigate
70
91
  through the pages to explore all icons.
71
92
 
72
93
  <Story of={IconStories.FontAwesomeIcons} />
@@ -5,8 +5,9 @@ import { IconName, IconProps, IconSizes } from './types';
5
5
  import { fas } from '@fortawesome/free-solid-svg-icons';
6
6
  import { registerFontAwesomeIcons } from '@/index';
7
7
  import { fontAwesomeIconNames, iconNames, indiconIconNames } from '@/storybook/iconNames';
8
+ import { indiconDefinitions } from './indicons';
8
9
 
9
- registerFontAwesomeIcons(...Object.values(fas));
10
+ registerFontAwesomeIcons(...Object.values(fas), ...indiconDefinitions);
10
11
 
11
12
  const meta: Meta = {
12
13
  title: 'Components/Icon',
@@ -105,7 +106,24 @@ export const IconStates: Story = {
105
106
 
106
107
  export const CustomStyledIcon: Story = {
107
108
  render: () => {
108
- return <Icon name="fa-mountain-sun" className="color-primary-600" />;
109
+ return <Icon name="fa-mountain-sun" size="xl" className="color-primary-600" />;
110
+ },
111
+ };
112
+
113
+ export const DuotoneIconStory: Story = {
114
+ render: () => {
115
+ return (
116
+ <Icon
117
+ name="warning-stroke"
118
+ size="xl"
119
+ style={{
120
+ // @ts-ignore
121
+ '--fa-primary-color': 'gold',
122
+ '--fa-secondary-color': 'black',
123
+ '--fa-secondary-opacity': '1.0',
124
+ }}
125
+ />
126
+ );
109
127
  },
110
128
  };
111
129
 
@@ -164,7 +182,7 @@ export const FontAwesomeIcons: Story = {
164
182
  const [searchQuery, setSearchQuery] = useState('');
165
183
  const iconsPerPage = 60;
166
184
 
167
- // Filter FontAwesome icons based on the search query
185
+ // Filter Font Awesome icons based on the search query
168
186
  const filteredIcons = fontAwesomeIconNames.filter((icon) =>
169
187
  icon.toLowerCase().includes(searchQuery.toLowerCase()),
170
188
  );
@@ -1,9 +1,7 @@
1
- import { ReactElement, cloneElement } from 'react';
2
1
  import classNames from 'classnames';
3
2
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
3
  import { IconName as FAIconName, findIconDefinition } from '@fortawesome/fontawesome-svg-core';
5
4
  import { IconProps } from './types';
6
- import { indicons } from './indicons';
7
5
 
8
6
  export const Icon = ({ name, size = 'md', className, ariaLabel, ...props }: IconProps) => {
9
7
  const label = ariaLabel || `${name} Icon`;
@@ -16,7 +14,6 @@ export const Icon = ({ name, size = 'md', className, ariaLabel, ...props }: Icon
16
14
  className,
17
15
  );
18
16
 
19
- const customIcon = indicons[name as keyof typeof indicons];
20
17
  const faIconName = name.split('fa-')[1];
21
18
  const faIcon =
22
19
  faIconName &&
@@ -24,21 +21,20 @@ export const Icon = ({ name, size = 'md', className, ariaLabel, ...props }: Icon
24
21
  prefix: 'fas',
25
22
  iconName: faIconName as FAIconName,
26
23
  });
24
+ const icon =
25
+ faIcon ||
26
+ findIconDefinition({
27
+ prefix: 'fak',
28
+ // @ts-ignore
29
+ iconName: name,
30
+ });
27
31
 
28
- if (!faIcon && !customIcon) {
32
+ if (!icon) {
29
33
  console.error(
30
- `Icon '${name}' not found. Did you remember to register it? See the [Permafrost Icon Documentation](https://ds.indico-dev.indico.io/iframe.html?viewMode=docs&id=components-icon--icon#font-awesome-icons) for more.`,
34
+ `Icon '${name}' not found. Is it entered correctly, and did you remember to register it? See the [Permafrost Icon Documentation](https://ds.indico-dev.indico.io/iframe.html?viewMode=docs&id=components-icon--icon#font-awesome-icons) for more.`,
31
35
  );
32
36
  return null;
33
37
  }
34
38
 
35
- return faIcon ? (
36
- <FontAwesomeIcon icon={faIcon} className={iconClasses} aria-label={label} {...props} />
37
- ) : (
38
- cloneElement(customIcon as ReactElement, {
39
- className: iconClasses,
40
- 'aria-label': label,
41
- ...props,
42
- })
43
- );
39
+ return <FontAwesomeIcon icon={icon} className={iconClasses} aria-label={label} {...props} />;
44
40
  };
@@ -3,48 +3,48 @@ import userEvent from '@testing-library/user-event';
3
3
  import { Icon } from '@/components/icons/Icon';
4
4
 
5
5
  describe('Icon Component', () => {
6
- describe('FontAwesome Icons', () => {
6
+ describe('Font Awesome Icons', () => {
7
7
  const faIconName = 'fa-arrow-down';
8
8
  const labelText = `${faIconName} Icon`;
9
9
 
10
- test('renders FontAwesome icon', () => {
10
+ test('renders Font Awesome icon', () => {
11
11
  render(<Icon name={faIconName} />);
12
12
  const icon = screen.getByLabelText(labelText);
13
13
  expect(icon).toBeInTheDocument();
14
14
  expect(icon).toHaveClass(faIconName);
15
15
  });
16
16
 
17
- test('applies size class to FontAwesome icon', () => {
17
+ test('applies size class to Font Awesome icon', () => {
18
18
  render(<Icon name={faIconName} size="lg" />);
19
19
  const icon = screen.getByLabelText(labelText);
20
20
  expect(icon).toHaveClass('icon--lg');
21
21
  });
22
22
 
23
- test('applies custom className to FontAwesome icon', () => {
23
+ test('applies custom className to Font Awesome icon', () => {
24
24
  render(<Icon name={faIconName} className="custom-class" />);
25
25
  const icon = screen.getByLabelText(labelText);
26
26
  expect(icon).toHaveClass('custom-class');
27
27
  });
28
28
 
29
- test('applies style to FontAwesome icon', () => {
29
+ test('applies style to Font Awesome icon', () => {
30
30
  render(<Icon name={faIconName} style={{ color: 'red' }} />);
31
31
  const icon = screen.getByLabelText(labelText);
32
32
  expect(icon).toHaveStyle('color: red');
33
33
  });
34
34
 
35
- test('uses ariaLabel prop for FontAwesome icon', () => {
35
+ test('uses ariaLabel prop for Font Awesome icon', () => {
36
36
  render(<Icon name={faIconName} ariaLabel="custom label" />);
37
37
  const icon = screen.getByLabelText('custom label');
38
38
  expect(icon).toBeInTheDocument();
39
39
  });
40
40
 
41
- test('applies id to FontAwesome icon', () => {
41
+ test('applies id to Font Awesome icon', () => {
42
42
  render(<Icon name={faIconName} id="test-id" />);
43
43
  const icon = screen.getByLabelText(labelText);
44
44
  expect(icon).toHaveAttribute('id', 'test-id');
45
45
  });
46
46
 
47
- test('handles onClick event for FontAwesome icon', async () => {
47
+ test('handles onClick event for Font Awesome icon', async () => {
48
48
  const handleClick = jest.fn();
49
49
  render(<Icon name={faIconName} onClick={handleClick} />);
50
50
  const icon = screen.getByLabelText(labelText);