@edvisor/product-language 0.2.0 → 0.3.0
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/.eslintrc.json +14 -6
- package/package.json +2 -3
- package/project.json +18 -7
- package/src/README.md +61 -0
- package/src/helpers/index.ts +2 -1
- package/src/helpers/playground.ts +16 -0
- package/src/lib/components/badge/badge.tsx +8 -17
- package/src/lib/components/badge/stories/badge.stories.mdx +44 -0
- package/src/lib/components/badge/stories/components.tsx +49 -0
- package/src/lib/components/card/card.test.tsx +4 -5
- package/src/lib/components/card/card.tsx +2 -2
- package/src/lib/components/card/components/card-section-slot.tsx +2 -2
- package/src/lib/components/card/storybook/card.stories.mdx +1 -1
- package/src/lib/components/card/storybook/components.tsx +6 -23
- package/src/lib/components/checkbox/checkbox.tsx +34 -95
- package/src/lib/components/checkbox/helpers.tsx +100 -0
- package/src/lib/components/checkbox/stories/checkbox.stories.mdx +27 -24
- package/src/lib/components/checkbox/stories/components.tsx +63 -15
- package/src/lib/components/divider/stories/divider.stories.mdx +7 -13
- package/src/lib/components/flag/flag-size-flags.tsx +55 -0
- package/src/lib/components/flag/flag.list.tsx +788 -0
- package/src/lib/components/flag/flag.test.tsx +65 -0
- package/src/lib/components/flag/flag.tsx +97 -0
- package/src/lib/components/flag/index.tsx +1 -0
- package/src/lib/components/flag/stories/components.tsx +403 -0
- package/src/lib/components/flag/stories/flag.stories.mdx +48 -0
- package/src/lib/components/flag/stories/playGround-select.tsx +145 -0
- package/src/lib/components/icon/icon-list.tsx +135 -0
- package/src/lib/components/icon/icon.test.tsx +48 -0
- package/src/lib/components/icon/icon.tsx +181 -0
- package/src/lib/components/icon/index.tsx +1 -0
- package/src/lib/components/icon/stories/components.tsx +282 -0
- package/src/lib/components/icon/stories/icon.stories.mdx +65 -0
- package/src/lib/components/index.ts +5 -0
- package/src/lib/components/input-field/components/labeled-input.tsx +7 -14
- package/src/lib/components/input-field/components/stepper.tsx +4 -3
- package/src/lib/components/input-field/input-field.test.tsx +5 -6
- package/src/lib/components/input-field/input-field.tsx +8 -8
- package/src/lib/components/input-field/storybook/components.tsx +9 -16
- package/src/lib/components/link/storybook/link.stories.mdx +1 -0
- package/src/lib/components/molecules/avatar/avatar-size-flags.tsx +3 -3
- package/src/lib/components/molecules/avatar/avatar.tsx +2 -2
- package/src/lib/components/molecules/avatar/stories/avatar.stories.mdx +18 -21
- package/src/lib/components/molecules/button/button-flags.tsx +120 -15
- package/src/lib/components/molecules/button/button.test.tsx +9 -9
- package/src/lib/components/molecules/button/button.tsx +61 -78
- package/src/lib/components/molecules/button/stories/button.stories.mdx +43 -42
- package/src/lib/components/molecules/button/stories/components.tsx +6 -8
- package/src/lib/components/molecules/input-checkbox/input-checkbox.tsx +23 -24
- package/src/lib/components/molecules/input-checkbox/stories/components.tsx +32 -15
- package/src/lib/components/molecules/input-checkbox/stories/input-checkbox.stories.mdx +6 -8
- package/src/lib/components/organisms/multi-choice-list/multi-choice-list.tsx +7 -8
- package/src/lib/components/organisms/multi-choice-list/stories/components.tsx +3 -5
- package/src/lib/components/organisms/multi-choice-list/stories/multi-choice-list.stories.mdx +4 -4
- package/src/lib/components/spinner/spinner.test.tsx +2 -2
- package/src/lib/components/spinner/spinner.tsx +15 -28
- package/src/lib/components/spinner/stories/components.tsx +33 -2
- package/src/lib/components/spinner/stories/spinner.stories.mdx +3 -10
- package/src/lib/components/tabs/components/index.ts +1 -0
- package/src/lib/components/tabs/components/tab.tsx +62 -0
- package/src/lib/components/tabs/index.tsx +1 -0
- package/src/lib/components/tabs/storybook/components.tsx +282 -0
- package/src/lib/components/tabs/storybook/tabs.stories.mdx +97 -0
- package/src/lib/components/tabs/tabs.test.tsx +86 -0
- package/src/lib/components/tabs/tabs.tsx +101 -0
- package/src/lib/components/tag/components/close-button.tsx +85 -0
- package/src/lib/components/tag/components/index.ts +2 -0
- package/src/lib/components/tag/components/tag-label.tsx +44 -0
- package/src/lib/components/tag/index.tsx +1 -0
- package/src/lib/components/tag/stories/components.tsx +86 -0
- package/src/lib/components/tag/stories/tag.stories.mdx +42 -0
- package/src/lib/components/tag/tag.test.tsx +36 -0
- package/src/lib/components/tag/tag.tsx +33 -0
- package/src/lib/components/thumbnail/thumbnail.tsx +7 -2
- package/src/lib/components/typography/storybook/components.tsx +47 -15
- package/src/lib/components/typography/storybook/typography.stories.mdx +6 -4
- package/src/lib/components/typography/typography.test.tsx +34 -30
- package/src/lib/components/typography/typography.tsx +61 -19
- package/src/lib/foundations/color-system/base-palette/base-palette.ts +0 -1
- package/src/lib/foundations/color-system/color-guidelines/color-guidelines.ts +5 -4
- package/src/lib/foundations/color-system/components/color-sample.tsx +3 -3
- package/src/lib/foundations/typography/fonts.ts +205 -0
- package/src/lib/foundations/typography/text-aspect-flags.ts +11 -4
- package/src/lib/foundations/typography/typography.tsx +38 -33
- package/src/lib/helpers/numbers.ts +14 -0
- package/src/lib/helpers/safe-navigation.ts +10 -0
- package/src/lib/helpers/slots.test.tsx +98 -0
- package/src/lib/helpers/slots.tsx +93 -12
- package/.storybook/preview-head.html +0 -1
- package/src/lib/components/badge/badge.stories.tsx +0 -16
- package/src/lib/components/checkbox/components/components.tsx +0 -59
- package/src/lib/components/checkbox/stories/index.tsx +0 -1
- package/src/lib/components/molecules/input-checkbox/stories/index.tsx +0 -1
- package/src/lib/components/organisms/multi-choice-list/stories/index.tsx +0 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { Margin } from '@foundations'
|
|
3
|
+
import { render, screen } from '@testing-library/react'
|
|
4
|
+
import userEvent from '@testing-library/user-event'
|
|
5
|
+
import { Tabs, Tab } from './index'
|
|
6
|
+
|
|
7
|
+
describe('Tabs', () => {
|
|
8
|
+
it('renders both tabs and the currently selected content', async () => {
|
|
9
|
+
render(
|
|
10
|
+
<Tabs selected={0}>
|
|
11
|
+
<Tabs.TabList>
|
|
12
|
+
<Tab>One</Tab>
|
|
13
|
+
<Tab>Two</Tab>
|
|
14
|
+
</Tabs.TabList>
|
|
15
|
+
<Tabs.Content>ONE</Tabs.Content>
|
|
16
|
+
<Tabs.Content>TWO</Tabs.Content>
|
|
17
|
+
</Tabs>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
expect(screen.getAllByRole('tab')).toHaveLength(2)
|
|
21
|
+
expect(screen.getAllByRole('tabpanel')).toHaveLength(1)
|
|
22
|
+
expect(screen.getByRole('tabpanel')).toHaveTextContent('ONE')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('does not render the unselected tabs', async () => {
|
|
26
|
+
render(
|
|
27
|
+
<Tabs selected={2}>
|
|
28
|
+
<Tabs.TabList>
|
|
29
|
+
<Tab>One</Tab>
|
|
30
|
+
<Tab>Two</Tab>
|
|
31
|
+
<Tab>Three</Tab>
|
|
32
|
+
</Tabs.TabList>
|
|
33
|
+
<Tabs.Content>ONE</Tabs.Content>
|
|
34
|
+
<Tabs.Content>TWO</Tabs.Content>
|
|
35
|
+
<Tabs.Content>THREE</Tabs.Content>
|
|
36
|
+
</Tabs>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(screen.getAllByRole('tab')).toHaveLength(3)
|
|
40
|
+
expect(screen.getAllByRole('tabpanel', { hidden: true })).toHaveLength(3)
|
|
41
|
+
|
|
42
|
+
expect(screen.getByText('ONE')).toHaveAttribute('aria-hidden', 'true')
|
|
43
|
+
expect(screen.getByText('TWO')).toHaveAttribute('aria-hidden', 'true')
|
|
44
|
+
expect(screen.getByText('ONE')).not.toBeVisible()
|
|
45
|
+
expect(screen.getByText('TWO')).not.toBeVisible()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* this test just ensures that the programmer did not
|
|
50
|
+
* forget to add className? to the props and then pass
|
|
51
|
+
* it to the wrapping element so that users downstream
|
|
52
|
+
* can style the component
|
|
53
|
+
*/
|
|
54
|
+
it('accepts styles', () => {
|
|
55
|
+
const NiceTab = styled(Tab)`
|
|
56
|
+
margin-bottom: ${Margin.l};
|
|
57
|
+
`
|
|
58
|
+
|
|
59
|
+
const { container } = render(<NiceTab>Contents</NiceTab>)
|
|
60
|
+
|
|
61
|
+
expect(container.firstChild).toHaveStyle(`margin-bottom: ${Margin.l}`)
|
|
62
|
+
expect(screen.getByText('Contents')).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('calls onChange when a tab is clicked', async () => {
|
|
66
|
+
const handleChange = jest.fn()
|
|
67
|
+
|
|
68
|
+
render(
|
|
69
|
+
<Tabs selected={0} onChange={handleChange}>
|
|
70
|
+
<Tabs.TabList>
|
|
71
|
+
<Tab>One</Tab>
|
|
72
|
+
<Tab>Two</Tab>
|
|
73
|
+
</Tabs.TabList>
|
|
74
|
+
<Tabs.Content>ONE</Tabs.Content>
|
|
75
|
+
<Tabs.Content>TWO</Tabs.Content>
|
|
76
|
+
</Tabs>
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
await userEvent.click(screen.getByText('Two'))
|
|
80
|
+
|
|
81
|
+
expect(handleChange).toHaveBeenCalledTimes(1)
|
|
82
|
+
|
|
83
|
+
expect(screen.getByText('ONE')).toBeVisible()
|
|
84
|
+
expect(screen.getByText('TWO')).not.toBeVisible()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Children, ReactElement } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
|
|
4
|
+
import { Borders, Margin, Padding } from '@foundations'
|
|
5
|
+
import { applyProps, applyStyle, FC, is, isReactElementOfType, PropsWithChildren, safeCallback, Slot, StylableSlot } from '@helpers'
|
|
6
|
+
|
|
7
|
+
import { Flex } from 'components/layout'
|
|
8
|
+
import { ITabProps, Tab as TabBase } from './components'
|
|
9
|
+
|
|
10
|
+
const Tab = styled(TabBase)``
|
|
11
|
+
|
|
12
|
+
const TabList = styled(Flex)`
|
|
13
|
+
padding-left: ${Padding.m};
|
|
14
|
+
border-bottom: 1px solid ${Borders.Default.Subdued};
|
|
15
|
+
|
|
16
|
+
${Tab} {
|
|
17
|
+
margin-right: ${Margin.m};
|
|
18
|
+
margin-bottom: -1px;
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
class Content extends StylableSlot {}
|
|
24
|
+
class Navigation extends Slot {}
|
|
25
|
+
|
|
26
|
+
type SubComponents = {
|
|
27
|
+
Content: typeof Content
|
|
28
|
+
TabList: typeof Navigation
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ITabsProps extends PropsWithChildren {
|
|
32
|
+
selected: number
|
|
33
|
+
onChange?: (selected: number) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isTab = isReactElementOfType(Tab)
|
|
37
|
+
const isTabList = isReactElementOfType(Navigation)
|
|
38
|
+
const isContent = isReactElementOfType(Content)
|
|
39
|
+
|
|
40
|
+
export const Tabs: FC<ITabsProps, SubComponents> = (props) => {
|
|
41
|
+
/** iterate over the children, wiring up the tabs and contents */
|
|
42
|
+
let contentCount = 0
|
|
43
|
+
let tabCount = 0
|
|
44
|
+
const children = Children.map(props.children, (child) => {
|
|
45
|
+
if (isContent(child)) {
|
|
46
|
+
return decorateContent(child, props, contentCount++)
|
|
47
|
+
} if (isTabList(child)) {
|
|
48
|
+
const tabs = Children.map(child.props.children, (tab) => {
|
|
49
|
+
return isTab(tab) ? decorateTab(tab, props, tabCount++) : tab
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return <TabList>{tabs}</TabList>
|
|
53
|
+
} else {
|
|
54
|
+
return child
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return <>{children}</>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function decorateTab(
|
|
62
|
+
tab: ReactElement<ITabProps>,
|
|
63
|
+
{ selected, onChange = () => undefined }: ITabsProps,
|
|
64
|
+
index: number
|
|
65
|
+
) {
|
|
66
|
+
return (
|
|
67
|
+
<Tab
|
|
68
|
+
{...tab.props}
|
|
69
|
+
selected={
|
|
70
|
+
is(tab.props.selected) ? tab.props.selected : index === selected
|
|
71
|
+
}
|
|
72
|
+
onFocus={(e) => {
|
|
73
|
+
safeCallback(tab.props.onFocus, e)
|
|
74
|
+
|
|
75
|
+
if (index !== selected) {
|
|
76
|
+
onChange(index)
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{tab.props.children}
|
|
81
|
+
</Tab>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** content could be anything, but it needs to at least show and hide appropriately */
|
|
86
|
+
function decorateContent (child: ReactElement, props: ITabsProps, index: number) {
|
|
87
|
+
return applyProps(
|
|
88
|
+
applyStyle(child, {
|
|
89
|
+
display: index === props.selected ? 'inherit' : 'none',
|
|
90
|
+
}),
|
|
91
|
+
{
|
|
92
|
+
role: 'tabpanel',
|
|
93
|
+
'aria-hidden': !(index === props.selected),
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Tabs.Content = Content
|
|
99
|
+
Tabs.TabList = Navigation
|
|
100
|
+
|
|
101
|
+
export { Tab } from './components'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//close-button.tsx
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { Flex } from 'components/layout'
|
|
4
|
+
import { Surface, Padding, Icons } from '@foundations'
|
|
5
|
+
import { FC, is, isDefined, PropsWithChildren } from '@helpers'
|
|
6
|
+
import { HTMLAttributes } from 'react'
|
|
7
|
+
|
|
8
|
+
const ClosableWrapper = styled(Flex)<{
|
|
9
|
+
disabled: boolean
|
|
10
|
+
}>`
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
padding: ${Padding.xxxs} ${Padding.xxs};
|
|
15
|
+
gap: 8px;
|
|
16
|
+
width: 28px;
|
|
17
|
+
|
|
18
|
+
background: ${(props) =>
|
|
19
|
+
props.disabled ? Surface.Neutral.Subdued : Surface.Neutral.Default};
|
|
20
|
+
border-radius: 0px 4px 4px 0px;
|
|
21
|
+
|
|
22
|
+
${(props) =>
|
|
23
|
+
!props.disabled
|
|
24
|
+
? `
|
|
25
|
+
&:hover {
|
|
26
|
+
background: ${Surface.Neutral.Hover};
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
|
|
29
|
+
svg {
|
|
30
|
+
path {
|
|
31
|
+
fill: ${Icons.Hover}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:active {
|
|
37
|
+
background: ${Surface.Neutral.Pressed};
|
|
38
|
+
|
|
39
|
+
svg {
|
|
40
|
+
path {
|
|
41
|
+
fill: ${Icons.Pressed};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
: `
|
|
47
|
+
svg {
|
|
48
|
+
path {
|
|
49
|
+
fill: ${Icons.Disabled};
|
|
50
|
+
}
|
|
51
|
+
}`}
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
const ClosableIcon = styled.svg`
|
|
55
|
+
path {
|
|
56
|
+
fill: ${Icons.Default};
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
interface IProps extends HTMLAttributes<HTMLDivElement>, PropsWithChildren {
|
|
61
|
+
disabled?: boolean
|
|
62
|
+
onClick?: () => void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const CloseButton: FC<IProps> = (props) => {
|
|
66
|
+
const disabled = is(props.disabled)
|
|
67
|
+
|
|
68
|
+
const onClickHandler = () => {
|
|
69
|
+
if (!disabled && isDefined(props.onClick)) {
|
|
70
|
+
props.onClick()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
<ClosableWrapper disabled={disabled} onClick={onClickHandler} role="button">
|
|
75
|
+
<ClosableIcon
|
|
76
|
+
width="12.5"
|
|
77
|
+
height="12.5"
|
|
78
|
+
viewBox="0 0 16 16"
|
|
79
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
80
|
+
>
|
|
81
|
+
<path d="M8.00451 6.79303L14.5439 0.251124C14.8798 -0.0837079 15.423 -0.0837079 15.7267 0.251124C16.0912 0.585955 16.0912 1.12912 15.7267 1.46395L9.21591 8.00442L15.7267 14.5438C16.0912 14.8797 16.0912 15.4229 15.7267 15.7266C15.423 16.0911 14.8798 16.0911 14.5439 15.7266L8.00451 9.21582L1.46404 15.7266C1.12921 16.0911 0.586045 16.0911 0.251177 15.7266C-0.0837258 15.4229 -0.0837258 14.8797 0.251177 14.5438L6.79312 8.00442L0.251177 1.46395C-0.0837258 1.12912 -0.0837258 0.585955 0.251177 0.251124C0.586045 -0.0837079 1.12921 -0.0837079 1.46404 0.251124L8.00451 6.79303Z" />
|
|
82
|
+
</ClosableIcon>
|
|
83
|
+
</ClosableWrapper>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { Flex } from 'components/layout'
|
|
3
|
+
import { Surface, Padding, Text } from '@foundations'
|
|
4
|
+
import { Label } from 'components/typography'
|
|
5
|
+
import { FC, is, PropsWithChildren } from '@helpers'
|
|
6
|
+
import { HTMLAttributes } from 'react'
|
|
7
|
+
|
|
8
|
+
const LabelFrame = styled(Flex)<{
|
|
9
|
+
disabled: boolean
|
|
10
|
+
closable: boolean
|
|
11
|
+
}>`
|
|
12
|
+
background: ${(props) =>
|
|
13
|
+
props.disabled ? Surface.Neutral.Subdued : Surface.Neutral.Default};
|
|
14
|
+
flex-direction: row;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
padding: ${Padding.xxxs} ${Padding.xs} ${Padding.xxxs} ${Padding.xs};
|
|
19
|
+
width: 100%;
|
|
20
|
+
min-height: 24px;
|
|
21
|
+
border-radius: ${(props) => (props.closable ? '4px 0px 0px 4px' : '4px')};
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
const LabelText = styled(Label)<{
|
|
25
|
+
disabled: boolean
|
|
26
|
+
}>`
|
|
27
|
+
color: ${(props) => (props.disabled ? Text.Light : Text.Default)};
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
interface IProps extends HTMLAttributes<HTMLLabelElement>, PropsWithChildren {
|
|
31
|
+
closable?: boolean
|
|
32
|
+
disabled?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const TagLabel: FC<IProps> = (props) => {
|
|
36
|
+
const disabled = is(props.disabled)
|
|
37
|
+
const closable = is(props.closable)
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<LabelFrame disabled={disabled} closable={closable}>
|
|
41
|
+
<LabelText disabled={disabled}>{props.children}</LabelText>
|
|
42
|
+
</LabelFrame>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tag'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Playground } from 'storybook-addon-jarle-monaco'
|
|
2
|
+
import { Canvas } from '@storybook/addon-docs'
|
|
3
|
+
import { Tag } from '../tag'
|
|
4
|
+
import { Card } from 'components/card'
|
|
5
|
+
import styled from 'styled-components'
|
|
6
|
+
import { Margin, Padding, Surface } from '@foundations'
|
|
7
|
+
|
|
8
|
+
const WrapperStories = styled(Card)`
|
|
9
|
+
width: 240px;
|
|
10
|
+
height: 238px;
|
|
11
|
+
padding: ${Padding.l};
|
|
12
|
+
background: ${Surface.Default.Default};
|
|
13
|
+
`
|
|
14
|
+
const TagMarginBottom24 = styled(Tag)`
|
|
15
|
+
margin-bottom: ${Margin.l};
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const onClickHandler = () => {
|
|
19
|
+
alert('Close button clicked')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SelectedTagHTMLExample = () => {
|
|
23
|
+
return (
|
|
24
|
+
<WrapperStories heading="Selected (2)">
|
|
25
|
+
<TagMarginBottom24 closable onClick={onClickHandler}>Toronto, ON, Canada</TagMarginBottom24>
|
|
26
|
+
<TagMarginBottom24 closable onClick={onClickHandler}>Melbourne NSW, Australia</TagMarginBottom24>
|
|
27
|
+
</WrapperStories>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const TagExample = () => (
|
|
32
|
+
<Playground
|
|
33
|
+
code={`
|
|
34
|
+
/* Edit this code sample! */
|
|
35
|
+
<div style={{display:'flex', gap:'24px'}}>
|
|
36
|
+
<Tag >Label</Tag>
|
|
37
|
+
<Tag disabled >Label</Tag>
|
|
38
|
+
<Tag closable onClick={onClickHandler}>Label</Tag>
|
|
39
|
+
<Tag closable disabled>Label</Tag>
|
|
40
|
+
</div>
|
|
41
|
+
`}
|
|
42
|
+
providerProps={{
|
|
43
|
+
renderAsComponent: true,
|
|
44
|
+
scope: {
|
|
45
|
+
Tag,
|
|
46
|
+
Canvas,
|
|
47
|
+
onClickHandler
|
|
48
|
+
},
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
export const WorkedExample = () => (
|
|
54
|
+
<Playground
|
|
55
|
+
code={`
|
|
56
|
+
const [selectedCities, setSelected] = useState([
|
|
57
|
+
{ id: 1, name: 'Vancouver, BC', disabled: true },
|
|
58
|
+
{ id: 2, name: 'Montreal, QC'},
|
|
59
|
+
{ id: 3, name: 'Toronto, ON'},
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
<div style={{display:'flex', gap:'24px'}}>
|
|
63
|
+
{selectedCities.map((city, index) => {
|
|
64
|
+
return (
|
|
65
|
+
<Tag
|
|
66
|
+
closable
|
|
67
|
+
disabled={city.disabled}
|
|
68
|
+
key={city.id}
|
|
69
|
+
onClick={() => {
|
|
70
|
+
setSelected( prevCities => prevCities.filter( filteredCity => filteredCity.id !== city.id ) )
|
|
71
|
+
}}
|
|
72
|
+
>{city.name}</Tag>
|
|
73
|
+
)
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
`}
|
|
77
|
+
providerProps={{
|
|
78
|
+
renderAsComponent: true,
|
|
79
|
+
scope: {
|
|
80
|
+
Tag,
|
|
81
|
+
Canvas,
|
|
82
|
+
onClickHandler
|
|
83
|
+
},
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs'
|
|
2
|
+
import { Tag } from '../index'
|
|
3
|
+
import { TagExample, SelectedTagHTMLExample, WorkedExample } from './components'
|
|
4
|
+
|
|
5
|
+
<Meta title="Components/Tag" component={Tag} />
|
|
6
|
+
|
|
7
|
+
# Tag
|
|
8
|
+
|
|
9
|
+
For more details, check out the component page on [Figma](https://www.figma.com/file/ue1CurHfZ426o2T2l8Dk64/Edvisor-Product-Language?node-id=1545%3A4278)
|
|
10
|
+
|
|
11
|
+
## How to use
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
// Import the component
|
|
15
|
+
import { Tag } from '@edvisor/product-language'
|
|
16
|
+
|
|
17
|
+
// Render the default component
|
|
18
|
+
<Tag>Label</Tag>
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Properties
|
|
23
|
+
|
|
24
|
+
The available optional properties are as follows.
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Description |
|
|
27
|
+
| ------------ | ---------- | ---------------------------------------------------- |
|
|
28
|
+
| `closable?` | `boolean` | Add a close button to the tag |
|
|
29
|
+
| `disabled?` | `boolean` | Disabled the tag |
|
|
30
|
+
| `className?` | `string` | Add the class to the tag |
|
|
31
|
+
| `onClick?` | `function` | Called when the close button is clicked |
|
|
32
|
+
|
|
33
|
+
## Example
|
|
34
|
+
|
|
35
|
+
<SelectedTagHTMLExample />
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Playground
|
|
39
|
+
|
|
40
|
+
Tags
|
|
41
|
+
|
|
42
|
+
<WorkedExample />
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//tag.test
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { Tag } from './index'
|
|
5
|
+
|
|
6
|
+
describe('Tag Tests', () => {
|
|
7
|
+
it('should render Tag with given Text', async () => {
|
|
8
|
+
render(<Tag>Test</Tag>)
|
|
9
|
+
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
10
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should render a disabled Tag with the close button and with given Text', async () => {
|
|
14
|
+
render(
|
|
15
|
+
<Tag disabled closable>
|
|
16
|
+
Close
|
|
17
|
+
</Tag>
|
|
18
|
+
)
|
|
19
|
+
expect(screen.getByText('Close')).toBeInTheDocument()
|
|
20
|
+
expect(screen.queryByRole('button')).toBeInTheDocument()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should render the Tag with close button and handle event after click', async () => {
|
|
24
|
+
const spyOnClick = jest.fn()
|
|
25
|
+
render(
|
|
26
|
+
<Tag closable onClick={spyOnClick}>
|
|
27
|
+
Clicked
|
|
28
|
+
</Tag>
|
|
29
|
+
)
|
|
30
|
+
expect(screen.getByText('Clicked')).toBeInTheDocument()
|
|
31
|
+
|
|
32
|
+
const closeIcon = screen.queryByRole('button') as HTMLElement
|
|
33
|
+
await userEvent.click(closeIcon)
|
|
34
|
+
expect(spyOnClick).toHaveBeenCalled()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FC, PropsWithChildren, is } from '@helpers'
|
|
2
|
+
import { Padding } from '@foundations'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import { Flex } from 'components/layout'
|
|
5
|
+
import { CloseButton, TagLabel } from './components'
|
|
6
|
+
import { HTMLAttributes } from 'react'
|
|
7
|
+
|
|
8
|
+
const TagFrame = styled(Flex)`
|
|
9
|
+
flex-direction: row;
|
|
10
|
+
align-items: stretch;
|
|
11
|
+
padding: ${Padding.none};
|
|
12
|
+
width: fit-content;
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
interface IProps extends HTMLAttributes<HTMLDivElement>, PropsWithChildren {
|
|
16
|
+
closable?: boolean
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
className?: string
|
|
19
|
+
onClick?: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Tag: FC<IProps> = (props) => {
|
|
23
|
+
const { disabled, closable, onClick, ...domProps } = props
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<TagFrame {...domProps} className={props.className}>
|
|
27
|
+
<TagLabel disabled={disabled} closable={closable}>
|
|
28
|
+
{props.children}
|
|
29
|
+
</TagLabel>
|
|
30
|
+
{is(closable) && <CloseButton {...props} />}
|
|
31
|
+
</TagFrame>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -21,11 +21,12 @@ const Image = styled.img`
|
|
|
21
21
|
interface IThumbnailProps {
|
|
22
22
|
imageUrl?: string;
|
|
23
23
|
imageLabel?: string;
|
|
24
|
+
className?: string;
|
|
24
25
|
}
|
|
25
26
|
type IProps = IThumbnailProps & ThumbnailSizeProps
|
|
26
27
|
|
|
27
28
|
export const Thumbnail: FC<IProps> = (props) => {
|
|
28
|
-
const { imageUrl, imageLabel = imageUrl } = props
|
|
29
|
+
const { imageUrl, imageLabel = imageUrl, className = '' } = props
|
|
29
30
|
const size = getValuesBySize(props)
|
|
30
31
|
|
|
31
32
|
if (isNil(imageUrl)) {
|
|
@@ -33,7 +34,11 @@ export const Thumbnail: FC<IProps> = (props) => {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
return (
|
|
36
|
-
<Box
|
|
37
|
+
<Box
|
|
38
|
+
size={size}
|
|
39
|
+
data-testid="thumbnail-test"
|
|
40
|
+
className={className}
|
|
41
|
+
>
|
|
37
42
|
<Image src={imageUrl} alt={imageLabel}/>
|
|
38
43
|
</Box>
|
|
39
44
|
)
|
|
@@ -8,11 +8,10 @@ import {
|
|
|
8
8
|
Heading2,
|
|
9
9
|
Heading3,
|
|
10
10
|
Heading4,
|
|
11
|
-
BodyLarge,
|
|
12
11
|
Body,
|
|
12
|
+
BodyLarge,
|
|
13
13
|
Caption,
|
|
14
|
-
|
|
15
|
-
Label2,
|
|
14
|
+
Label,
|
|
16
15
|
} from '../typography'
|
|
17
16
|
import { Typography } from '@foundations'
|
|
18
17
|
import { Flex } from 'components/layout'
|
|
@@ -82,11 +81,11 @@ export const Button = styled.div<{ critical?: boolean, primary?: boolean }>`
|
|
|
82
81
|
|
|
83
82
|
const Header = () => (
|
|
84
83
|
<Row>
|
|
85
|
-
<HeaderColumn><
|
|
86
|
-
<HeaderColumn><
|
|
87
|
-
<HeaderColumn><
|
|
88
|
-
<HeaderColumn><
|
|
89
|
-
<HeaderColumn><
|
|
84
|
+
<HeaderColumn><Label subdued>Typographic System</Label></HeaderColumn>
|
|
85
|
+
<HeaderColumn><Label subdued>Weight</Label></HeaderColumn>
|
|
86
|
+
<HeaderColumn><Label subdued>Size</Label></HeaderColumn>
|
|
87
|
+
<HeaderColumn><Label subdued>Line Height</Label></HeaderColumn>
|
|
88
|
+
<HeaderColumn><Label subdued>Spacing</Label></HeaderColumn>
|
|
90
89
|
</Row>
|
|
91
90
|
)
|
|
92
91
|
|
|
@@ -198,10 +197,10 @@ export const TypographyTable: StoryComponent = () => {
|
|
|
198
197
|
<Row gray>
|
|
199
198
|
<Column>
|
|
200
199
|
<Button {...toButtonProps(aspect)}>
|
|
201
|
-
<BodyLarge {...toTextAspectProps(aspect)}>BodyLarge</BodyLarge>
|
|
200
|
+
<BodyLarge strong {...toTextAspectProps(aspect)}>BodyLarge/Strong</BodyLarge>
|
|
202
201
|
</Button>
|
|
203
202
|
</Column>
|
|
204
|
-
<Column>
|
|
203
|
+
<Column>Semibold 600</Column>
|
|
205
204
|
<Column>18px (1.125rem)</Column>
|
|
206
205
|
<Column>28px</Column>
|
|
207
206
|
<Column>0 px</Column>
|
|
@@ -209,10 +208,32 @@ export const TypographyTable: StoryComponent = () => {
|
|
|
209
208
|
<Row gray>
|
|
210
209
|
<Column>
|
|
211
210
|
<Button {...toButtonProps(aspect)}>
|
|
212
|
-
<
|
|
211
|
+
<BodyLarge {...toTextAspectProps(aspect)}>BodyLarge/Default</BodyLarge>
|
|
213
212
|
</Button>
|
|
214
213
|
</Column>
|
|
215
|
-
<Column>
|
|
214
|
+
<Column>Regular 400</Column>
|
|
215
|
+
<Column>18px (1.125rem)</Column>
|
|
216
|
+
<Column>28px</Column>
|
|
217
|
+
<Column>0 px</Column>
|
|
218
|
+
</Row>
|
|
219
|
+
<Row gray>
|
|
220
|
+
<Column>
|
|
221
|
+
<Button {...toButtonProps(aspect)}>
|
|
222
|
+
<Body strong {...toTextAspectProps(aspect)}>Body Strong</Body>
|
|
223
|
+
</Button>
|
|
224
|
+
</Column>
|
|
225
|
+
<Column>Medium 500</Column>
|
|
226
|
+
<Column>14px (0.875rem)</Column>
|
|
227
|
+
<Column>20px</Column>
|
|
228
|
+
<Column>0 px</Column>
|
|
229
|
+
</Row>
|
|
230
|
+
<Row gray>
|
|
231
|
+
<Column>
|
|
232
|
+
<Button {...toButtonProps(aspect)}>
|
|
233
|
+
<Body {...toTextAspectProps(aspect)}>Body default</Body>
|
|
234
|
+
</Button>
|
|
235
|
+
</Column>
|
|
236
|
+
<Column>Regular 400</Column>
|
|
216
237
|
<Column>14px (0.875rem)</Column>
|
|
217
238
|
<Column>20px</Column>
|
|
218
239
|
<Column>0 px</Column>
|
|
@@ -231,10 +252,21 @@ export const TypographyTable: StoryComponent = () => {
|
|
|
231
252
|
<Row>
|
|
232
253
|
<Column>
|
|
233
254
|
<Button {...toButtonProps(aspect)}>
|
|
234
|
-
<
|
|
255
|
+
<Label strong {...toTextAspectProps(aspect)}>Label/Strong</Label>
|
|
256
|
+
</Button>
|
|
257
|
+
</Column>
|
|
258
|
+
<Column>Medium 500</Column>
|
|
259
|
+
<Column>14px (0.875rem)</Column>
|
|
260
|
+
<Column>20px</Column>
|
|
261
|
+
<Column>0 px</Column>
|
|
262
|
+
</Row>
|
|
263
|
+
<Row>
|
|
264
|
+
<Column>
|
|
265
|
+
<Button {...toButtonProps(aspect)}>
|
|
266
|
+
<Label {...toTextAspectProps(aspect)}>Label/Default</Label>
|
|
235
267
|
</Button>
|
|
236
268
|
</Column>
|
|
237
|
-
<Column>
|
|
269
|
+
<Column>Regular 400</Column>
|
|
238
270
|
<Column>14px (0.875rem)</Column>
|
|
239
271
|
<Column>20px</Column>
|
|
240
272
|
<Column>0 px</Column>
|
|
@@ -242,7 +274,7 @@ export const TypographyTable: StoryComponent = () => {
|
|
|
242
274
|
<Row>
|
|
243
275
|
<Column>
|
|
244
276
|
<Button {...toButtonProps(aspect)}>
|
|
245
|
-
<
|
|
277
|
+
<Label subtle {...toTextAspectProps(aspect)}>Label/Subtle</Label>
|
|
246
278
|
</Button>
|
|
247
279
|
</Column>
|
|
248
280
|
<Column>Regular 400</Column>
|