diff --git a/ui/components/component-library/base-icon/README.mdx b/ui/components/component-library/base-icon/README.mdx new file mode 100644 index 000000000..519ebb745 --- /dev/null +++ b/ui/components/component-library/base-icon/README.mdx @@ -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. + + + + + +## 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 + + + +### 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 + + + + + +```jsx +import { SIZES } from '../../../helpers/constants/design-system'; +import { BaseIcon } from '../ui/component-library'; + + + + + + + +``` + +### 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. + + + + + +```jsx +import { COLORS } from '../../../helpers/constants/design-system'; +import { BaseIcon } from '../ui/component-library'; + + + + + + + + + + + + + + + + +``` diff --git a/ui/components/component-library/base-icon/base-icon.js b/ui/components/component-library/base-icon/base-icon.js new file mode 100644 index 000000000..966371410 --- /dev/null +++ b/ui/components/component-library/base-icon/base-icon.js @@ -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 ( + + {children} + + ); +}; + +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 to the icon. + */ + children: PropTypes.node, + /** + * BaseIcon accepts all the props from Box + */ + ...Box.propTypes, +}; diff --git a/ui/components/component-library/base-icon/base-icon.scss b/ui/components/component-library/base-icon/base-icon.scss new file mode 100644 index 000000000..fd81cab65 --- /dev/null +++ b/ui/components/component-library/base-icon/base-icon.scss @@ -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; +} diff --git a/ui/components/component-library/base-icon/base-icon.stories.js b/ui/components/component-library/base-icon/base-icon.stories.js new file mode 100644 index 000000000..89c7ce245 --- /dev/null +++ b/ui/components/component-library/base-icon/base-icon.stories.js @@ -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: ( + + ), + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; + +export const Size = (args) => ( + + + + + + + + +); + +export const Color = (args) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/ui/components/component-library/base-icon/base-icon.test.js b/ui/components/component-library/base-icon/base-icon.test.js new file mode 100644 index 000000000..6ecda4ec7 --- /dev/null +++ b/ui/components/component-library/base-icon/base-icon.test.js @@ -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( + , + ); + expect(getByTestId('base-icon')).toBeDefined(); + expect(container.querySelector('svg')).toBeDefined(); + }); + it('should render with different size classes', () => { + const { getByTestId } = render( + <> + + + + + + + , + ); + 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( + <> + + + + + , + ); + 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', + ); + }); +}); diff --git a/ui/components/component-library/base-icon/index.js b/ui/components/component-library/base-icon/index.js new file mode 100644 index 000000000..f1ceeadd2 --- /dev/null +++ b/ui/components/component-library/base-icon/index.js @@ -0,0 +1 @@ +export { BaseIcon } from './base-icon'; diff --git a/ui/components/component-library/component-library-components.scss b/ui/components/component-library/component-library-components.scss index b1e580564..22a8192c2 100644 --- a/ui/components/component-library/component-library-components.scss +++ b/ui/components/component-library/component-library-components.scss @@ -1,2 +1,3 @@ /** Please import your files in alphabetical order **/ @import 'base-avatar/base-avatar'; +@import 'base-icon/base-icon'; diff --git a/ui/components/ui/box/box.js b/ui/components/ui/box/box.js index 61bd7e39a..716247f9a 100644 --- a/ui/components/ui/box/box.js +++ b/ui/components/ui/box/box.js @@ -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) { diff --git a/ui/helpers/constants/design-system.js b/ui/helpers/constants/design-system.js index c5fff3da7..699f8bbe4 100644 --- a/ui/helpers/constants/design-system.js +++ b/ui/helpers/constants/design-system.js @@ -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',