Adding `BaseIcon` UI component (#15609)

* Adding BaseIcon component

* Updates to styles, docs and proptypes

* Updating box props link
feature/default_network_editable
George Marshall 2 years ago committed by GitHub
parent 6e0f130168
commit fc23cff03c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 77
      ui/components/component-library/base-icon/README.mdx
  2. 56
      ui/components/component-library/base-icon/base-icon.js
  3. 33
      ui/components/component-library/base-icon/base-icon.scss
  4. 176
      ui/components/component-library/base-icon/base-icon.stories.js
  5. 64
      ui/components/component-library/base-icon/base-icon.test.js
  6. 1
      ui/components/component-library/base-icon/index.js
  7. 1
      ui/components/component-library/component-library-components.scss
  8. 5
      ui/components/ui/box/box.js
  9. 19
      ui/helpers/constants/design-system.js

@ -0,0 +1,77 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { BaseIcon } from './base-icon';
### This is a base component. It should not be used in your feature code directly but as a "base" for other UI components
# BaseIcon
The `BaseIcon` is the base component for all icons. It is used in conjunction with a script to create all icons it should not be used directly.
<Canvas>
<Story id="ui-components-component-library-base-icon-base-icon-stories-js--default-story" />
</Canvas>
## Props
The `BaseIcon` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props
<ArgsTable of={BaseIcon} />
### Size
Use the `size` prop and the `SIZES` object from `./ui/helpers/constants/design-system.js` to change the size of `BaseIcon`. Defaults to `SIZES.SM`
Possible sizes include:
- `SIZES.XXS` 10px
- `SIZES.XS` 12px
- `SIZES.SM` 16px
- `SIZES.MD` 20px
- `SIZES.LG` 24px
- `SIZES.XL` 32px
<Canvas>
<Story id="ui-components-component-library-base-icon-base-icon-stories-js--size" />
</Canvas>
```jsx
import { SIZES } from '../../../helpers/constants/design-system';
import { BaseIcon } from '../ui/component-library';
<BaseIcon size={SIZES.XXS} />
<BaseIcon size={SIZES.XS} />
<BaseIcon size={SIZES.SM} />
<BaseIcon size={SIZES.MD} />
<BaseIcon size={SIZES.LG} />
<BaseIcon size={SIZES.XL} />
```
### Color
Use the `color` prop and the `COLORS` object from `./ui/helpers/constants/design-system.js` to change the color of `BaseIcon`. Defaults to `COLORS.INHERIT` which will use the text color of the parent element. This is useful for inline icons.
<Canvas>
<Story id="ui-components-component-library-base-icon-base-icon-stories-js--color" />
</Canvas>
```jsx
import { COLORS } from '../../../helpers/constants/design-system';
import { BaseIcon } from '../ui/component-library';
<BaseIcon color={COLORS.INHERIT} />
<BaseIcon color={COLORS.ICON_DEFAULT} />
<BaseIcon color={COLORS.ICON_ALTERNATIVE} />
<BaseIcon color={COLORS.ICON_MUTED} />
<BaseIcon color={COLORS.OVERLAY_INVERSE} />
<BaseIcon color={COLORS.PRIMARY_DEFAULT} />
<BaseIcon color={COLORS.PRIMARY_INVERSE} />
<BaseIcon color={COLORS.ERROR_DEFAULT} />
<BaseIcon color={COLORS.ERROR_INVERSE} />
<BaseIcon color={COLORS.SUCCESS_DEFAULT} />
<BaseIcon color={COLORS.SUCCESS_INVERSE} />
<BaseIcon color={COLORS.WARNING_DEFAULT} />
<BaseIcon color={COLORS.WARNING_INVERSE} />
<BaseIcon color={COLORS.INFO_DEFAULT} />
<BaseIcon color={COLORS.INFO_INVERSE} />
```

@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Box from '../../ui/box/box';
import {
SIZES,
COLORS,
ICON_COLORS,
} from '../../../helpers/constants/design-system';
export const BaseIcon = ({
className,
size = SIZES.MD,
color = COLORS.INHERIT,
children,
...props
}) => {
return (
<Box
as="svg"
viewBox="0 0 512 512"
className={classnames(className, 'base-icon', `base-icon--size-${size}`)}
color={color}
{...props}
>
{children}
</Box>
);
};
BaseIcon.propTypes = {
/**
* The size of the BaseIcon.
* Possible values could be 'SIZES.XXS', 'SIZES.XS', 'SIZES.SM', 'SIZES.MD', 'SIZES.LG', 'SIZES.XL',
* Default value is 'SIZES.MD'.
*/
size: PropTypes.oneOf(Object.values(SIZES)),
/**
* The color of the icon.
* Defaults to COLORS.INHERIT.
*/
color: PropTypes.oneOf(Object.values(ICON_COLORS)),
/**
* An additional className to apply to the icon.
*/
className: PropTypes.string,
/**
* The <path> to the icon.
*/
children: PropTypes.node,
/**
* BaseIcon accepts all the props from Box
*/
...Box.propTypes,
};

@ -0,0 +1,33 @@
.base-icon {
--icon-size: var(--size, 16px);
&--size-xxs {
--size: 10px;
}
&--size-xs {
--size: 12px;
}
&--size-sm {
--size: 16px;
}
&--size-md {
--size: 20px;
}
&--size-lg {
--size: 24px;
}
&--size-xl {
--size: 32px;
}
font-size: var(--icon-size);
width: 1em;
height: 1em;
display: inline-block;
fill: currentColor;
}

@ -0,0 +1,176 @@
import React from 'react';
import {
SIZES,
ALIGN_ITEMS,
DISPLAY,
COLORS,
ICON_COLORS,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
import { BaseIcon } from './base-icon';
import README from './README.mdx';
const marginSizeControlOptions = [
undefined,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
'auto',
];
export default {
title: 'Components/ComponentLibrary/BaseIcon',
id: __filename,
component: BaseIcon,
parameters: {
docs: {
page: README,
},
},
argTypes: {
size: {
control: 'select',
options: Object.values(SIZES),
},
color: {
control: 'select',
options: Object.values(ICON_COLORS),
},
className: {
control: 'text',
},
marginTop: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginRight: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginBottom: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginLeft: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
},
args: {
color: COLORS.INHERIT,
size: SIZES.MD,
children: (
<path d="M259 16l204 112 3 3c1 1 2 2 2 3 1 1 1 3 1 4 1 1 1 2 1 3l-2 224c0 5-3 10-7 13L261 496c-3 1-5 2-8 2h-1c-3 0-5-1-8-2L49 380c-5-2-7-7-7-12V141c0-5 2-10 7-13 1 0 1-1 2-1L245 16c5-2 10-2 14 0zm182 150l-175 97 1 195 172-101zm-370 0v190l2 2c0 1 1 2 1 3l163 97V261zm331 170c3-5 9-7 14-4l6 3c5 3 6 9 4 14-2 4-6 6-9 6-2 0-4-1-5-2l-6-3c-5-3-7-9-4-14zm-305-4c5-3 12-1 14 4 3 4 2 11-3 14l-6 3c-2 1-3 1-5 1-4 0-7-2-9-5-3-5-1-11 4-14zm265-18c3-5 9-7 14-4l6 3c5 3 7 9 4 14-2 3-6 5-9 5-2 0-4 0-5-1l-6-3c-5-3-7-9-4-14zm-226-5c5-3 11-1 14 4s1 11-4 14l-5 4c-2 0-4 1-5 1-4 0-7-2-9-5-3-5-1-12 3-14zm186-18c3-5 9-7 14-4l6 3c5 3 7 10 4 14-2 4-5 6-9 6-2 0-4-1-5-2l-6-3c-5-3-7-9-4-14zm-147-4c5-3 11-1 14 4s1 11-4 14l-5 3c-2 1-4 2-6 2-3 0-7-2-9-6-2-5-1-11 4-14zm107-18c3-5 10-7 14-4l6 3c5 3 7 9 4 14-2 3-5 5-9 5-2 0-3 0-5-1l-6-3c-5-3-6-9-4-14zm-69-4c5-3 12-2 15 3 2 5 1 12-4 15l-6 3c-1 1-3 1-5 1-3 0-7-2-9-5-3-5-1-11 4-14zm39-220L87 140l166 95 173-94zm0 150c6 0 11 4 11 10v6c0 6-5 11-11 11-5 0-10-5-10-11v-6c0-6 5-10 10-10zm0-45c6 0 11 4 11 10v6c0 6-5 11-11 11-5 0-10-5-10-11v-6c0-6 5-10 10-10zm0-45c6 0 11 4 11 10v7c0 5-5 10-11 10-5 0-10-5-10-10v-7c0-6 5-10 10-10zm0-45c6 0 11 5 11 10v7c0 5-5 10-11 10-5 0-10-5-10-10v-7c0-5 5-10 10-10z" />
),
},
};
export const DefaultStory = (args) => <BaseIcon {...args} />;
DefaultStory.storyName = 'Default';
export const Size = (args) => (
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.BASELINE} gap={1}>
<BaseIcon {...args} size={SIZES.XXS} />
<BaseIcon {...args} size={SIZES.XS} />
<BaseIcon {...args} size={SIZES.SM} />
<BaseIcon {...args} size={SIZES.MD} />
<BaseIcon {...args} size={SIZES.LG} />
<BaseIcon {...args} size={SIZES.XL} />
</Box>
);
export const Color = (args) => (
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.BASELINE}>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.INHERIT} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.ICON_DEFAULT} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.ICON_ALTERNATIVE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.ICON_MUTED} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.OVERLAY_DEFAULT}
>
<BaseIcon {...args} color={COLORS.OVERLAY_INVERSE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.PRIMARY_DEFAULT} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.PRIMARY_DEFAULT}
>
<BaseIcon {...args} color={COLORS.PRIMARY_INVERSE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.ERROR_DEFAULT} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.ERROR_DEFAULT}
>
<BaseIcon {...args} color={COLORS.ERROR_INVERSE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.SUCCESS_DEFAULT} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.SUCCESS_DEFAULT}
>
<BaseIcon {...args} color={COLORS.SUCCESS_INVERSE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.WARNING_DEFAULT} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.WARNING_DEFAULT}
>
<BaseIcon {...args} color={COLORS.WARNING_INVERSE} />
</Box>
<Box padding={1} display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
<BaseIcon {...args} color={COLORS.INFO_DEFAULT} />
</Box>
<Box
padding={1}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
backgroundColor={COLORS.INFO_DEFAULT}
>
<BaseIcon {...args} color={COLORS.INFO_INVERSE} />
</Box>
</Box>
);

