-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7b7af19
commit 2fa81a4
Showing
5 changed files
with
840 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import React from 'react'; | ||
import { select, text, boolean, number, color } from '@storybook/addon-knobs'; | ||
import { mdiMessage, mdiSend } from '@mdi/js'; | ||
import { storiesOf } from '@storybook/react'; | ||
|
||
import Tag from './Tag'; | ||
import colors from '../../enums/colors'; | ||
import variants from '../../enums/variants'; | ||
|
||
const options = { | ||
none: '', | ||
mdiMessage, | ||
mdiSend, | ||
}; | ||
|
||
const design = { | ||
type: 'figma', | ||
url: 'https://www.figma.com/file/3r2G00brulOwr9j7F6JF59/Generic-UI-Style?node-id=0%3A1', | ||
}; | ||
|
||
const testId = `tag-${Math.floor(Math.random() * 100000)}`; | ||
const containerProps = { 'data-test-id': testId }; | ||
|
||
storiesOf('Tag', module).add( | ||
'Basic Tag', | ||
() => { | ||
return ( | ||
<Tag | ||
variant={select('variant', variants, variants.fill)} | ||
color={color('color', colors.primaryDark)} | ||
isLoading={boolean('isLoading', false)} | ||
elevation={number('elevation', 1)} | ||
isProcessing={boolean('isProcessing', false)} | ||
iconPrefix={select('iconPrefix', options, options.none)} | ||
iconSuffix={select('iconSuffix', options, options.none)} | ||
containerProps={containerProps} | ||
> | ||
{text('children', 'Default text')} | ||
</Tag> | ||
); | ||
}, | ||
{ design }, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import React, { ReactNode } from 'react'; | ||
import UnstyledIcon from '@mdi/react'; | ||
import { mdiLoading } from '@mdi/js'; | ||
import styled, { StyledComponentBase } from 'styled-components'; | ||
import timings from '../../enums/timings'; | ||
import { useTheme } from '../../context'; | ||
import variants from '../../enums/variants'; | ||
import Progress from '../Progress/Progress'; | ||
import { Div } from '../../htmlElements'; | ||
import { getFontColorFromVariant, getBackgroundColorFromVariant } from '../../utils/color'; | ||
import { SubcomponentPropsType } from '../commonTypes'; | ||
import { getShadowStyle } from '../../utils/styles'; | ||
|
||
export type TagContainerProps = { | ||
elevation: number; | ||
color: string; | ||
variant: variants; | ||
type: string; | ||
disabled: boolean; | ||
}; | ||
|
||
export type IconContainerProps = { | ||
hasContent: boolean; | ||
position: 'right' | 'left'; | ||
}; | ||
|
||
export type TagProps = { | ||
StyledContainer?: string & StyledComponentBase<any, {}>; | ||
containerProps?: SubcomponentPropsType; | ||
iconPrefix?: string | JSX.Element; | ||
iconSuffix?: string | JSX.Element; | ||
isLoading?: boolean; | ||
isProcessing?: boolean; | ||
children?: ReactNode; | ||
elevation?: number; | ||
variant?: variants; | ||
color?: string; | ||
StyledLoadingBar?: string & StyledComponentBase<any, {}>; | ||
loadingBarProps?: SubcomponentPropsType; | ||
StyledIconContainer?: string & StyledComponentBase<any, {}>; | ||
iconPrefixContainerProps?: SubcomponentPropsType; | ||
iconSuffixContainerProps?: SubcomponentPropsType; | ||
id?: string; | ||
}; | ||
|
||
export const Container: string & StyledComponentBase<any, {}, TagContainerProps> = styled(Div)` | ||
${({ elevation = 0, color, variant }: TagContainerProps) => { | ||
const { colors } = useTheme(); | ||
const backgroundColor = getBackgroundColorFromVariant(variant, color, colors.transparent); | ||
const fontColor = getFontColorFromVariant(variant, color, colors.background, colors.grayDark); | ||
return ` | ||
display: inline-flex; | ||
font-size: 1em; | ||
padding: .75em 1em; | ||
border-radius: 0.25em; | ||
transition: | ||
background-color ${timings.fast}, | ||
color ${timings.slow}, | ||
outline ${timings.slow}, | ||
filter ${timings.slow}, | ||
box-shadow ${timings.slow}; | ||
${getShadowStyle(elevation, colors.shadow)} | ||
outline: 0 none; | ||
border: ${variant === variants.outline ? `1px solid ${color || colors.grayDark}` : '0 none;'}; | ||
cursor: pointer; | ||
background-color: ${backgroundColor}; | ||
color: ${fontColor}; | ||
align-items: center; | ||
`; | ||
}} | ||
`; | ||
|
||
const StyledProgress = styled(Progress)` | ||
width: 5rem; | ||
height: 10px; | ||
margin-top: -5px; | ||
margin-bottom: -5px; | ||
`; | ||
|
||
const IconContainer = styled(Div)` | ||
${({ position, hasContent }: IconContainerProps) => { | ||
return ` | ||
height: 1rem; | ||
${hasContent ? `margin-${position === 'right' ? 'left' : 'right'}: 1em;` : ''} | ||
`; | ||
}} | ||
`; | ||
|
||
const Tag = ({ | ||
StyledContainer = Container, | ||
containerProps = {}, | ||
StyledIconContainer = IconContainer, | ||
iconPrefixContainerProps = {}, | ||
iconSuffixContainerProps = {}, | ||
StyledLoadingBar = StyledProgress, | ||
loadingBarProps = {}, | ||
iconPrefix, | ||
iconSuffix, | ||
isLoading, | ||
isProcessing, | ||
children, | ||
elevation = 0, | ||
variant = variants.fill, | ||
color, | ||
id, | ||
}: TagProps): JSX.Element => { | ||
const hasContent = Boolean(children); | ||
const { colors } = useTheme(); | ||
const containerColor = color || colors.grayLight; | ||
// get everything we expose + anything consumer wants to send to container | ||
const mergedContainerProps = { | ||
id, | ||
elevation, | ||
color: containerColor, | ||
variant, | ||
...containerProps, | ||
}; | ||
|
||
return isLoading ? ( | ||
<StyledContainer {...mergedContainerProps}> | ||
<StyledLoadingBar {...loadingBarProps} /> | ||
</StyledContainer> | ||
) : ( | ||
<StyledContainer {...mergedContainerProps}> | ||
{!isProcessing && | ||
iconPrefix && | ||
(typeof iconPrefix === 'string' && iconPrefix !== '' ? ( | ||
<StyledIconContainer | ||
hasContent={hasContent} | ||
position="left" | ||
{...iconPrefixContainerProps} | ||
> | ||
<UnstyledIcon path={iconPrefix} size="1rem" /> | ||
</StyledIconContainer> | ||
) : ( | ||
<StyledIconContainer>{iconPrefix}</StyledIconContainer> | ||
))} | ||
{isProcessing && ( | ||
<StyledIconContainer hasContent={hasContent} position="left" {...iconPrefixContainerProps}> | ||
<UnstyledIcon path={mdiLoading} size="1rem" spin={1} /> | ||
</StyledIconContainer> | ||
)} | ||
{children} | ||
|
||
{iconSuffix && | ||
(typeof iconSuffix === 'string' ? ( | ||
<StyledIconContainer | ||
hasContent={hasContent} | ||
position="right" | ||
{...iconSuffixContainerProps} | ||
> | ||
<UnstyledIcon path={iconSuffix} size="1rem" /> | ||
</StyledIconContainer> | ||
) : ( | ||
<StyledIconContainer | ||
hasContent={hasContent} | ||
position="right" | ||
{...iconSuffixContainerProps} | ||
> | ||
{iconSuffix} | ||
</StyledIconContainer> | ||
))} | ||
</StyledContainer> | ||
); | ||
}; | ||
|
||
Tag.Container = Container; | ||
Tag.LoadingBar = StyledProgress; | ||
Tag.IconContainer = IconContainer; | ||
export default Tag; |
146 changes: 146 additions & 0 deletions
146
packages/hs-react-ui/src/components/Tag/__tests__/Tag.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import React from 'react'; | ||
import { render, waitFor, configure } from '@testing-library/react'; | ||
import Icon from '@mdi/react'; | ||
import colors from '../../../enums/colors'; | ||
import variants from '../../../enums/variants'; | ||
import Tag from '../Tag'; | ||
import { mdiComment } from '@mdi/js'; | ||
|
||
configure({ testIdAttribute: 'data-test-id' }); | ||
|
||
describe('Tag', () => { | ||
it('shows loading text when provided', async () => { | ||
const testId = 'tag-1'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag isLoading={true} containerProps={containerProps}> | ||
Shows Loading | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows Tag with non-default props variant and color', async () => { | ||
const testId = 'tag-2'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag | ||
color={colors.black} | ||
elevation={2} | ||
variant={variants.outline} | ||
containerProps={containerProps} | ||
> | ||
Variant and color | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows Tag with text variant', async () => { | ||
const testId = 'tag-3'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag variant={variants.text} containerProps={containerProps}> | ||
text variant | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows Tag with fill variant', async () => { | ||
const testId = 'tag-4'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag variant={variants.fill} containerProps={containerProps}> | ||
fill variant | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows Tag with outline variant', async () => { | ||
const testId = 'tag-5'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag variant={variants.outline} containerProps={containerProps}> | ||
outline variant | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows LeftIconContainer when isProcessing', async () => { | ||
const testId = 'tag-6'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag isProcessing containerProps={containerProps}> | ||
processing | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows icons with type string', async () => { | ||
const testId = 'tag-7'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag iconSuffix={mdiComment} iconPrefix={mdiComment} containerProps={containerProps}> | ||
string icon props | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('shows icons', async () => { | ||
const testId = 'tag-8'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { container, getByTestId } = render( | ||
<Tag | ||
iconSuffix={<Icon path={mdiComment} />} | ||
iconPrefix={<Icon path={mdiComment} />} | ||
containerProps={containerProps} | ||
> | ||
shows icons | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('keeps the container the same when switching between isLoading and not isLoading', async () => { | ||
const testId = 'tag-9'; | ||
const containerProps = { 'data-test-id': testId }; | ||
const { getByTestId, rerender, asFragment } = render( | ||
<Tag isLoading={true} containerProps={containerProps}> | ||
Hello World! | ||
</Tag>, | ||
); | ||
|
||
await waitFor(() => getByTestId(testId)); | ||
const loadingFragment = asFragment(); | ||
|
||
rerender(<Tag containerProps={containerProps}>Hello World!</Tag>); | ||
await waitFor(() => getByTestId(testId)); | ||
const loadedFragment = asFragment(); | ||
|
||
// TODO: Use toMatchDiffSnapshot() between the fragments once we can figure out | ||
// how to make it use the jest-styled-components plugin | ||
expect(loadingFragment.firstChild).toMatchSnapshot(); | ||
expect(loadedFragment.firstChild).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.