@transferwise/components 46.52.3 → 46.54.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.
Files changed (156) hide show
  1. package/build/dateLookup/dateTrigger/DateTrigger.js +8 -4
  2. package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
  3. package/build/dateLookup/dateTrigger/DateTrigger.mjs +8 -4
  4. package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
  5. package/build/field/Field.js +36 -8
  6. package/build/field/Field.js.map +1 -1
  7. package/build/field/Field.mjs +37 -9
  8. package/build/field/Field.mjs.map +1 -1
  9. package/build/i18n/en.json +2 -0
  10. package/build/i18n/en.json.js +2 -0
  11. package/build/i18n/en.json.js.map +1 -1
  12. package/build/i18n/en.json.mjs +2 -0
  13. package/build/i18n/en.json.mjs.map +1 -1
  14. package/build/index.js +2 -2
  15. package/build/index.mjs +1 -1
  16. package/build/inlineAlert/InlineAlert.js +13 -6
  17. package/build/inlineAlert/InlineAlert.js.map +1 -1
  18. package/build/inlineAlert/InlineAlert.mjs +13 -6
  19. package/build/inlineAlert/InlineAlert.mjs.map +1 -1
  20. package/build/label/Label.js +35 -4
  21. package/build/label/Label.js.map +1 -1
  22. package/build/label/Label.messages.js +12 -0
  23. package/build/label/Label.messages.js.map +1 -0
  24. package/build/label/Label.messages.mjs +10 -0
  25. package/build/label/Label.messages.mjs.map +1 -0
  26. package/build/label/Label.mjs +36 -5
  27. package/build/label/Label.mjs.map +1 -1
  28. package/build/main.css +14 -8
  29. package/build/styles/dateLookup/dateTrigger/DateTrigger.css +0 -8
  30. package/build/styles/field/Field.css +4 -0
  31. package/build/styles/main.css +14 -8
  32. package/build/styles/upload/Upload.css +10 -0
  33. package/build/tabs/Tab.js +13 -38
  34. package/build/tabs/Tab.js.map +1 -1
  35. package/build/tabs/Tab.mjs +13 -34
  36. package/build/tabs/Tab.mjs.map +1 -1
  37. package/build/tabs/TabList.js +3 -11
  38. package/build/tabs/TabList.js.map +1 -1
  39. package/build/tabs/TabList.mjs +3 -7
  40. package/build/tabs/TabList.mjs.map +1 -1
  41. package/build/tabs/TabPanel.js +3 -16
  42. package/build/tabs/TabPanel.js.map +1 -1
  43. package/build/tabs/TabPanel.mjs +3 -12
  44. package/build/tabs/TabPanel.mjs.map +1 -1
  45. package/build/tabs/Tabs.js +24 -48
  46. package/build/tabs/Tabs.js.map +1 -1
  47. package/build/tabs/Tabs.mjs +24 -47
  48. package/build/tabs/Tabs.mjs.map +1 -1
  49. package/build/tabs/utils.js +0 -1
  50. package/build/tabs/utils.js.map +1 -1
  51. package/build/tabs/utils.mjs +0 -1
  52. package/build/tabs/utils.mjs.map +1 -1
  53. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  54. package/build/types/field/Field.d.ts +4 -2
  55. package/build/types/field/Field.d.ts.map +1 -1
  56. package/build/types/index.d.ts +2 -1
  57. package/build/types/index.d.ts.map +1 -1
  58. package/build/types/inlineAlert/InlineAlert.d.ts +9 -0
  59. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  60. package/build/types/label/Label.d.ts +21 -1
  61. package/build/types/label/Label.d.ts.map +1 -1
  62. package/build/types/label/Label.messages.d.ts +8 -0
  63. package/build/types/label/Label.messages.d.ts.map +1 -0
  64. package/build/types/label/index.d.ts +3 -0
  65. package/build/types/label/index.d.ts.map +1 -0
  66. package/build/types/tabs/Tab.d.ts +12 -1
  67. package/build/types/tabs/Tab.d.ts.map +1 -1
  68. package/build/types/tabs/TabList.d.ts +3 -8
  69. package/build/types/tabs/TabList.d.ts.map +1 -1
  70. package/build/types/tabs/TabPanel.d.ts +6 -14
  71. package/build/types/tabs/TabPanel.d.ts.map +1 -1
  72. package/build/types/tabs/Tabs.d.ts +83 -30
  73. package/build/types/tabs/Tabs.d.ts.map +1 -1
  74. package/build/types/tabs/index.d.ts +2 -1
  75. package/build/types/tabs/index.d.ts.map +1 -1
  76. package/build/types/tabs/utils.d.ts +12 -7
  77. package/build/types/tabs/utils.d.ts.map +1 -1
  78. package/build/types/upload/Upload.d.ts +1 -0
  79. package/build/types/upload/Upload.d.ts.map +1 -1
  80. package/build/types/upload/Upload.messages.d.ts +4 -0
  81. package/build/types/upload/Upload.messages.d.ts.map +1 -1
  82. package/build/types/upload/steps/completeStep/completeStep.d.ts +1 -3
  83. package/build/types/upload/steps/completeStep/completeStep.d.ts.map +1 -1
  84. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts +1 -0
  85. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
  86. package/build/upload/Upload.js +26 -12
  87. package/build/upload/Upload.js.map +1 -1
  88. package/build/upload/Upload.messages.js +3 -0
  89. package/build/upload/Upload.messages.js.map +1 -1
  90. package/build/upload/Upload.messages.mjs +3 -0
  91. package/build/upload/Upload.messages.mjs.map +1 -1
  92. package/build/upload/Upload.mjs +26 -12
  93. package/build/upload/Upload.mjs.map +1 -1
  94. package/build/upload/steps/completeStep/completeStep.js +15 -30
  95. package/build/upload/steps/completeStep/completeStep.js.map +1 -1
  96. package/build/upload/steps/completeStep/completeStep.mjs +16 -31
  97. package/build/upload/steps/completeStep/completeStep.mjs.map +1 -1
  98. package/build/upload/steps/uploadImageStep/uploadImageStep.js +56 -32
  99. package/build/upload/steps/uploadImageStep/uploadImageStep.js.map +1 -1
  100. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs +56 -32
  101. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs.map +1 -1
  102. package/package.json +2 -2
  103. package/src/dateInput/DateInput.tests.story.tsx +6 -42
  104. package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
  105. package/src/dateLookup/dateTrigger/DateTrigger.css +0 -8
  106. package/src/dateLookup/dateTrigger/DateTrigger.less +0 -8
  107. package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
  108. package/src/dateLookup/dateTrigger/DateTrigger.tsx +9 -4
  109. package/src/field/Field.css +4 -0
  110. package/src/field/Field.less +5 -0
  111. package/src/field/Field.spec.tsx +41 -5
  112. package/src/field/Field.story.tsx +105 -7
  113. package/src/field/Field.tsx +34 -10
  114. package/src/i18n/en.json +2 -0
  115. package/src/index.ts +2 -1
  116. package/src/inlineAlert/InlineAlert.story.tsx +7 -72
  117. package/src/inlineAlert/InlineAlert.tsx +14 -3
  118. package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.js +5 -10
  119. package/src/inputs/InputGroup.spec.tsx +1 -1
  120. package/src/inputs/SearchInput.spec.tsx +1 -1
  121. package/src/inputs/SelectInput.spec.tsx +1 -1
  122. package/src/label/Label.messages.tsx +8 -0
  123. package/src/label/Label.spec.tsx +53 -4
  124. package/src/label/Label.story.tsx +32 -26
  125. package/src/label/Label.tsx +47 -2
  126. package/src/label/index.ts +2 -0
  127. package/src/main.css +14 -8
  128. package/src/main.less +1 -0
  129. package/src/moneyInput/MoneyInput.story.tsx +11 -11
  130. package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
  131. package/src/select/Select.rtl.spec.tsx +1 -1
  132. package/src/switch/Switch.spec.tsx +1 -1
  133. package/src/switch/Switch.story.tsx +19 -21
  134. package/src/tabs/Tab.tsx +72 -0
  135. package/src/tabs/TabList.tsx +11 -0
  136. package/src/tabs/TabPanel.tsx +14 -0
  137. package/src/tabs/{Tabs.story.js → Tabs.story.tsx} +1 -1
  138. package/src/tabs/{Tabs.js → Tabs.tsx} +111 -74
  139. package/src/tabs/index.ts +2 -0
  140. package/src/tabs/{utils.spec.js → utils.spec.ts} +24 -21
  141. package/src/tabs/{utils.js → utils.ts} +15 -9
  142. package/src/upload/Upload.css +10 -0
  143. package/src/upload/Upload.less +9 -0
  144. package/src/upload/Upload.messages.ts +4 -0
  145. package/src/upload/Upload.spec.js +8 -7
  146. package/src/upload/Upload.story.tsx +1 -0
  147. package/src/upload/Upload.tsx +39 -20
  148. package/src/upload/steps/completeStep/completeStep.spec.js +0 -9
  149. package/src/upload/steps/completeStep/completeStep.tsx +14 -29
  150. package/src/upload/steps/uploadImageStep/uploadImageStep.spec.js +12 -0
  151. package/src/upload/steps/uploadImageStep/uploadImageStep.tsx +43 -24
  152. package/src/field/Field.tests.story.tsx +0 -33
  153. package/src/tabs/Tab.js +0 -71
  154. package/src/tabs/TabList.js +0 -15
  155. package/src/tabs/TabPanel.js +0 -20
  156. package/src/tabs/index.js +0 -1
