@transferwise/components 0.0.0-experimental-b7e52e5 → 0.0.0-experimental-2442a28

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 (43) hide show
  1. package/build/header/Header.js +78 -38
  2. package/build/header/Header.js.map +1 -1
  3. package/build/header/Header.mjs +75 -38
  4. package/build/header/Header.mjs.map +1 -1
  5. package/build/index.js +1 -1
  6. package/build/index.mjs +1 -1
  7. package/build/inputs/SelectInput.js +1 -1
  8. package/build/inputs/SelectInput.js.map +1 -1
  9. package/build/inputs/SelectInput.mjs +1 -1
  10. package/build/main.css +4 -0
  11. package/build/selectOption/SelectOption.js +1 -1
  12. package/build/selectOption/SelectOption.js.map +1 -1
  13. package/build/selectOption/SelectOption.mjs +1 -1
  14. package/build/styles/header/Header.css +4 -0
  15. package/build/styles/main.css +4 -0
  16. package/build/title/Title.js +6 -4
  17. package/build/title/Title.js.map +1 -1
  18. package/build/title/Title.mjs +6 -4
  19. package/build/title/Title.mjs.map +1 -1
  20. package/build/types/header/Header.d.ts +22 -14
  21. package/build/types/header/Header.d.ts.map +1 -1
  22. package/build/types/header/index.d.ts +1 -0
  23. package/build/types/header/index.d.ts.map +1 -1
  24. package/build/types/index.d.ts +1 -0
  25. package/build/types/index.d.ts.map +1 -1
  26. package/build/types/title/Title.d.ts +3 -4
  27. package/build/types/title/Title.d.ts.map +1 -1
  28. package/package.json +5 -5
  29. package/src/header/Header.accessibility.docs.mdx +85 -0
  30. package/src/header/Header.css +4 -0
  31. package/src/header/Header.less +5 -0
  32. package/src/header/Header.spec.tsx +68 -50
  33. package/src/header/Header.story.tsx +174 -40
  34. package/src/header/Header.tsx +116 -72
  35. package/src/header/index.ts +1 -0
  36. package/src/index.ts +1 -0
  37. package/src/listItem/ListItem.tests.story.tsx +22 -17
  38. package/src/main.css +4 -0
  39. package/src/summary/Summary.tests.story.tsx +29 -29
  40. package/src/title/Title.tsx +25 -11
  41. package/src/upload/Upload.spec.tsx +3 -1
  42. package/src/upload/steps/processingStep/processingStep.spec.tsx +1 -1
  43. package/src/upload/steps/uploadImageStep/uploadImageStep.spec.tsx +4 -4
@@ -1,102 +1,146 @@
1
1
  import { clsx } from 'clsx';
2
2
 
3
- import { ActionButtonProps } from '../actionButton/ActionButton';
4
- import Button from '../button';
5
3
  import { AriaLabelProperty, CommonProps, Heading, LinkProps, Typography } from '../common';
6
4
  import Link from '../link';
5
+ import Button from '../button';
7
6
  import Title from '../title';
8
- import { HTMLAttributes } from 'react';
7
+ import React, { useEffect, useRef, FunctionComponent } from 'react';
9
8
 
10
9
  type ActionProps = AriaLabelProperty & {
11
10
  text: string;
11
+ onClick?: () => void;
12
12
  };
13
13
 
14
- type ButtonActionProps = ActionProps & ActionButtonProps;
15
-
14
+ type ButtonActionProps = ActionProps;
16
15
  type LinkActionProps = ActionProps & LinkProps;
17
16
 
