@transferwise/components 0.0.0-experimental-b2dc1ea → 0.0.0-experimental-4c86474
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/build/divider/Divider.js +33 -0
- package/build/divider/Divider.js.map +1 -0
- package/build/divider/Divider.mjs +31 -0
- package/build/divider/Divider.mjs.map +1 -0
- package/build/header/Header.js +34 -89
- package/build/header/Header.js.map +1 -1
- package/build/header/Header.mjs +31 -85
- package/build/header/Header.mjs.map +1 -1
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/build/index.mjs +2 -1
- package/build/index.mjs.map +1 -1
- package/build/link/Link.js +3 -5
- package/build/link/Link.js.map +1 -1
- package/build/link/Link.mjs +3 -5
- package/build/link/Link.mjs.map +1 -1
- package/build/main.css +24 -3
- package/build/selectOption/SelectOption.js +1 -1
- package/build/selectOption/SelectOption.js.map +1 -1
- package/build/selectOption/SelectOption.mjs +1 -1
- package/build/styles/divider/Divider.css +24 -0
- package/build/styles/header/Header.css +0 -3
- package/build/styles/main.css +24 -3
- package/build/title/Title.js +3 -6
- package/build/title/Title.js.map +1 -1
- package/build/title/Title.mjs +3 -6
- package/build/title/Title.mjs.map +1 -1
- package/build/types/divider/Divider.d.ts +42 -0
- package/build/types/divider/Divider.d.ts.map +1 -0
- package/build/types/divider/index.d.ts +3 -0
- package/build/types/divider/index.d.ts.map +1 -0
- package/build/types/header/Header.d.ts +10 -34
- package/build/types/header/Header.d.ts.map +1 -1
- package/build/types/header/index.d.ts +0 -1
- package/build/types/header/index.d.ts.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/link/Link.d.ts +1 -3
- package/build/types/link/Link.d.ts.map +1 -1
- package/build/types/title/Title.d.ts +4 -3
- package/build/types/title/Title.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/divider/Divider.css +24 -0
- package/src/divider/Divider.less +31 -0
- package/src/divider/Divider.spec.tsx +85 -0
- package/src/divider/Divider.story.tsx +29 -0
- package/src/divider/Divider.tsx +72 -0
- package/src/divider/index.ts +2 -0
- package/src/header/Header.css +0 -3
- package/src/header/Header.less +0 -4
- package/src/header/Header.spec.tsx +0 -33
- package/src/header/Header.story.tsx +40 -54
- package/src/header/Header.tsx +60 -126
- package/src/header/index.ts +0 -1
- package/src/index.ts +2 -1
- package/src/link/Link.tsx +27 -29
- package/src/main.css +24 -3
- package/src/main.less +1 -0
- package/src/ssr.spec.js +1 -0
- package/src/title/Title.tsx +11 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-4c86474",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
"rollup": "^4.18.1",
|
|
93
93
|
"rollup-preserve-directives": "^1.1.1",
|
|
94
94
|
"storybook": "^8.2.2",
|
|
95
|
+
"@transferwise/less-config": "3.1.0",
|
|
95
96
|
"@transferwise/neptune-css": "14.18.0",
|
|
96
|
-
"@wise/components-theming": "1.6.0"
|
|
97
|
-
"@transferwise/less-config": "3.1.0"
|
|
97
|
+
"@wise/components-theming": "1.6.0"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"@transferwise/icons": "^3.7.0",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.wds-Divider {
|
|
2
|
+
--Divider-border-width: var(--size-4);
|
|
3
|
+
--Divider-border-color: var(--color-border-neutral);
|
|
4
|
+
--Divider-border-style: solid;
|
|
5
|
+
--Divider-dash-length: var(--size-4);
|
|
6
|
+
--Divider-dash-spacing: var(--size-4);
|
|
7
|
+
border-top: 4px solid rgba(0,0,0,0.10196);
|
|
8
|
+
border-top: var(--Divider-border-width) var(--Divider-border-style) var(--Divider-border-color);
|
|
9
|
+
display: inline-block;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
height: 4px;
|
|
13
|
+
height: var(--Divider-border-width);
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
.wds-Divider--content {
|
|
17
|
+
border-top: none;
|
|
18
|
+
background: repeating-linear-gradient(to right, var(--Divider-border-color), var(--Divider-border-color) var(--Divider-dash-length), transparent var(--Divider-dash-length), transparent calc(var(--Divider-dash-length) + var(--Divider-dash-spacing)));
|
|
19
|
+
height: var(--Divider-border-width);
|
|
20
|
+
}
|
|
21
|
+
.wds-Divider--content,
|
|
22
|
+
.wds-Divider--subsection {
|
|
23
|
+
--Divider-border-width: 1px;
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.wds-Divider {
|
|
2
|
+
--Divider-border-width: var(--size-4);
|
|
3
|
+
--Divider-border-color: var(--color-border-neutral);
|
|
4
|
+
--Divider-border-style: solid;
|
|
5
|
+
--Divider-dash-length: var(--size-4);
|
|
6
|
+
--Divider-dash-spacing: var(--size-4);
|
|
7
|
+
|
|
8
|
+
border-top: var(--Divider-border-width) var(--Divider-border-style) var(--Divider-border-color);
|
|
9
|
+
display: inline-block;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
height: var(--Divider-border-width);
|
|
13
|
+
width: 100%;
|
|
14
|
+
|
|
15
|
+
&--content {
|
|
16
|
+
border-top: none;
|
|
17
|
+
background: repeating-linear-gradient(
|
|
18
|
+
to right,
|
|
19
|
+
var(--Divider-border-color),
|
|
20
|
+
var(--Divider-border-color) var(--Divider-dash-length),
|
|
21
|
+
transparent var(--Divider-dash-length),
|
|
22
|
+
transparent calc(var(--Divider-dash-length) + var(--Divider-dash-spacing))
|
|
23
|
+
);
|
|
24
|
+
height: var(--Divider-border-width);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&--content,
|
|
28
|
+
&--subsection {
|
|
29
|
+
--Divider-border-width: 1px;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import Divider, { DividerProps } from './Divider';
|
|
4
|
+
|
|
5
|
+
describe('Divider', () => {
|
|
6
|
+
const defaultProps: DividerProps = {
|
|
7
|
+
testId: 'test-divider',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let rerenderDivider: (props?: DividerProps) => void;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
const { rerender } = render(<Divider {...defaultProps} />);
|
|
14
|
+
|
|
15
|
+
rerenderDivider = (props?: DividerProps) => {
|
|
16
|
+
rerender(<Divider {...defaultProps} {...props} />);
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const renderDivider = (props: Partial<DividerProps> = {}) => {
|
|
21
|
+
return render(<Divider {...props} />);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
it('renders', () => {
|
|
25
|
+
expect(screen.getByTestId('test-divider')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByTestId('test-divider')).toHaveClass('wds-Divider');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders as an `div`', () => {
|
|
30
|
+
const props: DividerProps = {
|
|
31
|
+
as: 'div',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(screen.getByTestId('test-divider')).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByTestId('test-divider')).not.toHaveAttribute('role', 'separator');
|
|
36
|
+
expect(screen.getByTestId('test-divider')).not.toHaveAttribute(
|
|
37
|
+
'aria-orientation',
|
|
38
|
+
'horizontal',
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
rerenderDivider(props);
|
|
42
|
+
|
|
43
|
+
expect(screen.getByTestId('test-divider')).toHaveAttribute('role', 'separator');
|
|
44
|
+
expect(screen.getByTestId('test-divider')).toHaveAttribute('aria-orientation', 'horizontal');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders with a custom class', () => {
|
|
48
|
+
const props: DividerProps = {
|
|
49
|
+
className: 'custom-class',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId('test-divider')).not.toHaveClass('custom-class');
|
|
53
|
+
|
|
54
|
+
rerenderDivider(props);
|
|
55
|
+
|
|
56
|
+
expect(screen.getByTestId('test-divider')).toHaveClass('custom-class');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders with content variant class', () => {
|
|
60
|
+
const props: DividerProps = {
|
|
61
|
+
variant: 'content',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
expect(screen.getByTestId('test-divider')).not.toHaveClass('wds-Divider--content');
|
|
65
|
+
|
|
66
|
+
rerenderDivider(props);
|
|
67
|
+
|
|
68
|
+
expect(screen.getByTestId('test-divider')).toHaveClass('wds-Divider', 'wds-Divider--content');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('renders with subsection variant class', () => {
|
|
72
|
+
const props: DividerProps = {
|
|
73
|
+
variant: 'subsection',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId('test-divider')).not.toHaveClass('wds-Divider--subsection');
|
|
77
|
+
|
|
78
|
+
rerenderDivider(props);
|
|
79
|
+
|
|
80
|
+
expect(screen.getByTestId('test-divider')).toHaveClass(
|
|
81
|
+
'wds-Divider',
|
|
82
|
+
'wds-Divider--subsection',
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import Divider from './Divider';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Divider> = {
|
|
6
|
+
component: Divider,
|
|
7
|
+
title: 'Layouts/Divider',
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
actions: { argTypesRegex: null },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof Divider>;
|
|
16
|
+
|
|
17
|
+
export const Basic: Story = {};
|
|
18
|
+
|
|
19
|
+
export const Subsection: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
variant: 'subsection',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Content: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
variant: 'content',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import React, { FunctionComponent } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface DividerProps {
|
|
5
|
+
/** Option prop to specify DOM render element */
|
|
6
|
+
as?: 'hr' | 'div';
|
|
7
|
+
|
|
8
|
+
/** Optional prop to specify classNames onto the Divider */
|
|
9
|
+
className?: string;
|
|
10
|
+
|
|
11
|
+
/** Optional prop to specify the ID used for testing */
|
|
12
|
+
testId?: string;
|
|
13
|
+
|
|
14
|
+
/** Optional prop to specify the variant of the Divider */
|
|
15
|
+
variant?: 'content' | 'subsection' | 'section';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Divider component
|
|
20
|
+
*
|
|
21
|
+
* The Divider component is used to visually separate different sections of content.
|
|
22
|
+
* It can be rendered as a horizontal line and supports various styles such as
|
|
23
|
+
* section, subsection, and content. Additionally, the Divider can include custom styles.
|
|
24
|
+
*
|
|
25
|
+
* @component
|
|
26
|
+
* @param {'hr' | 'div'} [as='hr'] - Option prop to specify DOM render element.
|
|
27
|
+
* @param {string} [className] - Optional prop to specify classNames onto the Divider.
|
|
28
|
+
* @param {string} [testId] - Optional prop to specify the ID used for testing.
|
|
29
|
+
* @param {'section' | 'subsection' | 'content'} [variant='section'] - Optional prop to specify the variant of the Divider.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Example usage:
|
|
33
|
+
* import Divider from './Divider';
|
|
34
|
+
*
|
|
35
|
+
* function App() {
|
|
36
|
+
* return (
|
|
37
|
+
* <div>
|
|
38
|
+
* <p>Content above the divider</p>
|
|
39
|
+
* <Divider variant="content" />
|
|
40
|
+
* <p>Content below the divider</p>
|
|
41
|
+
* <Divider variant="subsection" />
|
|
42
|
+
* </div>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
const Divider: FunctionComponent<DividerProps> = ({
|
|
47
|
+
as = 'hr',
|
|
48
|
+
className,
|
|
49
|
+
testId,
|
|
50
|
+
variant = 'section',
|
|
51
|
+
...props
|
|
52
|
+
}) => {
|
|
53
|
+
const Element = as;
|
|
54
|
+
const variantClass = variant && variant !== 'section' ? `wds-Divider--${variant}` : '';
|
|
55
|
+
|
|
56
|
+
const commonProps = {
|
|
57
|
+
className: clsx('wds-Divider', variantClass, className),
|
|
58
|
+
'data-testid': testId,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const divProps = {
|
|
62
|
+
...commonProps,
|
|
63
|
+
'aria-orientation': 'horizontal',
|
|
64
|
+
role: 'separator',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const componentProps = Element === 'div' ? divProps : commonProps;
|
|
68
|
+
|
|
69
|
+
return <Element {...componentProps} {...props} />;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default Divider;
|
package/src/header/Header.css
CHANGED
package/src/header/Header.less
CHANGED
|
@@ -75,39 +75,6 @@ describe('Header', () => {
|
|
|
75
75
|
expect(screen.getByRole('heading', { name: 'Header title', level: 3 })).toBeInTheDocument();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it('renders header with section variant', () => {
|
|
79
|
-
render(<Header title="Header title" variant="section" />);
|
|
80
|
-
|
|
81
|
-
const header = screen.getByRole('heading', { name: 'Header title' });
|
|
82
|
-
expect(header).toHaveClass('np-header--section');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('warns if Header as legend is not inside a fieldset', () => {
|
|
86
|
-
const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation();
|
|
87
|
-
|
|
88
|
-
render(<Header as="legend" title="Header title" />);
|
|
89
|
-
|
|
90
|
-
expect(consoleWarnMock).toHaveBeenCalledWith(
|
|
91
|
-
'Legends should be the first child in a fieldset, and this is not possible when including an action',
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
consoleWarnMock.mockRestore();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('does not warn if Header as legend is inside a fieldset', () => {
|
|
98
|
-
const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation();
|
|
99
|
-
|
|
100
|
-
render(
|
|
101
|
-
<fieldset>
|
|
102
|
-
<Header as="legend" title="Header title" />
|
|
103
|
-
</fieldset>,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(consoleWarnMock).not.toHaveBeenCalled();
|
|
107
|
-
|
|
108
|
-
consoleWarnMock.mockRestore();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
78
|
it('runs onClick if specified even when it got href prop', async () => {
|
|
112
79
|
const callback = jest.fn();
|
|
113
80
|
render(
|
|
@@ -1,67 +1,53 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
|
|
3
1
|
import Header from './Header';
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
export default {
|
|
6
4
|
component: Header,
|
|
7
5
|
title: 'Typography/Header',
|
|
8
|
-
tags: ['autodocs'],
|
|
9
|
-
parameters: {
|
|
10
|
-
actions: { argTypesRegex: null },
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
type Story = StoryObj<typeof Header>;
|
|
16
|
-
|
|
17
|
-
export const Basic: Story = {
|
|
18
|
-
args: {
|
|
19
|
-
title: 'Group header',
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const WithAction: Story = {
|
|
24
|
-
args: {
|
|
25
|
-
...Basic.args,
|
|
26
|
-
action: {
|
|
27
|
-
'aria-label': 'Magic',
|
|
28
|
-
text: 'Action',
|
|
29
|
-
onClick: () => alert('Action!'),
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
6
|
};
|
|
33
7
|
|
|
34
|
-
export const
|
|
35
|
-
|
|
36
|
-
...Basic.args,
|
|
37
|
-
action: {
|
|
38
|
-
text: 'Link',
|
|
39
|
-
href: 'https://wise.com',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
8
|
+
export const Basic = () => {
|
|
9
|
+
return <Header title="Header title" />;
|
|
42
10
|
};
|
|
43
11
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
12
|
+
export const WithAction = () => {
|
|
13
|
+
return (
|
|
14
|
+
<Header
|
|
15
|
+
title="Header title"
|
|
16
|
+
action={{
|
|
17
|
+
'aria-label': 'Magic',
|
|
18
|
+
text: 'Click me!',
|
|
19
|
+
onClick: () => alert('Action!'),
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
49
23
|
};
|
|
50
24
|
|
|
51
|
-
export const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
25
|
+
export const WithActionAsLink = () => {
|
|
26
|
+
return (
|
|
27
|
+
<Header
|
|
28
|
+
title="Header title"
|
|
29
|
+
action={{
|
|
30
|
+
text: 'This is a link',
|
|
31
|
+
href: 'https://wise.com',
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
56
35
|
};
|
|
57
36
|
|
|
58
|
-
export const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
37
|
+
export const WithActionAsLinkPreventingNavigationWithTracking = () => {
|
|
38
|
+
return (
|
|
39
|
+
<Header
|
|
40
|
+
title="Header title"
|
|
41
|
+
action={{
|
|
42
|
+
text: 'This is a link',
|
|
43
|
+
href: 'https://wise.com',
|
|
44
|
+
onClick: (event: React.MouseEvent<HTMLElement>) => {
|
|
45
|
+
alert('Running onClick handler');
|
|
46
|
+
|
|
47
|
+
// we can stop the navigation from happening as onClick will always run before href redirect
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
},
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
67
53
|
};
|
package/src/header/Header.tsx
CHANGED
|
@@ -1,158 +1,92 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
2
|
|
|
3
3
|
import { ActionButtonProps } from '../actionButton/ActionButton';
|
|
4
|
+
import Button from '../button';
|
|
4
5
|
import { AriaLabelProperty, CommonProps, Heading, LinkProps, Typography } from '../common';
|
|
5
6
|
import Link from '../link';
|
|
6
|
-
import Button from '../button';
|
|
7
7
|
import Title from '../title';
|
|
8
|
-
import React, { useEffect, useRef, FunctionComponent } from 'react';
|
|
9
8
|
|
|
10
9
|
type ActionProps = AriaLabelProperty & {
|
|
11
10
|
text: string;
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
type ButtonActionProps = ActionProps & ActionButtonProps;
|
|
14
|
+
|
|
15
15
|
type LinkActionProps = ActionProps & LinkProps;
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export type HeaderProps = CommonProps & {
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
* is provided, a `Link` will be rendered instead of an `ActionButton`.
|
|
19
|
+
* When the `href` property is provided to the `action`, we will render a `Link` instead of a `ActionButton`.
|
|
21
20
|
*/
|
|
22
21
|
action?: ButtonActionProps | LinkActionProps;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Override the heading element rendered for the title, useful to specify the semantics of your header.
|
|
24
|
+
*
|
|
25
|
+
* @default "h5"
|
|
26
|
+
*/
|
|
25
27
|
as?: Heading | 'legend';
|
|
26
|
-
|
|
27
|
-
/** Optional prop to specify classNames onto the Header */
|
|
28
|
-
className?: string;
|
|
29
|
-
|
|
30
|
-
/** Optional prop to specify the ID used for testing */
|
|
31
|
-
testId?: string;
|
|
32
|
-
|
|
33
|
-
/** Required prop to set the title of the Header. */
|
|
34
28
|
title: string;
|
|
29
|
+
};
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Renders a header action which can be either a button or a link.
|
|
42
|
-
*
|
|
43
|
-
* @param {Object} props - The properties object.
|
|
44
|
-
* @param {ButtonActionProps | LinkActionProps} props.action - The action object which can be either a button or a link.
|
|
45
|
-
* @returns {JSX.Element} The rendered header action component.
|
|
46
|
-
*/
|
|
47
|
-
const HeaderAction = React.forwardRef(
|
|
48
|
-
(
|
|
49
|
-
{ action }: { action: ButtonActionProps | LinkActionProps },
|
|
50
|
-
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
|
|
51
|
-
) => {
|
|
52
|
-
const { 'aria-label': ariaLabel, text, onClick } = action;
|
|
53
|
-
|
|
54
|
-
if ('href' in action) {
|
|
55
|
-
const { href, target, onClick: linkOnClick } = action;
|
|
56
|
-
return (
|
|
57
|
-
<Link
|
|
58
|
-
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
59
|
-
href={href}
|
|
60
|
-
target={target}
|
|
61
|
-
aria-label={ariaLabel}
|
|
62
|
-
onClick={linkOnClick}
|
|
63
|
-
>
|
|
64
|
-
{text}
|
|
65
|
-
</Link>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
31
|
+
const HeaderAction = ({ action }: { action: ButtonActionProps | LinkActionProps }) => {
|
|
32
|
+
const props = {
|
|
33
|
+
'aria-label': action['aria-label'],
|
|
34
|
+
};
|
|
68
35
|
|
|
36
|
+
if ('href' in action) {
|
|
69
37
|
return (
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
priority="tertiary"
|
|
74
|
-
size="sm"
|
|
75
|
-
aria-label={ariaLabel}
|
|
76
|
-
onClick={onClick}
|
|
77
|
-
>
|
|
78
|
-
{text}
|
|
79
|
-
</Button>
|
|
38
|
+
<Link href={action.href} target={action.target} onClick={action.onClick} {...props}>
|
|
39
|
+
{action.text}
|
|
40
|
+
</Link>
|
|
80
41
|
);
|
|
81
|
-
}
|
|
82
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Button
|
|
46
|
+
className="np-header__button"
|
|
47
|
+
priority="tertiary"
|
|
48
|
+
size="sm"
|
|
49
|
+
onClick={action.onClick}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{action.text}
|
|
53
|
+
</Button>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
83
56
|
|
|
84
57
|
/**
|
|
85
|
-
* Header component
|
|
86
|
-
*
|
|
87
|
-
* The header component is used to render a section header with an optional action button or link.
|
|
88
|
-
* The header component can be rendered as a `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, or `legend` element.
|
|
89
58
|
*
|
|
90
|
-
*
|
|
91
|
-
* @param {ButtonActionProps | LinkActionProps} [action] - Optional prop to specify the action button or link.
|
|
92
|
-
* @param {Heading | 'legend'} [as='h5'] - Optional prop to override the heading element rendered for the title.
|
|
93
|
-
* @param {string} title - Required prop to set the title of the section header.
|
|
94
|
-
* @param {'group' | 'section'} [variant='group'] - Optional prop to specify the variant of the section header.
|
|
95
|
-
* @param {string} [className] - Optional prop to specify classNames onto the Header.
|
|
96
|
-
* @param {string} [testId] - Optional prop to specify the ID used for testing.
|
|
59
|
+
* Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
|
|
97
60
|
*
|
|
98
|
-
* @example
|
|
99
|
-
* // Example usage:
|
|
100
|
-
* import Header from './Header';
|
|
101
|
-
*
|
|
102
|
-
* function App() {
|
|
103
|
-
* return (
|
|
104
|
-
* <Header title="Header" />
|
|
105
|
-
* );
|
|
106
|
-
* }
|
|
107
61
|
*/
|
|
108
|
-
const Header:
|
|
109
|
-
(
|
|
110
|
-
{ as = 'h5', action, className, testId, title, variant = 'group', ...props },
|
|
111
|
-
ref: React.Ref<HTMLDivElement | HTMLHeadingElement | HTMLLegendElement>,
|
|
112
|
-
) => {
|
|
113
|
-
const internalRef = useRef<HTMLLegendElement>(null);
|
|
114
|
-
const variantTypography =
|
|
115
|
-
variant === 'section' ? Typography.TITLE_SUBSECTION : Typography.TITLE_GROUP;
|
|
116
|
-
const headerClasses = clsx('np-header', className, {
|
|
117
|
-
'np-header--section': variant === 'section',
|
|
118
|
-
'np-header__title': !action || as === 'legend',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const commonProps = {
|
|
122
|
-
className: headerClasses,
|
|
123
|
-
'data-testid': testId,
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (as === 'legend' && internalRef.current) {
|
|
128
|
-
const { parentElement } = internalRef.current;
|
|
129
|
-
if (!parentElement || parentElement.tagName.toLowerCase() !== 'fieldset') {
|
|
130
|
-
console.warn(
|
|
131
|
-
'Legends should be the first child in a fieldset, and this is not possible when including an action',
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}, [as]);
|
|
136
|
-
|
|
137
|
-
if (!action || as === 'legend') {
|
|
138
|
-
return (
|
|
139
|
-
<Title ref={internalRef} as={as} type={variantTypography} {...commonProps} {...props}>
|
|
140
|
-
{title}
|
|
141
|
-
</Title>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const actionRef = React.createRef<HTMLButtonElement | HTMLAnchorElement>();
|
|
146
|
-
|
|
62
|
+
export const Header = ({ action, as = 'h5', title, className }: HeaderProps) => {
|
|
63
|
+
if (!action) {
|
|
147
64
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
65
|
+
<Title
|
|
66
|
+
as={as}
|
|
67
|
+
type={Typography.TITLE_GROUP}
|
|
68
|
+
className={clsx('np-header', 'np-header__title', className)}
|
|
69
|
+
>
|
|
70
|
+
{title}
|
|
71
|
+
</Title>
|
|
154
72
|
);
|
|
155
|
-
}
|
|
156
|
-
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (as === 'legend') {
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.warn(
|
|
78
|
+
'Legends should be the first child in a fieldset, and this is not possible when including an action',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className={clsx('np-header', className)}>
|
|
84
|
+
<Title as={as} type={Typography.TITLE_GROUP} className="np-header__title">
|
|
85
|
+
{title}
|
|
86
|
+
</Title>
|
|
87
|
+
<HeaderAction action={action} />
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
157
91
|
|
|
158
92
|
export default Header;
|
package/src/header/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type { DecisionProps } from './decision/Decision';
|
|
|
28
28
|
export type { DefinitionListProps, DefinitionListDefinition } from './definitionList';
|
|
29
29
|
export type { DimmerProps } from './dimmer';
|
|
30
30
|
export type { DrawerProps } from './drawer';
|
|
31
|
-
export type {
|
|
31
|
+
export type { DividerProps } from './divider';
|
|
32
32
|
export type { EmphasisProps } from './emphasis';
|
|
33
33
|
export type { FieldProps } from './field/Field';
|
|
34
34
|
export type { InfoProps } from './info';
|
|
@@ -125,6 +125,7 @@ export { default as Decision } from './decision';
|
|
|
125
125
|
export { default as DefinitionList } from './definitionList';
|
|
126
126
|
export { default as Dimmer } from './dimmer';
|
|
127
127
|
export { default as Display } from './display';
|
|
128
|
+
export { default as Divider } from './divider';
|
|
128
129
|
export { default as Drawer } from './drawer';
|
|
129
130
|
export { default as DropFade } from './dropFade';
|
|
130
131
|
export { default as Emphasis } from './emphasis';
|