@@ -2,9 +2,9 @@ import { select, text } from '@storybook/addon-knobs';
2
2
  import { Meta } from '@storybook/react';
3
3
 
4
4
  import { Sentiment } from '../common';
5
- import { Input } from '../inputs/Input';
6
5
 
7
6
  import InlineAlert, { InlineAlertProps } from './InlineAlert';
7
+ import { lorem40 } from '../test-utils';
8
8
 
9
9
  export default {
10
10
  component: InlineAlert,
@@ -29,81 +29,16 @@ export const Basic = () => {
29
29
 
30
30
  const message = text('message', 'Please enter a password over 5 characters');
31
31
 
32
- let typeClass = '';
33
- switch (type) {
34
- case Sentiment.ERROR:
35
- case Sentiment.NEGATIVE:
36
- typeClass = 'has-error';
37
- break;
38
- case Sentiment.SUCCESS:
39
- case Sentiment.POSITIVE:
40
- typeClass = 'has-success';
41
- break;
42
- case Sentiment.INFO:
43
- case Sentiment.NEUTRAL:
44
- typeClass = 'has-info';
45
- break;
46
- case Sentiment.WARNING:
47
- typeClass = 'has-warning';
48
- break;
49
- case Sentiment.PENDING:
50
- }
51
-
52
32
  return (
53
33
  <>
54
- {/* eslint-disable-next-line react/no-adjacent-inline-elements */}
55
- <p>
56
- The styling for the input (the coloured border) and the visibility of the inline alert is
57
- controlled through the use of <code>has-***</code> classes which are applied to the{' '}
58
- <code>form-group</code> element. For example, to display an inline alert of type error, you
59
- must also apply the class <code>has-error</code> to the parent <code>form-group</code>{' '}
60
- element. The available classes are <code>has-error</code>, <code>has-info</code>,{' '}
61
- <code>has-warning</code> and <code>has-success</code>.
62
- </p>
63
34
  <p>
64
- Where possible consumers should use{' '}
65
- <a href="https://storybook.wise.design/?path=/story/field--basic">Field</a> instead of doing
66
- this manually.
35
+ Avoid using <code>InlineAlert</code> directly (unless for some custom use cases), for form
36
+ control validation, info messaging please use <code>Field</code> component
37
+ <pre>{'<Field sentiment={..} message={..}>'}</pre>
67
38
  </p>
68
- <div className={`form-group ${typeClass}`}>
69
- <label className="control-label" htmlFor="id0">
70
- Toggleable
71
- </label>
72
- <Input id="id0" value="Neptune is cool" />
73
- <InlineAlert type={type}>{message}</InlineAlert>
74
- </div>
75
- <div className="form-group has-error">
76
- <label className="control-label" htmlFor="id1">
77
- Negative
78
- </label>
79
- <Input id="id1" value="Neptune is cool" />
80
- <InlineAlert type="negative">{message}</InlineAlert>
81
- </div>
82
- <div className="form-group has-success">
83
- <label className="control-label" htmlFor="id2">
84
- Positive
85
- </label>
86
- <Input id="id2" value="Neptune is cool" />
87
- <InlineAlert type="positive">
88
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
89
- ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
90
- ullamco laboris nisi ut aliquip ex ea commodo consequat.
91
- </InlineAlert>
92
- </div>
93
- <div className="form-group has-neutral">
94
- <label className="control-label" htmlFor="id3">
95
- Neutral
96
- </label>
97
- <Input id="id3" value="Neptune is cool" />
98
- <InlineAlert type="neutral">{message}</InlineAlert>
99
- </div>
100
- <div className="form-group">
101
- <label className="control-label" htmlFor="id4">
102
- No has-* class
103
- </label>
104
- <Input id="id4" value="Neptune is cool" />
105
- <InlineAlert type="negative">{message}</InlineAlert>
106
- </div>
39
+ <InlineAlert type={type}>{message}</InlineAlert>
40
+ <InlineAlert type="negative">{message}</InlineAlert>
41
+ <InlineAlert type="positive">{lorem40}</InlineAlert>
107
42
  </>
108
43
  );
109
44
  };
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
3
3
 
4
4
  import { Sentiment, Size } from '../common';
5
5
  import StatusIcon from '../statusIcon';
6
+ import Body from '../body';
6
7
 
7
8
  export interface InlineAlertProps {
8
9
  id?: string;
@@ -19,6 +20,15 @@ const iconTypes = new Set<NonNullable<InlineAlertProps['type']>>([
19
20
  Sentiment.WARNING,
20
21
  ]);
21
22
 
23
+ /**
24
+ * Avoid using `<InlineAlert>` component directly
25
+ * it's for edge cases when `<Field />` isn't suitable for some reasons.
26
+ *
27
+ * Example:
28
+ * ```
29
+ * <Field sentiment={..} message={..}>..</Field>
30
+ * ```
31
+ */
22
32
  export default function InlineAlert({
23
33
  id,
24
34
  type = 'neutral',
@@ -26,17 +36,18 @@ export default function InlineAlert({
26
36
  children,
27
37
  }: InlineAlertProps) {
28
38
  return (
29
- <div
39
+ <Body
30
40
  role="alert"
31
41
  id={id}
32
42
  className={clsx(
33
43
  'alert alert-detach',
34
44
  `alert-${type === Sentiment.NEGATIVE || type === Sentiment.ERROR ? 'danger' : type}`,
45
+ 'd-flex',
35
46
  className,
36
47
  )}
37
48
  >
38
49
  {iconTypes.has(type) && <StatusIcon sentiment={type} size={Size.SMALL} />}
39
- <div className="np-text-body-default">{children}</div>
40
- </div>
50
+ {children}
51
+ </Body>
41
52
  );
42
53
  }
@@ -2,6 +2,7 @@ import { text } from '@storybook/addon-knobs';
2
2
  import { userEvent, within } from '@storybook/test';
3
3
 
4
4
  import InputWithDisplayFormat from '.';
5
+ import { Field } from '../field/Field';
5
6
 
6
7
  export default {
7
8
  component: InputWithDisplayFormat,
@@ -13,10 +14,7 @@ export const Basic = () => {
13
14
  const displayPattern = text('DisplayPattern', '**-**-**');
14
15
 
15
16
  return (
16
- <>
17
- <label id="template" htmlFor="Basic">
18
- Display pattern is controlled via knobs
19
- </label>
17
+ <Field label="Display pattern is controlled via knobs" id="Basic">
20
18
  <InputWithDisplayFormat
21
19
  id="Basic"
22
20
  placeholder={placeholder}
@@ -26,7 +24,7 @@ export const Basic = () => {
26
24
  onBlur={(v) => console.log(v)}
27
25
  onFocus={(v) => console.log(v)}
28
26
  />
29
- </>
27
+ </Field>
30
28
  );
31
29
  };
32
30
 
@@ -37,10 +35,7 @@ const Template = (args) => {
37
35
  const id = label.replaceAll(' ', '-').toLowerCase();
38
36
 
39
37
  return (
40
- <>
41
- <label id="template" htmlFor={id}>
42
- {label}
43
- </label>
38
+ <Field label={label} id={id}>
44
39
  <InputWithDisplayFormat
45
40
  id={id}
46
41
  placeholder={placeholder}
@@ -50,7 +45,7 @@ const Template = (args) => {
50
45
  onBlur={(v) => console.log(v)}
51
46
  onFocus={(v) => console.log(v)}
52
47
  />
53
- </>
48
+ </Field>
54
49
  );
55
50
  };
56
51
 
@@ -26,6 +26,6 @@ describe('InputGroup', () => {
26
26
  </InputGroup>
27
27
  </Field>,
28
28
  );
29
- expect(screen.getByLabelText('Search…')).toHaveRole('textbox');
29
+ expect(screen.getByLabelText(/Search…/)).toHaveRole('textbox');
30
30
  });
31
31
  });
@@ -11,6 +11,6 @@ describe('SearchInput', () => {
11
11
  <SearchInput />
12
12
  </Field>,
13
13
  );
14
- expect(screen.getByLabelText('Search…')).toHaveRole('searchbox');
14
+ expect(screen.getByLabelText(/Search…/)).toHaveRole('searchbox');
15
15
  });
16
16
  });
@@ -214,6 +214,6 @@ describe('SelectInput', () => {
214
214
  <SelectInput items={[{ type: 'option', value: 'USD' }]} value="USD" />
215
215
  </Field>,
216
216
  );
217
- expect(screen.getByLabelText('Currency')).toHaveAttribute('aria-haspopup');
217
+ expect(screen.getByLabelText(/Currency/)).toHaveAttribute('aria-haspopup');
218
218
  });