18
- export type HeaderProps = CommonProps & {
17
+ export interface HeaderProps extends CommonProps {
19
18
  /**
20
- * When the `href` property is provided to the `action`, we will render a `Link` instead of a `ActionButton`.
19
+ * Optional prop to define the action for the header. If the `href` property
20
+ * is provided, a `Link` will be rendered instead of an `ActionButton`.
21
21
  */
22
22
  action?: ButtonActionProps | LinkActionProps;
23
- /**
24
- * Override the heading element rendered for the title, useful to specify the semantics of your header.
25
- *
26
- * @default "h5"
27
- */
23
+
24
+ /** Option prop to specify DOM render element of the title */
28
25
  as?: Heading | 'legend' | 'header';
26
+
27
+ /** Required prop to set the title of the Header. */
29
28
  title: string;
30
- } & Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'id'>;
31
29
 
32
- const HeaderAction = ({ action }: { action: ButtonActionProps | LinkActionProps }) => {
33
- const props = {
34
- 'aria-label': action['aria-label'],
35
- };
30
+ /** Optional prop to specify the level of the Header */
31
+ level?: 'section' | 'group';
36
32
 
37
- if ('href' in action) {
38
- return (
39
- <Link href={action.href} target={action.target} onClick={action.onClick} {...props}>
40
- {action.text}
41
- </Link>
42
- );
43
- }
44
-
45
- return (
46
- <Button
47
- className="np-header__button"
48
- priority="tertiary"
49
- size="sm"
50
- onClick={action.onClick}
51
- {...props}
52
- >
53
- {action.text}
54
- </Button>
55
- );
56
- };
33
+ className?: string;
34
+ testId?: string;
35
+ }
57
36
 
58
37
  /**
38
+ * Renders a header action which can be either a button or a link.
59
39
  *
60
- * Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
61
- *
40
+ * @param {Object} props - The properties object.
41
+ * @param {ButtonActionProps | LinkActionProps} props.action - The action object which can be either a button or a link.
42
+ * @returns {JSX.Element} The rendered header action component.
62
43
  */
