uikit-react-public 0.43.4 → 0.44.2
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/dist/components/Calendar/Calendar.d.ts +1 -1
- package/dist/components/Calendar/Calendar.stories.d.ts +6 -1
- package/dist/components/Calendar/Calendar.types.d.ts +1 -0
- package/dist/components/Datepicker/Datepicker.stories.d.ts +5 -0
- package/dist/components/Datepicker/Datepicker.types.d.ts +1 -0
- package/dist/components/Datepicker/subcomponents/CustomDatepicker.d.ts +2 -1
- package/dist/components/Header/Header.d.ts +2 -1
- package/dist/components/Header/Header.stories.d.ts +5 -1
- package/dist/components/HeaderNew/Header.d.ts +2 -1
- package/dist/components/NavigationBar/NavigationBar.d.ts +12 -0
- package/dist/components/NavigationBar/NavigationBar.stories.d.ts +14 -0
- package/dist/components/NavigationBar/NavigationBarItem.d.ts +19 -0
- package/dist/components/NavigationBar/__tests__/NavigationBar.test.d.ts +1 -0
- package/dist/components/NavigationBar/index.d.ts +2 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/index.js +5645 -5442
- package/lib/components/Calendar/Calendar.stories.tsx +13 -0
- package/lib/components/Calendar/Calendar.tsx +25 -20
- package/lib/components/Calendar/Calendar.types.ts +2 -1
- package/lib/components/Calendar/__tests__/Calendar.test.tsx +39 -0
- package/lib/components/Datepicker/Datepicker.stories.tsx +26 -18
- package/lib/components/Datepicker/Datepicker.types.ts +1 -0
- package/lib/components/Datepicker/__tests__/Datepicker.test.tsx +64 -0
- package/lib/components/Datepicker/subcomponents/CustomDatepicker.tsx +8 -0
- package/lib/components/Header/Header.mdx +8 -1
- package/lib/components/Header/Header.stories.tsx +13 -0
- package/lib/components/Header/Header.tsx +3 -0
- package/lib/components/Header/__tests__/Header.test.tsx +22 -0
- package/lib/components/HeaderNew/Header.tsx +7 -1
- package/lib/components/HeaderNew/__tests__/Header.test.tsx +41 -0
- package/lib/components/NavigationBar/NavigationBar.stories.tsx +99 -0
- package/lib/components/NavigationBar/NavigationBar.tsx +76 -0
- package/lib/components/NavigationBar/NavigationBarItem.tsx +236 -0
- package/lib/components/NavigationBar/__tests__/NavigationBar.test.tsx +90 -0
- package/lib/components/NavigationBar/index.ts +5 -0
- package/lib/components/index.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { css } from '@emotion/css';
|
|
3
|
+
import NavigationBar from './NavigationBar';
|
|
4
|
+
import Icon from '../Icon';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/NavigationBar',
|
|
8
|
+
component: NavigationBar,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
},
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
} satisfies Meta<typeof NavigationBar>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
const pageStyle = css`
|
|
19
|
+
min-height: 160px;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<div className={pageStyle}>
|
|
25
|
+
<NavigationBar>
|
|
26
|
+
<NavigationBar.Item href='/courses'>Courses</NavigationBar.Item>
|
|
27
|
+
<NavigationBar.Item
|
|
28
|
+
href='/research'
|
|
29
|
+
active
|
|
30
|
+
>
|
|
31
|
+
Research
|
|
32
|
+
</NavigationBar.Item>
|
|
33
|
+
<NavigationBar.Item
|
|
34
|
+
href='/open-days'
|
|
35
|
+
icon={<Icon.ChevronDown />}
|
|
36
|
+
>
|
|
37
|
+
Open days
|
|
38
|
+
</NavigationBar.Item>
|
|
39
|
+
<NavigationBar.Item href='/events'>Events</NavigationBar.Item>
|
|
40
|
+
<NavigationBar.Item
|
|
41
|
+
href='/news'
|
|
42
|
+
disabled
|
|
43
|
+
>
|
|
44
|
+
News
|
|
45
|
+
</NavigationBar.Item>
|
|
46
|
+
</NavigationBar>
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Wrapped: Story = {
|
|
52
|
+
render: () => (
|
|
53
|
+
<div
|
|
54
|
+
className={css`
|
|
55
|
+
width: 720px;
|
|
56
|
+
`}
|
|
57
|
+
>
|
|
58
|
+
<NavigationBar>
|
|
59
|
+
<NavigationBar.Item
|
|
60
|
+
href='/courses'
|
|
61
|
+
active
|
|
62
|
+
>
|
|
63
|
+
Courses
|
|
64
|
+
</NavigationBar.Item>
|
|
65
|
+
<NavigationBar.Item href='/research'>Research</NavigationBar.Item>
|
|
66
|
+
<NavigationBar.Item href='/open-days'>Open days</NavigationBar.Item>
|
|
67
|
+
<NavigationBar.Item href='/events'>Events</NavigationBar.Item>
|
|
68
|
+
<NavigationBar.Item href='/news'>News</NavigationBar.Item>
|
|
69
|
+
<NavigationBar.Item href='/blogs'>IOE blogs</NavigationBar.Item>
|
|
70
|
+
<NavigationBar.Item href='/work-with-us'>
|
|
71
|
+
Work with us
|
|
72
|
+
</NavigationBar.Item>
|
|
73
|
+
<NavigationBar.Item href='/departments-and-centres'>
|
|
74
|
+
Departments and Centres
|
|
75
|
+
</NavigationBar.Item>
|
|
76
|
+
<NavigationBar.Item href='/about'>About IOE</NavigationBar.Item>
|
|
77
|
+
</NavigationBar>
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithRouterLinks: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<NavigationBar>
|
|
85
|
+
<NavigationBar.Item
|
|
86
|
+
active
|
|
87
|
+
asChild
|
|
88
|
+
>
|
|
89
|
+
<a href='/overview'>Overview</a>
|
|
90
|
+
</NavigationBar.Item>
|
|
91
|
+
<NavigationBar.Item asChild>
|
|
92
|
+
<a href='/modules'>Modules</a>
|
|
93
|
+
</NavigationBar.Item>
|
|
94
|
+
<NavigationBar.Item asChild>
|
|
95
|
+
<a href='/people'>People</a>
|
|
96
|
+
</NavigationBar.Item>
|
|
97
|
+
</NavigationBar>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
NamedExoticComponent,
|
|
5
|
+
isValidElement,
|
|
6
|
+
memo,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { css, cx } from '@emotion/css';
|
|
9
|
+
import { useTheme } from '../../theme';
|
|
10
|
+
import NavigationBarItem, { NavigationBarItemProps } from './NavigationBarItem';
|
|
11
|
+
|
|
12
|
+
export const NAME = 'ucl-uikit-navigation-bar';
|
|
13
|
+
|
|
14
|
+
export interface NavigationBarProps extends HTMLAttributes<HTMLElement> {
|
|
15
|
+
testId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const NavigationBarBase = ({
|
|
19
|
+
testId = NAME,
|
|
20
|
+
className,
|
|
21
|
+
children,
|
|
22
|
+
'aria-label': ariaLabel = 'Primary navigation',
|
|
23
|
+
...props
|
|
24
|
+
}: NavigationBarProps) => {
|
|
25
|
+
const [theme] = useTheme();
|
|
26
|
+
const items = Children.toArray(children).filter(isValidElement);
|
|
27
|
+
|
|
28
|
+
const baseStyle = css`
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
display: flex;
|
|
31
|
+
width: 100%;
|
|
32
|
+
padding: 0 ${theme.padding.p16};
|
|
33
|
+
align-items: flex-end;
|
|
34
|
+
background-color: ${theme.colour.surface.primary};
|
|
35
|
+
border-bottom: ${theme.border.b1} solid ${theme.colour.border.subtle};
|
|
36
|
+
|
|
37
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
38
|
+
padding: 0 ${theme.padding.p48};
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const listStyle = css`
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 2px 0;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 0;
|
|
49
|
+
list-style: none;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const style = cx(NAME, baseStyle, className);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<nav
|
|
56
|
+
className={style}
|
|
57
|
+
data-testid={testId}
|
|
58
|
+
aria-label={ariaLabel}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<ul className={listStyle}>{items}</ul>
|
|
62
|
+
</nav>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export interface NavigationBarComponent extends NamedExoticComponent<NavigationBarProps> {
|
|
67
|
+
Item: typeof NavigationBarItem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const NavigationBar = Object.assign(memo(NavigationBarBase), {
|
|
71
|
+
Item: NavigationBarItem,
|
|
72
|
+
}) as NavigationBarComponent;
|
|
73
|
+
|
|
74
|
+
export type { NavigationBarItemProps };
|
|
75
|
+
|
|
76
|
+
export default NavigationBar;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
ComponentPropsWithoutRef,
|
|
4
|
+
ElementType,
|
|
5
|
+
ReactElement,
|
|
6
|
+
ReactNode,
|
|
7
|
+
cloneElement,
|
|
8
|
+
forwardRef,
|
|
9
|
+
isValidElement,
|
|
10
|
+
memo,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { css, cx } from '@emotion/css';
|
|
13
|
+
import { useTheme } from '../../theme';
|
|
14
|
+
import Text from '../Text';
|
|
15
|
+
import BaseLink, { BaseLinkProps } from '../Link/BaseLink';
|
|
16
|
+
import type { IconProps } from '../Icon';
|
|
17
|
+
|
|
18
|
+
export const NAME = 'ucl-uikit-navigation-bar__item';
|
|
19
|
+
|
|
20
|
+
type NavigationBarItemOwnProps = {
|
|
21
|
+
active?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
icon?: ReactElement<IconProps>;
|
|
24
|
+
iconPosition?: 'left' | 'right';
|
|
25
|
+
testId?: string;
|
|
26
|
+
className?: string;
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type NavigationBarItemProps<C extends ElementType = 'a'> =
|
|
31
|
+
NavigationBarItemOwnProps &
|
|
32
|
+
BaseLinkProps<C> &
|
|
33
|
+
Omit<ComponentPropsWithoutRef<C>, keyof NavigationBarItemOwnProps>;
|
|
34
|
+
|
|
35
|
+
type NavigationBarItemComponent = <C extends ElementType = 'a'>(
|
|
36
|
+
props: NavigationBarItemProps<C> & {
|
|
37
|
+
ref?: React.ComponentPropsWithRef<C>['ref'];
|
|
38
|
+
}
|
|
39
|
+
) => React.JSX.Element;
|
|
40
|
+
|
|
41
|
+
const NavigationBarItem = forwardRef(function NavigationBarItemInner(
|
|
42
|
+
{
|
|
43
|
+
active = false,
|
|
44
|
+
disabled = false,
|
|
45
|
+
icon,
|
|
46
|
+
iconPosition = 'right',
|
|
47
|
+
testId = NAME,
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
asChild = false,
|
|
51
|
+
...props
|
|
52
|
+
}: NavigationBarItemProps<ElementType>,
|
|
53
|
+
ref: React.Ref<Element>
|
|
54
|
+
) {
|
|
55
|
+
const [theme] = useTheme();
|
|
56
|
+
|
|
57
|
+
const listItemStyle = css`
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const baseStyle = css`
|
|
62
|
+
box-sizing: border-box;
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
min-height: 40px;
|
|
65
|
+
padding: ${theme.padding.p8} ${theme.padding.p16} 0;
|
|
66
|
+
align-items: stretch;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
color: ${theme.colour.link.primary};
|
|
69
|
+
background-color: transparent;
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
outline: none;
|
|
73
|
+
|
|
74
|
+
&:visited {
|
|
75
|
+
color: ${theme.colour.link.primary};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:hover {
|
|
79
|
+
color: ${theme.colour.link.primary};
|
|
80
|
+
background-color: ${theme.colour.fill.brandSubtleHover};
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&:focus-visible {
|
|
85
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
86
|
+
z-index: 1;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const activeStyle = css`
|
|
91
|
+
color: ${theme.colour.link.primary};
|
|
92
|
+
|
|
93
|
+
&:visited,
|
|
94
|
+
&:hover {
|
|
95
|
+
color: ${theme.colour.link.primary};
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const disabledStyle = css`
|
|
100
|
+
color: ${theme.colour.link.disabled};
|
|
101
|
+
background-color: transparent;
|
|
102
|
+
cursor: not-allowed;
|
|
103
|
+
|
|
104
|
+
&:visited,
|
|
105
|
+
&:hover {
|
|
106
|
+
color: ${theme.colour.link.disabled};
|
|
107
|
+
background-color: transparent;
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
const contentStyle = css`
|
|
112
|
+
display: inline-flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
align-items: stretch;
|
|
115
|
+
justify-content: flex-start;
|
|
116
|
+
gap: 2px;
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const labelRowStyle = css`
|
|
120
|
+
display: inline-flex;
|
|
121
|
+
min-height: 24px;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
gap: ${theme.padding.p4};
|
|
125
|
+
color: inherit;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const textStackStyle = css`
|
|
130
|
+
display: inline-grid;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-items: start;
|
|
133
|
+
color: inherit;
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const textStyle = css`
|
|
137
|
+
grid-area: 1 / 1;
|
|
138
|
+
color: inherit;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const hiddenTextStyle = css`
|
|
143
|
+
height: 0;
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
visibility: hidden;
|
|
146
|
+
pointer-events: none;
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
const indicatorStyle = css`
|
|
150
|
+
display: block;
|
|
151
|
+
height: 6px;
|
|
152
|
+
background-color: ${active ? theme.colour.fill.brand : 'transparent'};
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
const iconStyle = css`
|
|
156
|
+
display: block;
|
|
157
|
+
flex-shrink: 0;
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
const style = cx(
|
|
161
|
+
NAME,
|
|
162
|
+
baseStyle,
|
|
163
|
+
active && activeStyle,
|
|
164
|
+
disabled && disabledStyle,
|
|
165
|
+
className
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const renderContent = (content: ReactNode) => (
|
|
169
|
+
<span className={contentStyle}>
|
|
170
|
+
<span className={labelRowStyle}>
|
|
171
|
+
{icon &&
|
|
172
|
+
iconPosition === 'left' &&
|
|
173
|
+
cloneElement(icon, {
|
|
174
|
+
size: icon.props.size ?? 20,
|
|
175
|
+
className: cx(iconStyle, icon.props.className),
|
|
176
|
+
'aria-hidden': icon.props['aria-hidden'] ?? true,
|
|
177
|
+
})}
|
|
178
|
+
<span className={textStackStyle}>
|
|
179
|
+
<Text
|
|
180
|
+
level={active ? 'md-semibold' : 'md'}
|
|
181
|
+
className={textStyle}
|
|
182
|
+
>
|
|
183
|
+
{content}
|
|
184
|
+
</Text>
|
|
185
|
+
<Text
|
|
186
|
+
aria-hidden
|
|
187
|
+
level='md-semibold'
|
|
188
|
+
className={cx(textStyle, hiddenTextStyle)}
|
|
189
|
+
>
|
|
190
|
+
{content}
|
|
191
|
+
</Text>
|
|
192
|
+
</span>
|
|
193
|
+
{icon &&
|
|
194
|
+
iconPosition === 'right' &&
|
|
195
|
+
cloneElement(icon, {
|
|
196
|
+
size: icon.props.size ?? 20,
|
|
197
|
+
className: cx(iconStyle, icon.props.className),
|
|
198
|
+
'aria-hidden': icon.props['aria-hidden'] ?? true,
|
|
199
|
+
})}
|
|
200
|
+
</span>
|
|
201
|
+
<span
|
|
202
|
+
className={indicatorStyle}
|
|
203
|
+
aria-hidden='true'
|
|
204
|
+
/>
|
|
205
|
+
</span>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const content = asChild
|
|
209
|
+
? (() => {
|
|
210
|
+
const child = Children.only(children);
|
|
211
|
+
|
|
212
|
+
return isValidElement<{ children?: ReactNode }>(child)
|
|
213
|
+
? cloneElement(child, {}, renderContent(child.props.children))
|
|
214
|
+
: child;
|
|
215
|
+
})()
|
|
216
|
+
: renderContent(children);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<li className={listItemStyle}>
|
|
220
|
+
<BaseLink
|
|
221
|
+
ref={ref}
|
|
222
|
+
className={style}
|
|
223
|
+
testId={testId}
|
|
224
|
+
disabled={disabled}
|
|
225
|
+
asChild={asChild}
|
|
226
|
+
aria-current={active ? 'page' : undefined}
|
|
227
|
+
data-state={active ? 'active' : 'inactive'}
|
|
228
|
+
{...props}
|
|
229
|
+
>
|
|
230
|
+
{content}
|
|
231
|
+
</BaseLink>
|
|
232
|
+
</li>
|
|
233
|
+
);
|
|
234
|
+
}) as unknown as NavigationBarItemComponent;
|
|
235
|
+
|
|
236
|
+
export default memo(NavigationBarItem) as NavigationBarItemComponent;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import NavigationBar from '../NavigationBar';
|
|
4
|
+
import Icon from '../../Icon';
|
|
5
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
6
|
+
|
|
7
|
+
const wrap = (ui: React.ReactElement) =>
|
|
8
|
+
render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
|
|
9
|
+
|
|
10
|
+
describe('NavigationBar', () => {
|
|
11
|
+
test('renders a navigation landmark with link items', () => {
|
|
12
|
+
wrap(
|
|
13
|
+
<NavigationBar aria-label='Section navigation'>
|
|
14
|
+
<NavigationBar.Item href='/courses'>Courses</NavigationBar.Item>
|
|
15
|
+
<NavigationBar.Item href='/research'>Research</NavigationBar.Item>
|
|
16
|
+
</NavigationBar>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
screen.getByRole('navigation', { name: 'Section navigation' })
|
|
21
|
+
).toBeInTheDocument();
|
|
22
|
+
expect(screen.getByRole('link', { name: 'Courses' })).toHaveAttribute(
|
|
23
|
+
'href',
|
|
24
|
+
'/courses'
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('marks the active item and reserves bold label width', () => {
|
|
29
|
+
wrap(
|
|
30
|
+
<NavigationBar>
|
|
31
|
+
<NavigationBar.Item
|
|
32
|
+
href='/courses'
|
|
33
|
+
active
|
|
34
|
+
>
|
|
35
|
+
Courses
|
|
36
|
+
</NavigationBar.Item>
|
|
37
|
+
</NavigationBar>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const item = screen.getByRole('link', { name: 'Courses' });
|
|
41
|
+
const generatedClass = [...item.classList].find((className) =>
|
|
42
|
+
className.startsWith('css-')
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(item).toHaveAttribute('aria-current', 'page');
|
|
46
|
+
expect(item).toHaveAttribute('data-state', 'active');
|
|
47
|
+
expect(screen.getAllByText('Courses')).toHaveLength(2);
|
|
48
|
+
expect(generatedClass).toBeDefined();
|
|
49
|
+
expect(document.head.textContent).toContain('visibility:hidden');
|
|
50
|
+
expect(document.head.textContent).toContain('white-space:nowrap');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('supports disabled items', () => {
|
|
54
|
+
wrap(
|
|
55
|
+
<NavigationBar>
|
|
56
|
+
<NavigationBar.Item
|
|
57
|
+
href='/news'
|
|
58
|
+
disabled
|
|
59
|
+
>
|
|
60
|
+
News
|
|
61
|
+
</NavigationBar.Item>
|
|
62
|
+
</NavigationBar>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const item = screen.getAllByText('News')[0].closest('a');
|
|
66
|
+
|
|
67
|
+
expect(item).toBeInTheDocument();
|
|
68
|
+
expect(item).toHaveAttribute('aria-disabled', 'true');
|
|
69
|
+
expect(item).toHaveAttribute('tabindex', '-1');
|
|
70
|
+
expect(item).not.toHaveAttribute('href');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('supports icons and asChild links', () => {
|
|
74
|
+
wrap(
|
|
75
|
+
<NavigationBar>
|
|
76
|
+
<NavigationBar.Item
|
|
77
|
+
icon={<Icon.ChevronDown />}
|
|
78
|
+
asChild
|
|
79
|
+
>
|
|
80
|
+
<a href='/open-days'>Open days</a>
|
|
81
|
+
</NavigationBar.Item>
|
|
82
|
+
</NavigationBar>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const item = screen.getByRole('link', { name: 'Open days' });
|
|
86
|
+
|
|
87
|
+
expect(item).toHaveAttribute('href', '/open-days');
|
|
88
|
+
expect(item.querySelector('svg')).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
});
|
package/lib/components/index.ts
CHANGED
|
@@ -148,6 +148,12 @@ export type { HeaderProps } from './Header';
|
|
|
148
148
|
export { default as HeaderNew } from './HeaderNew';
|
|
149
149
|
export type { HeaderProps as HeaderNewProps } from './HeaderNew';
|
|
150
150
|
|
|
151
|
+
export { default as NavigationBar } from './NavigationBar';
|
|
152
|
+
export type {
|
|
153
|
+
NavigationBarItemProps,
|
|
154
|
+
NavigationBarProps,
|
|
155
|
+
} from './NavigationBar';
|
|
156
|
+
|
|
151
157
|
export { default as HeaderDraft } from './HeaderDraft';
|
|
152
158
|
export type { HeaderDraftProps } from './HeaderDraft';
|
|
153
159
|
|