@ -0,0 +1,64 @@
/* eslint-disable jest/require-top-level-describe */
import { render } from '@testing-library/react';
import React from 'react';
import { SIZES, COLORS } from '../../../helpers/constants/design-system';
import { BaseIcon } from './base-icon';
describe('BaseIcon', () => {
it('should render correctly', () => {
const { getByTestId, container } = render(
<BaseIcon data-testid="base-icon" />,
);
expect(getByTestId('base-icon')).toBeDefined();
expect(container.querySelector('svg')).toBeDefined();
});
it('should render with different size classes', () => {
const { getByTestId } = render(
<>
<BaseIcon size={SIZES.XXS} data-testid="base-icon-xxs" />
<BaseIcon size={SIZES.XS} data-testid="base-icon-xs" />
<BaseIcon size={SIZES.SM} data-testid="base-icon-sm" />
<BaseIcon size={SIZES.MD} data-testid="base-icon-md" />
<BaseIcon size={SIZES.LG} data-testid="base-icon-lg" />
<BaseIcon size={SIZES.XL} data-testid="base-icon-xl" />
</>,
);
expect(getByTestId('base-icon-xxs')).toHaveClass('base-icon--size-xxs');
expect(getByTestId('base-icon-xs')).toHaveClass('base-icon--size-xs');
expect(getByTestId('base-icon-sm')).toHaveClass('base-icon--size-sm');
expect(getByTestId('base-icon-md')).toHaveClass('base-icon--size-md');
expect(getByTestId('base-icon-lg')).toHaveClass('base-icon--size-lg');
expect(getByTestId('base-icon-xl')).toHaveClass('base-icon--size-xl');
});
it('should render with icon colors', () => {
const { getByTestId } = render(
<>
<BaseIcon data-testid="base-icon-color-inherit" />
<BaseIcon
color={COLORS.ICON_DEFAULT}
data-testid="base-icon-color-default"
/>
<BaseIcon
color={COLORS.ICON_ALTERNATIVE}
data-testid="base-icon-color-alternative"
/>
<BaseIcon
color={COLORS.ICON_MUTED}
data-testid="base-icon-color-muted"
/>
</>,
);
expect(getByTestId('base-icon-color-inherit')).toHaveClass(
'box--color-inherit',
);
expect(getByTestId('base-icon-color-default')).toHaveClass(
'box--color-icon-default',
);
expect(getByTestId('base-icon-color-alternative')).toHaveClass(
'box--color-icon-alternative',
);
expect(getByTestId('base-icon-color-muted')).toHaveClass(
'box--color-icon-muted',
);
});
});