63
- export const Header = ({
64
- id,
65
- action,
66
- as = 'h5',
67
- title,
68
- className,
69
- role = undefined,
70
- }: HeaderProps) => {
71
- if (!action) {
44
+ const HeaderAction = React.forwardRef(
45
+ (
46
+ { action }: { action: ButtonActionProps | LinkActionProps },
47
+ ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
48
+ ) => {
49
+ const { 'aria-label': ariaLabel, text, onClick } = action;
50
+
51
+ if ('href' in action) {
52
+ const { href, target, onClick: linkOnClick } = action;
53
+ return (
54
+ <Link
55
+ ref={ref as React.Ref<HTMLAnchorElement>}
56
+ href={href}
57
+ target={target}
58
+ aria-label={ariaLabel}
59
+ onClick={linkOnClick}
60
+ >
61
+ {text}
62
+ </Link>
63
+ );
64
+ }
65
+
72
66
  return (
73
- <Title
74
- as={as}
75
- id={id}
76
- role={role}
77
- type={Typography.TITLE_GROUP}
78
- className={clsx('np-header', 'np-header__title', className)}
67
+ <Button
68
+ ref={ref as React.Ref<HTMLButtonElement>}
69
+ className="np-header__button"
70
+ priority="tertiary"
71
+ size="sm"
72
+ aria-label={ariaLabel}
73
+ onClick={onClick}
79
74
  >
80
- {title}
81
- </Title>
75
+ {text}
76
+ </Button>
82
77
  );
83
- }
78
+ },
79
+ );
84
80
 
85
- if (as === 'legend') {
86
- // eslint-disable-next-line no-console
87
- console.warn(
88
- 'Legends should be the first child in a fieldset, and this is not possible when including an action',
81
+ HeaderAction.displayName = 'HeaderAction';
82
+
83
+ /**
84
+ * @param {ButtonActionProps | LinkActionProps} [action] - Optional prop to specify the action button or link.
85
+ * @param {Heading | 'legend'} [as='h5'] - Optional prop to override the heading element rendered for the title.
86
+ * @param {string} title - Required prop to set the title of the section header.
87
+ * @param {'group' | 'section'} [level='group'] - Optional prop to specify the level of the section header.
88
+ * @param {string} [className]
89
+ * @param {string} [testId]
90
+ *
91
+ * @see {@link Header } for further information.
92
+ * @see {@link https://storybook.wise.design/?path=/docs/typography-header--docs|Storybook Wise Design}
93
+ */
94
+ const Header: FunctionComponent<HeaderProps> = React.forwardRef(
95
+ (
96
+ { as = 'h5', action, className, testId, title, level = 'group', ...props },
97
+ ref: React.Ref<HTMLDivElement | HTMLHeadingElement | HTMLLegendElement>,
98
+ ) => {
99
+ const internalRef = useRef<HTMLLegendElement>(null);
100
+ const levelTypography =
101
+ level === 'section' ? Typography.TITLE_SUBSECTION : Typography.TITLE_GROUP;
102
+ const headerClasses = clsx('np-header', className, {
103
+ 'np-header--section': level === 'section',
104
+ 'np-header__title': !action || as === 'legend',
105
+ });
106
+
107
+ const commonProps = {
108
+ className: headerClasses,
109
+ 'data-testid': testId,
110
+ };
111
+
112
+ useEffect(() => {
113
+ if (as === 'legend' && internalRef.current) {
114
+ const { parentElement } = internalRef.current;
115
+ if (!parentElement || parentElement.tagName.toLowerCase() !== 'fieldset') {
116
+ console.warn(
117
+ 'Legends should be the first child in a fieldset, and this is not possible when including an action',
118
+ );
119
+ }
120
+ }
121
+ }, [as]);
122
+
123
+ if (!action || as === 'legend') {
124
+ return (
125
+ <Title ref={internalRef} as={as} type={levelTypography} {...commonProps} {...props}>
126
+ {title}
127
+ </Title>
128
+ );
129
+ }
130
+
131
+ const actionRef = React.createRef<HTMLButtonElement | HTMLAnchorElement>();
132
+
133
+ return (
134
+ <div {...commonProps} {...props} ref={ref as React.Ref<HTMLDivElement>}>
135
+ <Title as={as} type={levelTypography} className="np-header__title">
136
+ {title}
137
+ </Title>
138
+ <HeaderAction ref={actionRef} action={action} />
139
+ </div>
89
140
  );
90
- }
91
-
92
- return (
93
- <div className={clsx('np-header', className)}>
94
- <Title as={as} type={Typography.TITLE_GROUP} id={id} className="np-header__title">
95
- {title}
96
- </Title>
97
- <HeaderAction action={action} />
98
- </div>
99
- );
100
- };
141
+ },
142
+ );
143
+
144
+ Header.displayName = 'Header';
101
145
 
102
146
  export default Header;
@@ -1 +1,2 @@
1
1
  export { default } from './Header';
2
+ export type { HeaderProps } from './Header';
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ export type { DefinitionListProps, DefinitionListDefinition } from './definition
32
32
  export type { DimmerProps } from './dimmer';
33
33
  export type { DrawerProps } from './drawer';
34
34
  export type { DividerProps } from './divider';
35
+ export type { HeaderProps } from './header';
35
36
  export type { EmphasisProps } from './emphasis';
36
37
  export type { FieldProps } from './field/Field';
37
38
  export type { InfoProps } from './info';
@@ -1,7 +1,7 @@
1
- import ListItem, { List } from '.';
2
- import { Button } from '../index';
3
- import AvatarView from '../avatarView';
4
1
  import { FastFlag } from '@transferwise/icons';
2
+ import Button from '../button';
3
+ import AvatarView from '../avatarView';
4
+ import ListItem, { List, type ListItemProps } from '.';
5
5
 
6
6
  export default {
7
7
  component: ListItem,
@@ -9,22 +9,27 @@ export default {
9
9
  };
10
10
 
11
11
  export const LongText = () => {
12
+ const sharedProps: Partial<ListItemProps> = {
13
+ media: <AvatarView profileName="Super Pepa" badge={{ icon: <FastFlag /> }} />,
14
+ action: (
15
+ <Button v2 size="sm" onClick={() => {}}>
16
+ Action
17
+ </Button>
18
+ ),
19
+ };
20
+
12
21
  return (
13
22
  <div style={{ width: '35rem' }}>
14
23
  <List>
15
24
  <ListItem
16
- title="Wise"
25
+ {...sharedProps}
26
+ title="Default behaviour"
17
27
  value="This is a test of a long word dontbreakme dontbreakme dontbreakme breakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakme word."
18
- media={<AvatarView profileName="Super Pepa" badge={{ icon: <FastFlag /> }} />}
19
- action={
20
- <Button v2 size="sm" onClick={() => {}}>
21
- Action
22
- </Button>
23
- }
24
28
  />
25
29
 
26
30
  <ListItem
27
- title="Wise"
31
+ {...sharedProps}
32
+ title="Wrapping the long word with a 'span.text-word-break'"
28
33
  value={
29
34
  <>
30
35
  {'This is a test of a a very long word dontbreakme dontbreakme dontbreakme '}
@@ -34,12 +39,12 @@ export const LongText = () => {
34
39
  {' word.'}
35
40
  </>
36
41
  }
37
- media={<AvatarView profileName="Super Pepa" badge={{ icon: <FastFlag /> }} />}
38
- action={
39
- <Button v2 size="sm" onClick={() => {}}>
40
- Action
41
- </Button>
42
- }
42
+ />
43
+
44
+ <ListItem
45
+ {...sharedProps}
46
+ title="Using '&amp;shy;' HTML entitty"
47
+ value="This is a test of a a very long word dontbreakme dontbreakme dontbreakme break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me word."
43
48
  />
44
49
  </List>
45
50
  </div>
package/src/main.css CHANGED
@@ -2472,11 +2472,15 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
2472
2472
  border-bottom: 1px solid var(--color-border-neutral);
2473
2473
  margin-bottom: 8px;
2474
2474
  margin-bottom: var(--size-8);
2475
+ width: 100%;
2475
2476
  -moz-column-gap: 24px;
2476
2477
  column-gap: 24px;
2477
2478
  -moz-column-gap: var(--size-24);
2478
2479
  column-gap: var(--size-24);
2479
2480
  }
2481
+ .np-header--section {
2482
+ border-bottom: none;
2483
+ }
2480
2484
  .np-header__title {
2481
2485
  color: #5d7079;
2482
2486
  color: var(--color-content-secondary);
@@ -1,7 +1,6 @@
1
1
  import { Home as HomeIcon } from '@transferwise/icons';
2
2
  import { Status } from '../common';
3
- import Summary from './Summary';
4
- import { InfoPresentation } from '../info';
3
+ import Summary, { type SummaryProps } from '.';
5
4
 
6
5
  export default {
7
6
  component: Summary,
@@ -9,46 +8,47 @@ export default {
9
8
  };
10
9
 
11
10
  export const LongText = () => {
11
+ const sharedProps: Partial<SummaryProps> = {
12
+ action: {
13
+ text: 'Change address',
14
+ href: '#change-address',
15
+ 'aria-label': ' Click here to change address',
16
+ },
17
+ as: 'li',
18
+ icon: <HomeIcon size={24} />,
19
+ status: Status.NOT_DONE,
20
+ info: {
21
+ title: 'Title',
22
+ },
23
+ };
12
24
  return (
13
- <ul style={{ width: '30rem' }}>
25
+ <ul style={{ width: '34rem' }}>
14
26
  <Summary
15
- action={{
16
- text: 'Change address',
17
- href: '#change-address',
18
- 'aria-label': ' Click here to change address',
19
- }}
20
- as="li"
21
- description="This is a test of a a very long word dontbreakme dontbreakme dontbreakme breakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakme word."
22
- icon={<HomeIcon size={24} />}
23
- title="Summary title"
24
- status={Status.NOT_DONE}
25
- info={{
26
- title: 'Title',
27
- }}
27
+ title="Default behaviour"
28
+ description="This is a test of a a very long word dontbreakme dontbreakme dontbreakme dontbreakme dontbreakme breakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakme word."
29
+ {...sharedProps}
28
30
  />
29
31
 
30
32
  <Summary
31
- action={{
32
- text: 'Change address',
33
- href: '#change-address',
34
- 'aria-label': ' Click here to change address',
35
- }}
36
- as="li"
33
+ title="Wrapping the long word with a 'span.text-word-break'"
37
34
  description={
38
35
  <>
39
- {'This is a test of a a very long word dontbreakme dontbreakme dontbreakme '}
36
+ {
37
+ 'This is a test of a a very long word dontbreakme dontbreakme dontbreakme dontbreakme dontbreakme '
38
+ }
40
39
  <span className="text-word-break">
41
40
  breakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakmebreakme
42
41
  </span>
43
42
  {' word.'}
44
43
  </>
45
44
  }
46
- icon={<HomeIcon size={24} />}
47
- title="Summary title"
48
- status={Status.NOT_DONE}
49
- info={{
50
- title: 'Title',
51
- }}
45
+ {...sharedProps}
46
+ />
47
+
48
+ <Summary
49
+ title="Using '&amp;shy;' HTML entitty"
50
+ description="This is a test of a a very long word dontbreakme dontbreakme dontbreakme dontbreakme dontbreakme break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me&shy;break&shy;me word."
51
+ {...sharedProps}
52
52
  />
53
53
  </ul>
54
54
  );
@@ -1,5 +1,5 @@
1
1
  import { clsx } from 'clsx';
2
- import { LabelHTMLAttributes, LiHTMLAttributes, ReactHTML } from 'react';
2
+ import { forwardRef, LabelHTMLAttributes, LiHTMLAttributes } from 'react';
3
3
 
4
4
  import { TitleTypes, Typography, Heading } from '../common';
5
5
 
@@ -25,15 +25,29 @@ type Props = LabelHTMLAttributes<HTMLHeadingElement | HTMLSpanElement | HTMLLabe
25
25
  type?: TitleTypes;
26
26
  };
27
27
 
28
- function Title({ as, type = DEFAULT_TYPE, className, ...props }: Props) {
29
- const mapping = titleTypeMapping[type];
30
- const isTypeSupported = mapping !== undefined;
31
- if (isTypeSupported) {
32
- const HeaderTag = as ?? mapping;
33
- return <HeaderTag {...props} className={clsx(`np-text-${type}`, className)} />;
34
- }
35
- const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
36
- return <HeaderTag {...props} className={clsx(`np-text-${DEFAULT_TYPE}`, className)} />;
37
- }
28
+ const Title = forwardRef<HTMLHeadingElement | HTMLSpanElement | HTMLLabelElement, Props>(
29
+ ({ as, type = DEFAULT_TYPE, className, ...props }, ref) => {
30
+ const mapping = titleTypeMapping[type as keyof typeof titleTypeMapping];
31
+ const isTypeSupported = mapping !== undefined;
32
+ if (isTypeSupported) {
33
+ const HeaderTag = as ?? mapping;
34
+ return (
35
+ <HeaderTag
36
+ ref={ref as React.Ref<any>}
37
+ {...props}
38
+ className={clsx(`np-text-${type}`, className)}
39
+ />
40
+ );
41
+ }
42
+ const HeaderTag = as ?? titleTypeMapping[DEFAULT_TYPE];
43
+ return (
44
+ <HeaderTag
45
+ ref={ref as React.Ref<any>}
46
+ {...props}
47
+ className={clsx(`np-text-${DEFAULT_TYPE}`, className)}
48
+ />
49
+ );
50
+ },
51
+ );
38
52
 
39
53
  export default Title;
@@ -184,7 +184,9 @@ describe('Upload Component', () => {
184
184
 
185
185
  await waitForUpload();
186
186
 
187
- expect(await screen.findByText(/File type not supported. Please try again with a different file/i)).toBeInTheDocument();
187
+ expect(
188
+ await screen.findByText(/File type not supported. Please try again with a different file/i),
189
+ ).toBeInTheDocument();
188
190
  });
189
191
 
190
192
  test('should handle canceling the upload', async () => {
@@ -27,7 +27,7 @@ describe('ProcessingStep', () => {
27
27
  expect(processing).toBeInTheDocument();
28
28
  });
29
29
  });
30
-
30
+
31
31
  test('renders ProcessIndicator with default status', () => {
32
32
  expect(screen.getByText(PROCESSING_STEP_PROPS.psProcessingText)).toBeInTheDocument();
33
33
  });
@@ -66,11 +66,11 @@ describe('uploadImageStep', () => {
66
66
  const errorIconLabel = 'Error icon label';
67
67
  const errorMessage = 'Maximum filesize is 2MB.';
68
68
  const { container } = render(
69
- <UploadImageStep
70
- {...UPLOADIMAGE_STEP_PROPS}
69
+ <UploadImageStep
70
+ {...UPLOADIMAGE_STEP_PROPS}
71
71
  errorMessage={errorMessage}
72
- errorIconLabel={errorIconLabel}
73
- />
72
+ errorIconLabel={errorIconLabel}
73
+ />,
74
74
  );
75
75
 
76
76
  expect(screen.getByLabelText(errorIconLabel)).toBeInTheDocument();