@livechat/design-system-react-components 1.0.0-alpha.2 → 1.0.0-alpha.4

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.
Files changed (271) hide show
  1. package/.eslintrc +9 -0
  2. package/.storybook/main.js +20 -0
  3. package/.storybook/preview-head.html +31 -0
  4. package/.storybook/preview.js +21 -0
  5. package/package.json +40 -37
  6. package/src/components/Alert/Alert.module.scss +88 -0
  7. package/src/components/Alert/Alert.spec.tsx +78 -0
  8. package/src/components/Alert/Alert.stories.tsx +73 -0
  9. package/src/components/Alert/Alert.tsx +94 -0
  10. package/src/components/Alert/index.ts +1 -0
  11. package/src/components/Badge/Badge.module.scss +23 -0
  12. package/src/components/Badge/Badge.spec.tsx +12 -0
  13. package/src/components/Badge/Badge.stories.tsx +24 -0
  14. package/src/components/Badge/Badge.tsx +24 -0
  15. package/src/components/Badge/index.ts +1 -0
  16. package/src/components/Button/Button.module.scss +257 -0
  17. package/src/components/Button/Button.spec.tsx +110 -0
  18. package/src/components/Button/Button.stories.tsx +152 -0
  19. package/src/components/Button/Button.tsx +88 -0
  20. package/src/components/Button/index.ts +2 -0
  21. package/src/components/ButtonGroup/ButtonGroup.module.scss +31 -0
  22. package/src/components/ButtonGroup/ButtonGroup.spec.tsx +61 -0
  23. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +53 -0
  24. package/src/components/ButtonGroup/ButtonGroup.tsx +75 -0
  25. package/src/components/ButtonGroup/index.ts +1 -0
  26. package/src/components/Card/Card.module.scss +31 -0
  27. package/src/components/Card/Card.spec.tsx +38 -0
  28. package/src/components/Card/Card.stories.tsx +30 -0
  29. package/src/components/Card/Card.tsx +34 -0
  30. package/src/components/Card/index.ts +2 -0
  31. package/src/components/Checkbox/Checkbox.module.scss +99 -0
  32. package/src/components/Checkbox/Checkbox.tsx +39 -0
  33. package/src/components/Checkbox/CheckboxField.spec.tsx +58 -0
  34. package/src/components/Checkbox/CheckboxField.stories.tsx +30 -0
  35. package/src/components/Checkbox/CheckboxField.tsx +46 -0
  36. package/src/components/Checkbox/index.ts +2 -0
  37. package/src/components/FieldDescription/FieldDescription.module.scss +5 -0
  38. package/src/components/FieldDescription/FieldDescription.tsx +23 -0
  39. package/src/components/FieldDescription/index.ts +1 -0
  40. package/src/components/FieldError/FieldError.module.scss +5 -0
  41. package/src/components/FieldError/FieldError.tsx +23 -0
  42. package/src/components/FieldError/index.ts +1 -0
  43. package/src/components/FieldGroup/FieldGroup.module.scss +25 -0
  44. package/src/components/FieldGroup/FieldGroup.spec.tsx +42 -0
  45. package/src/components/FieldGroup/FieldGroup.stories.tsx +34 -0
  46. package/src/components/FieldGroup/FieldGroup.tsx +38 -0
  47. package/src/components/FieldGroup/index.ts +1 -0
  48. package/src/components/Form/Form.module.scss +22 -0
  49. package/src/components/Form/Form.spec.tsx +38 -0
  50. package/src/components/Form/Form.stories.tsx +42 -0
  51. package/src/components/Form/Form.tsx +46 -0
  52. package/src/components/Form/index.ts +2 -0
  53. package/src/components/FormGroup/FormGroup.module.scss +20 -0
  54. package/src/components/FormGroup/FormGroup.spec.tsx +34 -0
  55. package/src/components/FormGroup/FormGroup.stories.tsx +30 -0
  56. package/src/components/FormGroup/FormGroup.tsx +44 -0
  57. package/src/components/FormGroup/index.ts +1 -0
  58. package/src/components/Icon/Icon.module.scss +70 -0
  59. package/src/components/Icon/Icon.stories.tsx +53 -0
  60. package/src/components/Icon/Icon.tsx +118 -0
  61. package/src/components/Icon/index.ts +1 -0
  62. package/src/components/InputField/InputField.module.scss +37 -0
  63. package/src/components/InputField/InputField.spec.tsx +53 -0
  64. package/src/components/InputField/InputField.stories.tsx +73 -0
  65. package/src/components/InputField/InputField.tsx +72 -0
  66. package/src/components/InputField/index.ts +1 -0
  67. package/src/components/Link/Link.module.scss +18 -0
  68. package/src/components/Link/Link.stories.tsx +17 -0
  69. package/src/components/Link/Link.tsx +37 -0
  70. package/src/components/Link/index.ts +1 -0
  71. package/src/components/Loader/Loader.module.scss +66 -0
  72. package/src/components/Loader/Loader.spec.tsx +64 -0
  73. package/src/components/Loader/Loader.stories.tsx +25 -0
  74. package/src/components/Loader/Loader.tsx +48 -0
  75. package/src/components/Loader/index.ts +1 -0
  76. package/src/components/Modal/ActionModal.tsx +37 -0
  77. package/src/components/Modal/Modal.module.scss +131 -0
  78. package/src/components/Modal/Modal.stories.tsx +85 -0
  79. package/src/components/Modal/Modal.tsx +41 -0
  80. package/src/components/Modal/ModalBase.spec.tsx +58 -0
  81. package/src/components/Modal/ModalBase.tsx +66 -0
  82. package/src/components/Modal/ModalCloseButton.tsx +20 -0
  83. package/src/components/Modal/ModalPortal.tsx +35 -0
  84. package/src/components/Modal/index.ts +11 -0
  85. package/src/components/NumericInput/NumericInput.module.scss +110 -0
  86. package/src/components/NumericInput/NumericInput.spec.tsx +129 -0
  87. package/src/components/NumericInput/NumericInput.stories.tsx +56 -0
  88. package/src/components/NumericInput/NumericInput.tsx +161 -0
  89. package/src/components/NumericInput/NumericInputField.stories.tsx +58 -0
  90. package/src/components/NumericInput/NumericInputField.tsx +57 -0
  91. package/src/components/NumericInput/index.ts +2 -0
  92. package/src/components/Popover/Popover.module.scss +16 -0
  93. package/src/components/Popover/Popover.spec.tsx +39 -0
  94. package/src/components/Popover/Popover.stories.css +23 -0
  95. package/src/components/Popover/Popover.stories.tsx +164 -0
  96. package/src/components/Popover/Popover.tsx +124 -0
  97. package/src/components/Popover/index.ts +1 -0
  98. package/src/components/Progress/ProgressBar.module.scss +48 -0
  99. package/src/components/Progress/ProgressBar.spec.tsx +33 -0
  100. package/src/components/Progress/ProgressBar.stories.tsx +23 -0
  101. package/src/components/Progress/ProgressBar.tsx +62 -0
  102. package/src/components/Progress/ProgressCircle.module.scss +64 -0
  103. package/src/components/Progress/ProgressCircle.spec.tsx +34 -0
  104. package/src/components/Progress/ProgressCircle.stories.tsx +28 -0
  105. package/src/components/Progress/ProgressCircle.tsx +104 -0
  106. package/src/components/Progress/constants.ts +23 -0
  107. package/src/components/Progress/helpers.ts +23 -0
  108. package/src/components/Progress/index.ts +1 -0
  109. package/src/components/PromoBanner/PromoBanner.module.scss +121 -0
  110. package/src/components/PromoBanner/PromoBanner.spec.tsx +119 -0
  111. package/src/components/PromoBanner/PromoBanner.stories.tsx +125 -0
  112. package/src/components/PromoBanner/PromoBanner.tsx +99 -0
  113. package/src/components/PromoBanner/index.ts +1 -0
  114. package/src/components/RadioButton/RadioButton.module.scss +100 -0
  115. package/src/components/RadioButton/RadioButton.spec.tsx +70 -0
  116. package/src/components/RadioButton/RadioButton.stories.tsx +34 -0
  117. package/src/components/RadioButton/RadioButton.tsx +54 -0
  118. package/src/components/RadioButton/index.ts +1 -0
  119. package/src/components/Switch/Switch.module.scss +126 -0
  120. package/src/components/Switch/Switch.spec.tsx +40 -0
  121. package/src/components/Switch/Switch.stories.tsx +23 -0
  122. package/src/components/Switch/Switch.tsx +100 -0
  123. package/src/components/Switch/index.ts +1 -0
  124. package/src/components/Tab/Tab.module.scss +56 -0
  125. package/src/components/Tab/Tab.spec.tsx +34 -0
  126. package/src/components/Tab/Tab.stories.tsx +20 -0
  127. package/src/components/Tab/Tab.tsx +49 -0
  128. package/src/components/Tab/TabsWrapper.module.scss +11 -0
  129. package/src/components/Tab/TabsWrapper.spec.tsx +19 -0
  130. package/src/components/Tab/TabsWrapper.stories.tsx +46 -0
  131. package/src/components/Tab/TabsWrapper.tsx +24 -0
  132. package/src/components/Tab/index.ts +2 -0
  133. package/src/components/Tag/Tag.module.scss +131 -0
  134. package/src/components/Tag/Tag.spec.tsx +93 -0
  135. package/src/components/Tag/Tag.stories.tsx +151 -0
  136. package/src/components/Tag/Tag.tsx +129 -0
  137. package/src/components/Tag/index.ts +1 -0
  138. package/src/components/TagInput/EditableTag.tsx +54 -0
  139. package/src/components/TagInput/EditableTagContent.tsx +81 -0
  140. package/src/components/TagInput/EmailTagInput.tsx +31 -0
  141. package/src/components/TagInput/TagInput.module.scss +81 -0
  142. package/src/components/TagInput/TagInput.spec.tsx +58 -0
  143. package/src/components/TagInput/TagInput.stories.tsx +49 -0
  144. package/src/components/TagInput/TagInput.tsx +133 -0
  145. package/src/components/TagInput/index.ts +4 -0
  146. package/src/components/TextField/TextField.module.scss +90 -0
  147. package/src/components/TextField/TextField.spec.tsx +40 -0
  148. package/src/components/TextField/TextField.stories.tsx +60 -0
  149. package/src/components/TextField/TextField.tsx +100 -0
  150. package/src/components/TextField/index.ts +1 -0
  151. package/src/components/Toast/Toast.module.scss +218 -0
  152. package/src/components/Toast/Toast.spec.tsx +96 -0
  153. package/src/components/Toast/Toast.stories.tsx +64 -0
  154. package/src/components/Toast/Toast.tsx +121 -0
  155. package/src/components/Toast/ToastWrapper.spec.tsx +13 -0
  156. package/src/components/Toast/ToastWrapper.stories.tsx +55 -0
  157. package/src/components/Toast/ToastWrapper.tsx +87 -0
  158. package/src/components/Toast/index.ts +2 -0
  159. package/src/components/Typography/Heading.tsx +36 -0
  160. package/src/components/Typography/Text.tsx +49 -0
  161. package/src/components/Typography/Typography.module.scss +132 -0
  162. package/src/components/Typography/index.ts +2 -0
  163. package/src/index.scss +9 -0
  164. package/src/index.ts +32 -0
  165. package/src/stories/ColorTokens.stories.mdx +10 -0
  166. package/src/stories/Icons.stories.mdx +12 -0
  167. package/src/stories/Typography.stories.mdx +65 -0
  168. package/src/stories/components/ColorTokens.tsx +99 -0
  169. package/src/stories/components/IconsShowcase.tsx +25 -0
  170. package/src/stories/components/iconsShowcase.css +13 -0
  171. package/src/test/setup.ts +1 -0
  172. package/{dist/test-utils.d.ts → src/test/utils.ts} +3 -2
  173. package/src/themes/dark.scss +127 -0
  174. package/src/themes/designTokens.ts +126 -0
  175. package/src/themes/legacy.scss +128 -0
  176. package/src/themes/light.scss +127 -0
  177. package/src/utils/keyCodes.ts +12 -0
  178. package/src/utils/noop.ts +2 -0
  179. package/src/vite-env.d.ts +1 -0
  180. package/tsconfig.json +25 -0
  181. package/tsconfig.node.json +8 -0
  182. package/vite.config.ts +44 -0
  183. package/dist/components/Alert.d.ts +0 -19
  184. package/dist/components/Alert.spec.d.ts +0 -1
  185. package/dist/components/Badge.d.ts +0 -6
  186. package/dist/components/Badge.spec.d.ts +0 -1
  187. package/dist/components/Button.d.ts +0 -13
  188. package/dist/components/Button.spec.d.ts +0 -1
  189. package/dist/components/ButtonGroup.d.ts +0 -11
  190. package/dist/components/ButtonGroup.spec.d.ts +0 -1
  191. package/dist/components/Card.d.ts +0 -6
  192. package/dist/components/Card.spec.d.ts +0 -1
  193. package/dist/components/Checkbox.d.ts +0 -7
  194. package/dist/components/CheckboxField.d.ts +0 -7
  195. package/dist/components/CheckboxField.spec.d.ts +0 -1
  196. package/dist/components/FieldDescription.d.ts +0 -3
  197. package/dist/components/FieldError.d.ts +0 -3
  198. package/dist/components/FieldGroup.d.ts +0 -8
  199. package/dist/components/FieldGroup.spec.d.ts +0 -1
  200. package/dist/components/FieldLabel.d.ts +0 -3
  201. package/dist/components/Form.d.ts +0 -7
  202. package/dist/components/Form.spec.d.ts +0 -1
  203. package/dist/components/FormGroup.d.ts +0 -7
  204. package/dist/components/FormGroup.spec.d.ts +0 -1
  205. package/dist/components/Heading.d.ts +0 -11
  206. package/dist/components/Icon.d.ts +0 -69
  207. package/dist/components/Loader.d.ts +0 -10
  208. package/dist/components/Loader.spec.d.ts +0 -1
  209. package/dist/components/Modal/ActionModal.d.ts +0 -8
  210. package/dist/components/Modal/Modal.d.ts +0 -7
  211. package/dist/components/Modal/ModalBase.d.ts +0 -6
  212. package/dist/components/Modal/ModalBase.spec.d.ts +0 -1
  213. package/dist/components/Modal/ModalCloseButton.d.ts +0 -3
  214. package/dist/components/Modal/ModalPortal.d.ts +0 -7
  215. package/dist/components/ProgressBar.d.ts +0 -9
  216. package/dist/components/ProgressBar.spec.d.ts +0 -1
  217. package/dist/components/ProgressCircle.d.ts +0 -9
  218. package/dist/components/ProgressCircle.spec.d.ts +0 -1
  219. package/dist/components/PromoBanner.d.ts +0 -19
  220. package/dist/components/PromoBanner.spec.d.ts +0 -1
  221. package/dist/components/RadioButton.d.ts +0 -7
  222. package/dist/components/RadioButton.spec.d.ts +0 -1
  223. package/dist/components/Switch.d.ts +0 -17
  224. package/dist/components/Switch.spec.d.ts +0 -1
  225. package/dist/components/Tab.d.ts +0 -12
  226. package/dist/components/Tab.spec.d.ts +0 -1
  227. package/dist/components/TabsWrapper.d.ts +0 -3
  228. package/dist/components/TabsWrapper.spec.d.ts +0 -1
  229. package/dist/components/Text.d.ts +0 -15
  230. package/dist/components/Toast.d.ts +0 -22
  231. package/dist/components/Toast.spec.d.ts +0 -1
  232. package/dist/components/ToastWrapper.d.ts +0 -26
  233. package/dist/components/ToastWrapper.spec.d.ts +0 -1
  234. package/dist/components/constants.d.ts +0 -12
  235. package/dist/components/helpers.d.ts +0 -3
  236. package/dist/constants/keyCodes.d.ts +0 -3
  237. package/dist/index.css +0 -2
  238. package/dist/index.css.map +0 -1
  239. package/dist/index.d.ts +0 -8
  240. package/dist/index.js +0 -2
  241. package/dist/index.js.map +0 -1
  242. package/dist/index.modern.js +0 -2
  243. package/dist/index.modern.js.map +0 -1
  244. package/dist/index.module.js +0 -2
  245. package/dist/index.module.js.map +0 -1
  246. package/dist/index.umd.js +0 -2
  247. package/dist/index.umd.js.map +0 -1
  248. package/dist/stories/Alert.stories.d.ts +0 -14
  249. package/dist/stories/Badge.stories.d.ts +0 -16
  250. package/dist/stories/Button.stories.d.ts +0 -20
  251. package/dist/stories/ButtonGroup.stories.d.ts +0 -18
  252. package/dist/stories/Card.stories.d.ts +0 -10
  253. package/dist/stories/CheckboxField.stories.d.ts +0 -9
  254. package/dist/stories/FieldGroup.stories.d.ts +0 -9
  255. package/dist/stories/Form.stories.d.ts +0 -6
  256. package/dist/stories/FormGroup.stories.d.ts +0 -9
  257. package/dist/stories/Icon.stories.d.ts +0 -14
  258. package/dist/stories/Loader.stories.d.ts +0 -8
  259. package/dist/stories/Modal.stories.d.ts +0 -19
  260. package/dist/stories/ProgressCircle.stories.d.ts +0 -13
  261. package/dist/stories/Progressbar.stories.d.ts +0 -13
  262. package/dist/stories/PromoBanner.stories.d.ts +0 -13
  263. package/dist/stories/RadioButton.stories.d.ts +0 -9
  264. package/dist/stories/Switch.stories.d.ts +0 -12
  265. package/dist/stories/Tab.stories.d.ts +0 -10
  266. package/dist/stories/TabsWrapper.stories.d.ts +0 -15
  267. package/dist/stories/Toast.stories.d.ts +0 -15
  268. package/dist/stories/ToastWrapper.stories.d.ts +0 -40
  269. package/dist/stories/foundations/components/ColorTokens.d.ts +0 -2
  270. package/dist/stories/foundations/components/IconsShowcase.d.ts +0 -6
  271. package/dist/utils/noop.d.ts +0 -1
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { ComponentMeta } from '@storybook/react';
3
+
4
+ import {
5
+ ButtonGroup as ButtonGroupComponent,
6
+ ButtonGroupProps,
7
+ } from './ButtonGroup';
8
+
9
+ import { Button } from '../Button';
10
+
11
+ export default {
12
+ title: 'Components/Button Group',
13
+ component: ButtonGroupComponent,
14
+ } as ComponentMeta<typeof ButtonGroupComponent>;
15
+
16
+ export const controlled = (args: ButtonGroupProps): React.ReactElement => {
17
+ return (
18
+ <ButtonGroupComponent {...args}>
19
+ <Button>First option</Button>
20
+ <Button>Second option</Button>
21
+ </ButtonGroupComponent>
22
+ );
23
+ };
24
+
25
+ controlled.args = {
26
+ currentIndex: 1,
27
+ };
28
+
29
+ export const uncontrolledEmptySelection = (
30
+ args: ButtonGroupProps
31
+ ): React.ReactElement => {
32
+ return (
33
+ <ButtonGroupComponent {...args}>
34
+ <Button>First option</Button>
35
+ <Button>Second option</Button>
36
+ </ButtonGroupComponent>
37
+ );
38
+ };
39
+
40
+ export const uncontrolledWithInitialSelection = (
41
+ args: ButtonGroupProps
42
+ ): React.ReactElement => {
43
+ return (
44
+ <ButtonGroupComponent {...args}>
45
+ <Button>First option</Button>
46
+ <Button>Second option</Button>
47
+ </ButtonGroupComponent>
48
+ );
49
+ };
50
+
51
+ uncontrolledWithInitialSelection.args = {
52
+ initialIndex: 1,
53
+ };
@@ -0,0 +1,75 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+
4
+ import { ButtonSize, ButtonProps } from '../Button';
5
+
6
+ import styles from './ButtonGroup.module.scss';
7
+
8
+ import noop from '../../utils/noop';
9
+
10
+ const baseClass = 'btn-group';
11
+
12
+ export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
13
+ currentIndex?: number;
14
+ initialIndex?: number;
15
+ fullWidth?: boolean;
16
+ size?: ButtonSize;
17
+ children: ReadonlyArray<
18
+ React.ReactElement<React.PropsWithChildren<ButtonProps>>
19
+ >;
20
+ onIndexChange?: (
21
+ currentIndex: number,
22
+ event: React.MouseEvent<HTMLButtonElement>
23
+ ) => void;
24
+ }
25
+
26
+ export const ButtonGroup: React.FC<ButtonGroupProps> = ({
27
+ size = 'medium',
28
+ fullWidth = false,
29
+ onIndexChange = noop,
30
+ className,
31
+ children,
32
+ currentIndex,
33
+ initialIndex = -1,
34
+ ...restProps
35
+ }) => {
36
+ const mergedClassName = cx(styles[baseClass], className);
37
+ const [currentStateIndex, setCurrentStateIndex] =
38
+ React.useState(initialIndex);
39
+
40
+ const isControlled = typeof currentIndex === 'number';
41
+
42
+ const _currentIndex = isControlled ? currentIndex : currentStateIndex;
43
+
44
+ const handleClick = (index: number, event: any) => {
45
+ if (!isControlled) {
46
+ setCurrentStateIndex(index);
47
+ }
48
+
49
+ onIndexChange(index, event);
50
+ };
51
+
52
+ return (
53
+ <div role="group" className={mergedClassName} {...restProps}>
54
+ {React.Children.map(children, (child, i) =>
55
+ React.cloneElement(child, {
56
+ fullWidth,
57
+ size,
58
+ kind: 'secondary',
59
+ type: 'button',
60
+ onClick: (event: any) => {
61
+ handleClick(i, event);
62
+ if (child.props.onClick) {
63
+ child.props.onClick(event);
64
+ }
65
+ },
66
+ className: cx(
67
+ styles['btn'],
68
+ styles[`btn--${size as string}`],
69
+ i === _currentIndex && styles['btn--active']
70
+ ),
71
+ })
72
+ )}
73
+ </div>
74
+ );
75
+ };
@@ -0,0 +1 @@
1
+ export { ButtonGroup } from './ButtonGroup';
@@ -0,0 +1,31 @@
1
+ .card {
2
+ background-color: var(--surface-basic-default);
3
+ border: solid 1px var(--border-subtle);
4
+ border-radius: 4px;
5
+ color: var(--content-default);
6
+ margin-top: 8px;
7
+ overflow: hidden;
8
+ padding: 16px;
9
+
10
+ &__title {
11
+ align-items: center;
12
+ display: flex;
13
+ margin-bottom: 14px;
14
+ }
15
+
16
+ &__text {
17
+ color: var(--content-default);
18
+ overflow: hidden;
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap;
21
+ width: 100%;
22
+ }
23
+
24
+ &__img {
25
+ border-radius: 4px;
26
+ display: inline-block;
27
+ height: 24px;
28
+ margin-right: 10px;
29
+ width: 24px;
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ import * as React from 'react';
2
+ import { render } from 'test-utils';
3
+ import { Card } from './Card';
4
+
5
+ describe('<Card /> component', () => {
6
+ it('should render only title if image is not provided', () => {
7
+ const { getByText, getByRole } = render(<Card title="Hello" />);
8
+ expect(getByText('Hello')).toBeVisible();
9
+ expect(() => getByRole('img')).toThrow();
10
+ });
11
+
12
+ it('should render title and image if provided', () => {
13
+ const { getByText, getByRole } = render(
14
+ <Card title="Hello" img="https://via.placeholder.com/100" />
15
+ );
16
+ expect(getByText('Hello')).toBeVisible();
17
+ expect(getByRole('img')).toBeVisible();
18
+ });
19
+
20
+ it('should render text children', () => {
21
+ const { getByText } = render(<Card title="Hello">Lorem ipsum</Card>);
22
+ expect(getByText('Lorem ipsum')).toBeVisible();
23
+ });
24
+
25
+ it('should render nested elements as children', () => {
26
+ const { getByRole } = render(
27
+ <Card title="Hello">
28
+ <div>
29
+ <h3>Lorem ipsum</h3>
30
+ </div>
31
+ </Card>
32
+ );
33
+
34
+ const heading = getByRole('heading');
35
+ expect(heading).toBeVisible();
36
+ expect(heading).toHaveTextContent('Lorem ipsum');
37
+ });
38
+ });
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import { ComponentMeta } from '@storybook/react';
3
+
4
+ import { Card as CardComponent, CardProps } from '../Card';
5
+
6
+ export default {
7
+ title: 'Components/Card',
8
+ component: CardComponent,
9
+ } as ComponentMeta<typeof CardComponent>;
10
+
11
+ type ICardArgs = CardProps;
12
+
13
+ export const Card = (args: ICardArgs): React.ReactElement => {
14
+ return (
15
+ <div style={{ width: '300px' }}>
16
+ <CardComponent {...args} />
17
+ </div>
18
+ );
19
+ };
20
+
21
+ Card.args = {
22
+ title: 'Title goes here',
23
+ img: 'https://via.placeholder.com/100',
24
+ children: (
25
+ <p style={{ margin: 0 }}>
26
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
27
+ tempor incididunt ut labore
28
+ </p>
29
+ ),
30
+ } as ICardArgs;
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+ import { Text, Heading } from '../Typography';
4
+ import styles from './Card.module.scss';
5
+
6
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ title: string;
8
+ img?: string;
9
+ }
10
+
11
+ export const Card: React.FC<CardProps> = ({
12
+ img,
13
+ title,
14
+ children,
15
+ className,
16
+ ...restProps
17
+ }) => {
18
+ return (
19
+ <Text
20
+ as="div"
21
+ size="md"
22
+ className={cx(styles['card'], className)}
23
+ {...restProps}
24
+ >
25
+ {(img || title) && (
26
+ <Heading as="div" size="sm" className={styles['card__title']}>
27
+ {img && <img src={img} className={styles['card__img']} />}
28
+ {title && <div className={styles['card__text']}>{title}</div>}
29
+ </Heading>
30
+ )}
31
+ {children}
32
+ </Text>
33
+ );
34
+ };
@@ -0,0 +1,2 @@
1
+ export { Card } from './Card';
2
+ export type { CardProps } from './Card';
@@ -0,0 +1,99 @@
1
+ .checkbox {
2
+ display: inline-block;
3
+
4
+ &__label {
5
+ align-items: center;
6
+ cursor: pointer;
7
+ display: inline-flex;
8
+ flex: 0 1 auto;
9
+ width: 100%;
10
+ }
11
+
12
+ &__square {
13
+ align-items: center;
14
+ background-color: var(--surface-basic-default);
15
+ border: 1px solid var(--border-default);
16
+ border-radius: 4px;
17
+ box-sizing: border-box;
18
+ display: flex;
19
+ height: 16px;
20
+ justify-content: center;
21
+ margin-right: 8px;
22
+ position: relative;
23
+ transition: all 0.2s ease;
24
+ width: 16px;
25
+ }
26
+
27
+ &__checkmark {
28
+ opacity: 0;
29
+ pointer-events: none;
30
+ transition: all 0.2s ease;
31
+ }
32
+
33
+ &--selected {
34
+ .checkbox__square {
35
+ background-color: var(--color-action-default);
36
+ border-color: var(--color-action-default);
37
+ }
38
+
39
+ .checkbox__checkmark {
40
+ opacity: 1;
41
+ }
42
+
43
+ &:hover {
44
+ .checkbox__input:checked + .checkbox__square {
45
+ background-color: var(--color-action-hover);
46
+ border-color: var(--color-action-hover);
47
+ }
48
+ }
49
+ }
50
+
51
+ &__text {
52
+ color: var(--content-default);
53
+ flex: 1 0 auto;
54
+ }
55
+
56
+ &__input {
57
+ display: none;
58
+ height: 100%;
59
+ width: 100%;
60
+
61
+ &:checked + .checkbox__square {
62
+ background-color: var(--color-action-default);
63
+ border-color: var(--color-action-default);
64
+
65
+ .checkbox__checkmark {
66
+ opacity: 1;
67
+ }
68
+ }
69
+
70
+ &:focus {
71
+ + .checkbox__square {
72
+ box-shadow: 0 0 1px 2px rgba(66, 132, 245, 0.7); // TODO use shadow token
73
+ }
74
+ }
75
+ }
76
+
77
+ &--disabled {
78
+ .checkbox {
79
+ &__square {
80
+ background-color: var(--surface-basic-disabled);
81
+ border-color: var(--border-disabled);
82
+ opacity: 0.4;
83
+ }
84
+
85
+ &__label,
86
+ &__input,
87
+ &__helper,
88
+ &__text {
89
+ color: var(--content-disabled);
90
+ cursor: not-allowed;
91
+ }
92
+ }
93
+ }
94
+
95
+ &__helper {
96
+ cursor: default;
97
+ margin-left: 24px;
98
+ }
99
+ }
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+ import { Check } from '@livechat/design-system-icons/react/material';
4
+ import { Icon, IconSizeName, IconTypeName } from '../Icon';
5
+
6
+ import styles from './Checkbox.module.scss';
7
+
8
+ export interface CheckboxProps extends React.HTMLAttributes<HTMLInputElement> {
9
+ className?: string;
10
+ checked?: boolean;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ const baseClass = 'checkbox';
15
+
16
+ export const Checkbox: React.FC<CheckboxProps> = ({
17
+ className = '',
18
+ ...props
19
+ }) => {
20
+ const mergedClassNames = cx(styles[`${baseClass}__square`], className);
21
+
22
+ return (
23
+ <div>
24
+ <input
25
+ {...props}
26
+ className={styles[`${baseClass}__input`]}
27
+ type="checkbox"
28
+ />
29
+ <div className={mergedClassNames}>
30
+ <Icon
31
+ source={Check}
32
+ iconType={IconTypeName.Inverted}
33
+ size={IconSizeName.XSmall}
34
+ className={styles[`${baseClass}__checkmark`]}
35
+ />
36
+ </div>
37
+ </div>
38
+ );
39
+ };
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import { render, userEvent, vi } from 'test-utils';
3
+ import { CheckboxField } from './CheckboxField';
4
+
5
+ import styles from './Checkbox.module.scss';
6
+
7
+ const baseClass = 'checkbox';
8
+
9
+ describe('<CheckboxField> component', () => {
10
+ it('should allow for custom class', () => {
11
+ const { container } = render(
12
+ <CheckboxField className="my-css-class">test</CheckboxField>
13
+ );
14
+ expect(container.firstChild).toHaveClass('my-css-class');
15
+ });
16
+
17
+ it('should not have been checked or disabled by default', () => {
18
+ const { container } = render(<CheckboxField>test</CheckboxField>);
19
+ expect(container.firstChild).not.toHaveClass(
20
+ styles[`${baseClass}--selected`]
21
+ );
22
+ expect(container.firstChild).not.toHaveClass(
23
+ styles[`${baseClass}--disabled`]
24
+ );
25
+ });
26
+
27
+ it('should have checked and disabled classes when mentioned props are applied', () => {
28
+ const { container } = render(
29
+ <CheckboxField checked disabled>
30
+ test
31
+ </CheckboxField>
32
+ );
33
+ expect(container.firstChild).toHaveClass(styles[`${baseClass}--selected`]);
34
+ expect(container.firstChild).toHaveClass(styles[`${baseClass}--disabled`]);
35
+ });
36
+
37
+ it('should call onChange method', () => {
38
+ const onChange = vi.fn();
39
+
40
+ const { getByRole } = render(
41
+ <CheckboxField onChange={onChange}>test</CheckboxField>
42
+ );
43
+ userEvent.click(getByRole('checkbox'));
44
+ expect(onChange).toBeCalledTimes(1);
45
+ });
46
+
47
+ it('should not call onChange method when disabled', () => {
48
+ const onChange = vi.fn();
49
+
50
+ const { getByRole } = render(
51
+ <CheckboxField disabled onChange={onChange}>
52
+ test
53
+ </CheckboxField>
54
+ );
55
+ userEvent.click(getByRole('checkbox'));
56
+ expect(onChange).not.toBeCalled();
57
+ });
58
+ });
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import { ComponentMeta } from '@storybook/react';
3
+
4
+ import {
5
+ CheckboxField as CheckboxFieldComponent,
6
+ ICheckboxFieldProps,
7
+ } from './CheckboxField';
8
+
9
+ export default {
10
+ title: 'Forms (WIP)/Checkbox Field',
11
+ component: CheckboxFieldComponent,
12
+ parameters: {
13
+ componentSubtitle: `
14
+ Use checkboxes when there is a short list of options and the user can select multiple options, all or none.
15
+ `,
16
+ },
17
+ } as ComponentMeta<typeof CheckboxFieldComponent>;
18
+
19
+ export const CheckboxField = (
20
+ args: ICheckboxFieldProps
21
+ ): React.ReactElement => {
22
+ return <CheckboxFieldComponent {...args} />;
23
+ };
24
+
25
+ CheckboxField.args = {
26
+ checked: false,
27
+ disabled: false,
28
+ description: 'Help text',
29
+ children: 'Checkbox label',
30
+ } as ICheckboxFieldProps;
@@ -0,0 +1,46 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+ import { Text } from '../Typography';
4
+ import { FieldDescription } from '../FieldDescription';
5
+ import { Checkbox } from './Checkbox';
6
+
7
+ import styles from './Checkbox.module.scss';
8
+
9
+ export interface ICheckboxFieldProps
10
+ extends React.HTMLAttributes<HTMLInputElement> {
11
+ description?: string;
12
+ checked?: boolean;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ const baseClass = 'checkbox';
17
+
18
+ export const CheckboxField: React.FC<ICheckboxFieldProps> = ({
19
+ children,
20
+ className = '',
21
+ description,
22
+ checked,
23
+ disabled,
24
+ ...props
25
+ }) => {
26
+ const mergedClassNames = cx(styles[baseClass], className, {
27
+ [styles[`${baseClass}--selected`]]: checked,
28
+ [styles[`${baseClass}--disabled`]]: disabled,
29
+ });
30
+
31
+ return (
32
+ <div className={mergedClassNames}>
33
+ <label className={styles[`${baseClass}__label`]}>
34
+ <Checkbox {...props} checked={checked} disabled={disabled} />
35
+ <Text as="div" size="md" className={styles[`${baseClass}__text`]}>
36
+ {children}
37
+ </Text>
38
+ </label>
39
+ {description && (
40
+ <FieldDescription className={styles[`${baseClass}__helper`]}>
41
+ {description}
42
+ </FieldDescription>
43
+ )}
44
+ </div>
45
+ );
46
+ };
@@ -0,0 +1,2 @@
1
+ export { Checkbox } from './Checkbox';
2
+ export { CheckboxField } from './CheckboxField';
@@ -0,0 +1,5 @@
1
+ .field-description {
2
+ color: var(--content-subtle);
3
+ display: block;
4
+ margin: 4px 0 0;
5
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+ import { Text } from '../Typography';
4
+
5
+ import styles from './FieldDescription.module.scss';
6
+
7
+ export type FieldDescriptionProps = React.HTMLAttributes<HTMLSpanElement>;
8
+
9
+ const baseClass = 'field-description';
10
+
11
+ export const FieldDescription: React.FC<FieldDescriptionProps> = ({
12
+ children,
13
+ className = '',
14
+ ...props
15
+ }) => {
16
+ const mergedClassNames = cx(styles[baseClass], className);
17
+
18
+ return (
19
+ <Text as="span" size="sm" {...props} className={mergedClassNames}>
20
+ {children}
21
+ </Text>
22
+ );
23
+ };
@@ -0,0 +1 @@
1
+ export { FieldDescription } from './FieldDescription';
@@ -0,0 +1,5 @@
1
+ .field-error {
2
+ color: var(--color-negative-default);
3
+ display: block;
4
+ margin: 4px 0 0;
5
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import cx from 'clsx';
3
+ import { Text } from '../Typography';
4
+
5
+ import styles from './FieldError.module.scss';
6
+
7
+ export type FieldErrorProps = React.HTMLAttributes<HTMLSpanElement>;
8
+
9
+ const baseClass = 'field-error';
10
+
11
+ export const FieldError: React.FC<FieldErrorProps> = ({
12
+ children,
13
+ className = '',
14
+ ...props
15
+ }) => {
16
+ const mergedClassNames = cx(styles[baseClass], className);
17
+
18
+ return (
19
+ <Text as="span" size="sm" {...props} className={mergedClassNames}>
20
+ {children}
21
+ </Text>
22
+ );
23
+ };
@@ -0,0 +1 @@
1
+ export { FieldError } from './FieldError';
@@ -0,0 +1,25 @@
1
+ .field-group {
2
+ align-items: flex-start;
3
+ display: flex;
4
+ flex-direction: column;
5
+ margin: -8px 0;
6
+
7
+ > * {
8
+ padding: 0 0 8px;
9
+ }
10
+
11
+ &--inline {
12
+ flex-direction: row;
13
+ margin: 0 -8px;
14
+
15
+ > * {
16
+ padding: 0 8px;
17
+ }
18
+ }
19
+
20
+ &--stretched {
21
+ > * {
22
+ width: 100%;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+ import { render } from 'test-utils';
3
+ import { FieldGroup } from './FieldGroup';
4
+
5
+ import styles from './FieldGroup.module.scss';
6
+
7
+ const baseClass = 'field-group';
8
+
9
+ describe('<FieldGroup> component', () => {
10
+ it('should allow for custom class', () => {
11
+ const { container } = render(
12
+ <FieldGroup className="my-css-class">test</FieldGroup>
13
+ );
14
+ expect(container.firstChild).toHaveClass('my-css-class');
15
+ });
16
+
17
+ it('should have inline class when inline prop is applied', () => {
18
+ const { container } = render(<FieldGroup inline>test</FieldGroup>);
19
+ expect(container.firstChild).toHaveClass(styles[`${baseClass}--inline`]);
20
+ });
21
+
22
+ it('should have stretched class when stretched prop is applied', () => {
23
+ const { container } = render(<FieldGroup stretch>test</FieldGroup>);
24
+ expect(container.firstChild).toHaveClass(styles[`${baseClass}--stretched`]);
25
+ });
26
+
27
+ it('should render error text', () => {
28
+ const errorText = 'Error test text';
29
+ const { queryByText } = render(
30
+ <FieldGroup error={errorText}>test</FieldGroup>
31
+ );
32
+ expect(queryByText(errorText)).toBeVisible();
33
+ });
34
+
35
+ it('should render description text', () => {
36
+ const descriptionText = 'Description test text';
37
+ const { queryByText } = render(
38
+ <FieldGroup description={descriptionText}>test</FieldGroup>
39
+ );
40
+ expect(queryByText(descriptionText)).toBeVisible();
41
+ });
42
+ });