@ -0,0 +1 @@
export { BaseIcon } from './base-icon';

@ -1,2 +1,3 @@
/** Please import your files in alphabetical order **/
@import 'base-avatar/base-avatar';
@import 'base-icon/base-icon';

@ -9,6 +9,7 @@ import {
BACKGROUND_COLORS,
BORDER_COLORS,
TEXT_COLORS,
ICON_COLORS,
DISPLAY,
JUSTIFY_CONTENT,
SIZES,
@ -28,6 +29,7 @@ export const ValidBackgroundColors = PropTypes.oneOf(
);
export const ValidBorderColors = PropTypes.oneOf(Object.values(BORDER_COLORS));
export const ValidTextColors = PropTypes.oneOf(Object.values(TEXT_COLORS));
export const ValidIconColors = PropTypes.oneOf(Object.values(ICON_COLORS));
const ArrayOfValidSizes = PropTypes.arrayOf(ValidSize);
export const MultipleSizes = PropTypes.oneOfType([
@ -54,9 +56,12 @@ export const MultipleBackgroundColors = PropTypes.oneOfType([
]);
const ArrayOfValidTextColors = PropTypes.arrayOf(ValidTextColors);
const ArrayOfValidIconColors = PropTypes.arrayOf(ValidIconColors);
export const MultipleTextColors = PropTypes.oneOfType([
ValidTextColors,
ArrayOfValidTextColors,
ValidIconColors,
ArrayOfValidIconColors,
]);
function isValidSize(type, value) {

@ -126,6 +126,25 @@ export const TEXT_COLORS = pick(COLORS, [
'INFO_INVERSE',
'INHERIT',
]);
export const ICON_COLORS = pick(COLORS, [
'ICON_DEFAULT',
'ICON_ALTERNATIVE',
'ICON_MUTED',
'OVERLAY_INVERSE',
'PRIMARY_DEFAULT',
'PRIMARY_INVERSE',
'ERROR_DEFAULT',
'ERROR_INVERSE',
'SUCCESS_DEFAULT',
'SUCCESS_INVERSE',
'WARNING_DEFAULT',
'WARNING_INVERSE',
'INFO_DEFAULT',
'INFO_INVERSE',
'INHERIT',
]);
export const TYPOGRAPHY = {
H1: 'h1',
H2: 'h2',

Loading…
Cancel
Save