219
219
  });
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ optionalLabel: {
5
+ id: 'neptune.Label.optional',
6
+ defaultMessage: '(Optional)',
7
+ },
8
+ });
@@ -1,5 +1,5 @@
1
1
  import { Input } from '../inputs/Input';
2
- import { render, screen } from '../test-utils';
2
+ import { lorem10, render, screen } from '../test-utils';
3
3
  import { Label } from './Label';
4
4
 
5
5
  describe('Label', () => {
@@ -11,16 +11,65 @@ describe('Label', () => {
11
11
  </Label>,
12
12
  );
13
13
 
14
- expect(screen.getByLabelText('Phone number')).toBeInTheDocument();
14
+ expect(screen.getByLabelText(/Phone number/)).toBeInTheDocument();
15
15
  });
16
- it('renders node type labels', () => {
16
+
17
+ it('renders string labels with (Optional) suffix', () => {
17
18
  render(
18
19
  <Label>
19
- <span>Phone number</span>
20
+ <Label.Optional>Phone number</Label.Optional>
20
21
  <Input readOnly />
21
22
  </Label>,
22
23
  );
23
24
 
25
+ expect(screen.getByLabelText(/Phone number/)).toBeInTheDocument();
26
+ expect(screen.getByLabelText(/(Optional)/)).toBeInTheDocument();
27
+ });
28
+
29
+ it('renders description', () => {
30
+ const inputId = 'input-id';
31
+ const descriptionId = 'desc-test';
32
+ render(
33
+ <>
34
+ <Label htmlFor={inputId}>Phone number</Label>
35
+ <Label.Description id={descriptionId}>{lorem10}</Label.Description>
36
+ <Input id={inputId} readOnly aria-describedby={descriptionId} />
37
+ </>,
38
+ );
39
+
24
40
  expect(screen.getByLabelText('Phone number')).toBeInTheDocument();
41
+ expect(screen.getByText(lorem10)).toBeInTheDocument();
42
+ expect(screen.getByText(lorem10)).toHaveAttribute('id', descriptionId);
43
+ });
44
+
45
+ it('renders description with optional', () => {
46
+ const descriptionId = 'desc-test';
47
+ render(
48
+ <>
49
+ <Label htmlFor="test">
50
+ <Label.Optional>Phone number</Label.Optional>
51
+ </Label>
52
+ <Label.Description id={descriptionId}>{lorem10}</Label.Description>
53
+ <Input id="test" readOnly aria-describedby={descriptionId} />
54
+ </>,
55
+ );
56
+
57
+ expect(screen.getByLabelText(/Phone number/)).toBeInTheDocument();
58
+ expect(screen.getByText(lorem10)).toBeInTheDocument();
59
+ expect(screen.getByText(lorem10)).toHaveAttribute('id', descriptionId);
60
+ });
61
+
62
+ it('connects label with form control via `id` + `aria-labelledby`', () => {
63
+ const labelId = 'label-id';
64
+ render(
65
+ <>
66
+ <Label id={labelId}>Phone number</Label>
67
+ <div role="group" aria-labelledby={labelId}>
68
+ Custom complex component
69
+ </div>
70
+ </>,
71
+ );
72
+
73
+ expect(screen.getByLabelText('Phone number')).toHaveAttribute('aria-labelledby', labelId);
25
74
  });
26
75
  });
@@ -1,37 +1,43 @@
1
- import { useState } from 'react';
2
-
3
- import Info from '../info/Info';
4
- import { Input } from '../inputs/Input';
1
+ import Info from '../info';
5
2
  import { Label } from './Label';
6
3
 
7
4
  export default {
8
5
  component: Label,
9
6
  title: 'Label',
7
+ tags: ['autodocs'],
10
8
  };
11
9
 
12
10
  export const Basic = () => {
13
- const [value, setValue] = useState<string | undefined>('This is some text');
14
- return (
15
- <Label>
16
- Phone number
17
- <Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
18
- </Label>
19
- );
20
- };
21
-
22
- export const WithInfo = () => {
23
- const [value, setValue] = useState<string | undefined>('This is some text');
24
11
  return (
25
- <Label>
26
- <span className="d-flex">
27
- Phone number{' '}
28
- <Info
29
- content="This is some help in popover"
30
- aria-label="The aria label"
31
- className="m-l-1"
32
- />
33
- </span>
34
- <Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
35
- </Label>
12
+ <>
13
+ <p>
14
+ Avoid using <code>Label</code> component directly (unless for some custom use cases), for
15
+ form control labels and descriptions / info messages, please use <code>Field</code>{' '}
16
+ component
17
+ <pre>{'<Field label={..} description={..} required={..}>'}</pre>
18
+ </p>
19
+ <Label className="m-b-2">Text Input</Label>
20
+ <Label className="m-b-2">
21
+ <Label.Optional>Text Input</Label.Optional>
22
+ </Label>
23
+ <Label>
24
+ <Label.Optional>Text Input with Description</Label.Optional>
25
+ </Label>
26
+ <Label.Description className="m-b-2">This a field Description</Label.Description>
27
+ <Label>
28
+ <div>
29
+ Text Input with Description{' '}
30
+ <Info content="This is some help in popover" aria-label="The aria label" />
31
+ </div>
32
+ </Label>
33
+ <Label.Description className="m-b-2">This a field Description</Label.Description>
34
+ <Label>
35
+ <Label.Optional>
36
+ Text Input with Description{' '}
37
+ <Info content="This is some help in popover" aria-label="The aria label" />{' '}
38
+ </Label.Optional>
39
+ </Label>
40
+ <Label.Description className="m-b-2">This a field Description</Label.Description>
41
+ </>
36
42
  );
37
43
  };
@@ -1,4 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
+ import messages from './Label.messages';
3
+ import { useIntl } from 'react-intl';
4
+ import Body from '../body';
5
+ import { CommonProps } from '../common';
6
+ import { PropsWithChildren } from 'react';
2
7
 
3
8
  export type LabelProps = {
4
9
  id?: string;
@@ -7,14 +12,54 @@ export type LabelProps = {
7
12
  children?: React.ReactNode;
8
13
  };
9
14
 
10
- export const Label = ({ id, htmlFor, className, children }: LabelProps) => {
15
+ /**
16
+ * Avoid using `<Label>` component directly
17
+ * it's for edge cases when `<Field />` isn't suitable for some reasons.
18
+ *
19
+ * Example:
20
+ * ```
21
+ * <Field label={..} description={..} required={..}>..</Field>
22
+ * ```
23
+ */
24
+ const Label = ({ className, children, htmlFor, id }: LabelProps) => {
11
25
  return (
12
26
  <label
13
27
  id={id}
14
28
  htmlFor={htmlFor}
15
- className={clsx('control-label d-flex flex-column gap-y-1 m-b-0', className)}
29
+ className={clsx(
30
+ 'np-label d-flex flex-column np-text-body-default-bold text-primary m-b-0',
31
+ className,
32
+ )}
16
33
  >
17
34
  {children}
18
35
  </label>
19
36
  );
20
37
  };
38
+
39
+ export type LabelOptionalProps = PropsWithChildren<CommonProps>;
40
+
41
+ // eslint-disable-next-line functional/immutable-data
42
+ Label.Optional = function Optional({ children, className }: LabelOptionalProps) {
43
+ const { formatMessage } = useIntl();
44
+ return (
45
+ <div>
46
+ {children}
47
+ <Body as="span" className={clsx('text-secondary', 'm-l-1', className)}>
48
+ {formatMessage(messages.optionalLabel)}
49
+ </Body>
50
+ </div>
51
+ );
52
+ };
53
+
54
+ export type LabelDescriptionProps = PropsWithChildren<CommonProps> & { id?: string };
55
+
56
+ // eslint-disable-next-line functional/immutable-data
57
+ Label.Description = function Description({ id, children, className }: LabelDescriptionProps) {
58
+ return children ? (
59
+ <Body id={id} className={clsx('text-secondary', className)}>
60
+ {children}
61
+ </Body>
62
+ ) : null;
63
+ };
64
+
65
+ export { Label };
@@ -0,0 +1,2 @@
1
+ export { Label } from './Label';
2
+ export type { LabelProps, LabelOptionalProps, LabelDescriptionProps } from './Label';
package/src/main.css CHANGED
@@ -1719,18 +1719,10 @@ button.np-option {
1719
1719
  white-space: nowrap;
1720
1720
  width: 100%;
1721
1721
  }
1722
- .np-date-trigger .control-label {
1723
- font-weight: 400;
1724
- font-weight: var(--font-weight-regular);
1725
- }
1726
1722
  .np-theme-personal .np-date-trigger {
1727
1723
  padding-left: 16px;
1728
1724
  padding-left: var(--size-16);
1729
1725
  }
1730
- .np-theme-personal .np-date-trigger .control-label + span {
1731
- font-weight: 400;
1732
- font-weight: var(--font-weight-regular);
1733
- }
1734
1726
  .clear-btn {
1735
1727
  transition: color 0.15s ease-in-out;
1736
1728
  color: #c9cbce;
@@ -2385,6 +2377,10 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
2385
2377
  border-radius: 9999px !important;
2386
2378
  border-radius: var(--radius-full) !important;
2387
2379
  }
2380
+ .np-field-control {
2381
+ margin-top: 4px;
2382
+ margin-top: var(--size-4);
2383
+ }
2388
2384
  .np-input-group {
2389
2385
  display: inline-grid;
2390
2386
  width: 100%;
@@ -5444,6 +5440,16 @@ html:not([dir="rtl"]) .np-navigation-option {
5444
5440
  .tw-droppable-sm {
5445
5441
  min-height: 245px;
5446
5442
  }
5443
+ .upload-error-message {
5444
+ margin-top: 24px;
5445
+ margin-top: var(--padding-medium);
5446
+ border-top: 1px solid rgba(0,0,0,0.10196);
5447
+ border-top: 1px solid var(--color-border-neutral);
5448
+ text-align: start;
5449
+ }
5450
+ .upload-error-message .alert {
5451
+ min-width: 100px;
5452
+ }
5447
5453
  .np-upload-button-container {
5448
5454
  border-style: solid;
5449
5455
  }
package/src/main.less CHANGED
@@ -32,6 +32,7 @@
32
32
  @import "./image/Image.less";
33
33
  @import "./info/Info.less";
34
34
  @import "./inputs/Input.less";
35
+ @import "./field/Field.less";
35
36
  @import "./inputs/InputGroup.less";
36
37
  @import "./inputs/SelectInput.less";
37
38
  @import "./inputs/TextArea.less";
@@ -4,8 +4,7 @@ import { Lock } from '@transferwise/icons';
4
4
  import { useState } from 'react';
5
5
 
6
6
  import MoneyInput, { CurrencyOptionItem } from '.';
7
- import Provider from '../provider/Provider';
8
- import translations from '../i18n';
7
+ import { Field } from '../field/Field';
9
8
 
10
9
  export default {
11
10
  component: MoneyInput,
@@ -17,14 +16,13 @@ export default {
17
16
  const handleOnCurrencyChange = (value: CurrencyOptionItem) => setSelectedCurrency(value);
18
17
 
19
18
  return (
20
- <>
21
- <label htmlFor={args.id}>Editable money input label</label>
19
+ <Field id={args.id} label="Editable money input label" required>
22
20
  <MoneyInput
23
21
  {...args}
24
22
  selectedCurrency={selectedCurrency}
25
23
  onCurrencyChange={handleOnCurrencyChange}
26
24
  />
27
- </>
25
+ </Field>
28
26
  );
29
27
  },
30
28
  args: {
@@ -161,17 +159,19 @@ export const SmallInput: Story = {
161
159
  render: (args) => {
162
160
  return (
163
161
  <>
164
- <label htmlFor={args.id}>Money inputs</label>
165
- <MoneyInput {...args} {...SingleCurrency.args} />
162
+ <Field id={args.id} label="Money inputs" required>
163
+ <MoneyInput {...args} {...SingleCurrency.args} />
164
+ </Field>
166
165
  <br />
167
166
  <MoneyInput {...args} {...MultipleCurrencies.args} />
168
167
  <hr />
169
- <div className="has-error">
170
- <label htmlFor={args.id}>Error states</label>
168
+ <Field id={args.id} label="Error states" sentiment="negative" required>
171
169
  <MoneyInput {...args} {...SingleCurrency.args} />
172
- <br />
170
+ </Field>
171
+ <br />
172
+ <Field sentiment="negative">
173
173
  <MoneyInput {...args} {...MultipleCurrencies.args} />
174
- </div>
174
+ </Field>
175
175
  </>
176
176
  );
177
177
  },
@@ -1,4 +1,4 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { render, screen } from '../test-utils';
2
2
 
3
3
  import RadioGroup from '.';
4
4
  import { Field } from '../field/Field';
@@ -12,6 +12,6 @@ describe('Select', () => {
12
12
  <Select options={options} selected={options[0]} onChange={() => {}} />
13
13
  </Field>,
14
14
  );
15
- expect(screen.getByLabelText('Currency')).toHaveTextContent('USD');
15
+ expect(screen.getByLabelText(/Currency/)).toHaveTextContent('USD');
16
16
  });
17
17
  });
@@ -89,6 +89,6 @@ describe('Switch', () => {
89
89
  <Switch checked onClick={props.onClick} />
90
90
  </Field>,
91
91
  );
92
- expect(screen.getByLabelText('Dark mode')).toHaveAttribute('role', 'switch');
92
+ expect(screen.getByLabelText(/Dark mode/)).toHaveAttribute('role', 'switch');
93
93
  });
94
94
  });
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
 
3
3
  import Switch from './Switch';
4
+ import { Field } from '../field/Field';
4
5
 
5
6
  export default {
6
7
  component: Switch,
@@ -13,16 +14,14 @@ export const Basic = () => {
13
14
 
14
15
  return (
15
16
  <div className="d-flex flex-column">
16
- <label id="labelID" htmlFor="switchId">
17
- A switch with a label
18
- </label>
19
- <Switch
20
- checked={checked1}
21
- className="a-class-name"
22
- aria-labelledby="labelID"
23
- id="switchId"
24
- onClick={() => setCheck1(!checked1)}
25
- />
17
+ <Field id="switchId" label="A switch with a label" required>
18
+ <Switch
19
+ checked={checked1}
20
+ className="a-class-name"
21
+ id="switchId"
22
+ onClick={() => setCheck1(!checked1)}
23
+ />
24
+ </Field>
26
25
 
27
26
  <Switch
28
27
  checked={checked2}
@@ -39,17 +38,16 @@ export const Disabled = () => {
39
38
 
40
39
  return (
41
40
  <div className="d-flex flex-column">
42
- <label id="labelID" htmlFor="switchId">
43
- A switch with a label
44
- </label>
45
- <Switch
46
- checked={checked}
47
- disabled
48
- className="a-class-name"
49
- aria-labelledby="labelID"
50
- id="switchId"
51
- onClick={() => setCheck(!checked)}
52
- />
41
+ <Field id="switchId" label="A switch with a label" required>
42
+ <Switch
43
+ checked={checked}
44
+ disabled
45
+ className="a-class-name"
46
+ aria-labelledby="labelID"
47
+ id="switchId"
48
+ onClick={() => setCheck(!checked)}
49
+ />
50
+ </Field>
53
51
 
54
52
  <Switch
55
53
  checked={!checked}