uikit-react-public 0.26.1 → 0.26.3
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/index.js +4545 -4451
- package/lib/components/DrawerMenu/__tests__/DrawerMenu.test.tsx +93 -1
- package/lib/components/Dropdown/DropdownContent.tsx +139 -7
- package/lib/components/Footer/Footer.tsx +12 -0
- package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +36 -0
- package/lib/components/FooterNew/LegalAndCopyright.tsx +14 -0
- package/lib/components/FooterNew/__tests__/__snapshots__/Footer.test.tsx.snap +36 -0
- package/lib/components/MenuNew/MenuAccordion/MenuAccordion.tsx +9 -6
- package/lib/components/MenuNew/MenuAccordion/MenuAccordionItem.tsx +5 -3
- package/lib/components/MenuNew/__tests__/Menu.test.tsx +30 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
3
3
|
import DrawerMenu from '../DrawerMenu';
|
|
4
4
|
import Button from '../../Button';
|
|
5
5
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
@@ -44,6 +44,7 @@ describe('DrawerMenu', () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
afterEach(() => {
|
|
47
|
+
vi.useRealTimers();
|
|
47
48
|
vi.restoreAllMocks();
|
|
48
49
|
});
|
|
49
50
|
|
|
@@ -123,4 +124,95 @@ describe('DrawerMenu', () => {
|
|
|
123
124
|
expect(window.getComputedStyle(drawer as Element).top).toBe('0px');
|
|
124
125
|
expect(window.getComputedStyle(drawer as Element).height).toBe('100dvh');
|
|
125
126
|
});
|
|
127
|
+
|
|
128
|
+
test('keeps tablet and desktop focus inside the header and drawer menu', () => {
|
|
129
|
+
render(
|
|
130
|
+
<ThemeContextProvider>
|
|
131
|
+
<header>
|
|
132
|
+
<DrawerMenu defaultOpen>
|
|
133
|
+
<Button aria-label='First drawer action'>First</Button>
|
|
134
|
+
<Button aria-label='Last drawer action'>Last</Button>
|
|
135
|
+
</DrawerMenu>
|
|
136
|
+
</header>
|
|
137
|
+
<Button aria-label='Outside action'>Outside</Button>
|
|
138
|
+
</ThemeContextProvider>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const trigger = screen.getByRole('button', { name: 'Close menu' });
|
|
142
|
+
const firstDrawerAction = screen.getByRole('button', {
|
|
143
|
+
name: 'First drawer action',
|
|
144
|
+
});
|
|
145
|
+
const lastDrawerAction = screen.getByRole('button', {
|
|
146
|
+
name: 'Last drawer action',
|
|
147
|
+
});
|
|
148
|
+
const outsideAction = screen.getByRole('button', {
|
|
149
|
+
name: 'Outside action',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
lastDrawerAction.focus();
|
|
153
|
+
fireEvent.keyDown(document, { key: 'Tab' });
|
|
154
|
+
expect(trigger).toHaveFocus();
|
|
155
|
+
expect(outsideAction).not.toHaveFocus();
|
|
156
|
+
|
|
157
|
+
trigger.focus();
|
|
158
|
+
fireEvent.keyDown(document, {
|
|
159
|
+
key: 'Tab',
|
|
160
|
+
shiftKey: true,
|
|
161
|
+
});
|
|
162
|
+
expect(lastDrawerAction).toHaveFocus();
|
|
163
|
+
expect(firstDrawerAction).not.toHaveFocus();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('resets drawer and menu content scroll positions after closing transition', () => {
|
|
167
|
+
vi.useFakeTimers();
|
|
168
|
+
|
|
169
|
+
render(
|
|
170
|
+
<ThemeContextProvider>
|
|
171
|
+
<header>
|
|
172
|
+
<DrawerMenu>
|
|
173
|
+
{({ close }) => (
|
|
174
|
+
<div>
|
|
175
|
+
<div
|
|
176
|
+
data-testid='scrollable-menu-content'
|
|
177
|
+
style={{ height: 100, overflow: 'auto' }}
|
|
178
|
+
>
|
|
179
|
+
<div style={{ height: 400 }}>Menu content</div>
|
|
180
|
+
</div>
|
|
181
|
+
<Button
|
|
182
|
+
aria-label='Close from content'
|
|
183
|
+
onClick={close}
|
|
184
|
+
>
|
|
185
|
+
Close
|
|
186
|
+
</Button>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</DrawerMenu>
|
|
190
|
+
</header>
|
|
191
|
+
</ThemeContextProvider>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
fireEvent.click(screen.getByRole('button', { name: 'Open menu' }));
|
|
195
|
+
|
|
196
|
+
const content = screen.getByTestId('ucl-uikit-dropdown__content');
|
|
197
|
+
const drawer = content.parentElement as HTMLElement;
|
|
198
|
+
const scrollableMenuContent = screen.getByTestId('scrollable-menu-content');
|
|
199
|
+
|
|
200
|
+
drawer.scrollTop = 120;
|
|
201
|
+
content.scrollTop = 80;
|
|
202
|
+
scrollableMenuContent.scrollTop = 60;
|
|
203
|
+
|
|
204
|
+
fireEvent.click(screen.getByRole('button', { name: 'Close from content' }));
|
|
205
|
+
|
|
206
|
+
expect(drawer.scrollTop).toBe(120);
|
|
207
|
+
expect(content.scrollTop).toBe(80);
|
|
208
|
+
expect(scrollableMenuContent.scrollTop).toBe(60);
|
|
209
|
+
|
|
210
|
+
act(() => {
|
|
211
|
+
vi.advanceTimersByTime(300);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(drawer.scrollTop).toBe(0);
|
|
215
|
+
expect(content.scrollTop).toBe(0);
|
|
216
|
+
expect(scrollableMenuContent.scrollTop).toBe(0);
|
|
217
|
+
});
|
|
126
218
|
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
HTMLAttributes,
|
|
3
|
+
memo,
|
|
4
|
+
RefObject,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import { createPortal } from 'react-dom';
|
|
3
10
|
import { css, cx } from '@emotion/css';
|
|
4
11
|
import { useTheme } from '../../theme';
|
|
@@ -22,8 +29,11 @@ export interface DropdownContentProps extends Omit<
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
const TABLET_BOTTOM_GAP_PX = 32;
|
|
32
|
+
const DRAWER_TRANSITION_MS = 300;
|
|
25
33
|
const DEFAULT_DRAWER_BOUNDARY_SELECTOR =
|
|
26
34
|
'header,[role="banner"],[data-drawer-menu-boundary]';
|
|
35
|
+
const FOCUSABLE_SELECTOR =
|
|
36
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
27
37
|
|
|
28
38
|
const getCssLength = (value: number | string) =>
|
|
29
39
|
typeof value === 'number' ? `${value}px` : value;
|
|
@@ -52,6 +62,58 @@ const DropdownContent = ({
|
|
|
52
62
|
`(min-width: ${theme.breakpoints.tablet}px)`
|
|
53
63
|
);
|
|
54
64
|
const isDrawer = variant === 'drawer';
|
|
65
|
+
const wasOpenRef = useRef(isOpen);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const wasOpen = wasOpenRef.current;
|
|
69
|
+
wasOpenRef.current = isOpen;
|
|
70
|
+
|
|
71
|
+
if (isOpen || !wasOpen) return;
|
|
72
|
+
|
|
73
|
+
const container = contentRef.current;
|
|
74
|
+
if (!container) return;
|
|
75
|
+
|
|
76
|
+
const resetScroll = () => {
|
|
77
|
+
const scrollableElements = [
|
|
78
|
+
container,
|
|
79
|
+
...Array.from(container.querySelectorAll<HTMLElement>('*')),
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
scrollableElements.forEach((element) => {
|
|
83
|
+
element.scrollTop = 0;
|
|
84
|
+
element.scrollLeft = 0;
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!isDrawer) {
|
|
89
|
+
resetScroll();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let hasResetScroll = false;
|
|
94
|
+
|
|
95
|
+
const resetScrollOnce = () => {
|
|
96
|
+
if (hasResetScroll) return;
|
|
97
|
+
hasResetScroll = true;
|
|
98
|
+
resetScroll();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleTransitionEnd = (event: TransitionEvent) => {
|
|
102
|
+
if (event.target !== container || event.propertyName !== 'transform') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resetScrollOnce();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
container.addEventListener('transitionend', handleTransitionEnd);
|
|
110
|
+
const timeoutId = window.setTimeout(resetScrollOnce, DRAWER_TRANSITION_MS);
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
container.removeEventListener('transitionend', handleTransitionEnd);
|
|
114
|
+
window.clearTimeout(timeoutId);
|
|
115
|
+
};
|
|
116
|
+
}, [contentRef, isDrawer, isOpen]);
|
|
55
117
|
|
|
56
118
|
useEffect(() => {
|
|
57
119
|
if (typeof document === 'undefined') return;
|
|
@@ -80,9 +142,7 @@ const DropdownContent = ({
|
|
|
80
142
|
|
|
81
143
|
const getFocusableElements = () =>
|
|
82
144
|
Array.from(
|
|
83
|
-
container.querySelectorAll<HTMLElement>(
|
|
84
|
-
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
85
|
-
)
|
|
145
|
+
container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
|
|
86
146
|
).filter((element) => !element.hasAttribute('disabled'));
|
|
87
147
|
|
|
88
148
|
const focusableElements = getFocusableElements();
|
|
@@ -131,6 +191,78 @@ const DropdownContent = ({
|
|
|
131
191
|
};
|
|
132
192
|
}, [contentRef, isOpen, isTabletAndUp]);
|
|
133
193
|
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (typeof document === 'undefined') return;
|
|
196
|
+
if (!isDrawer || !isTabletAndUp || !isOpen) return;
|
|
197
|
+
|
|
198
|
+
const container = contentRef.current;
|
|
199
|
+
if (!container) return;
|
|
200
|
+
|
|
201
|
+
const triggerElement = triggerRef.current;
|
|
202
|
+
const boundaryElement =
|
|
203
|
+
drawerBoundaryRef?.current ??
|
|
204
|
+
triggerElement?.closest<HTMLElement>(drawerBoundarySelector);
|
|
205
|
+
const focusContainers: HTMLElement[] = boundaryElement
|
|
206
|
+
? [boundaryElement]
|
|
207
|
+
: [triggerElement, container].filter((element): element is HTMLElement =>
|
|
208
|
+
Boolean(element)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const getFocusableElements = () =>
|
|
212
|
+
Array.from(
|
|
213
|
+
new Set(
|
|
214
|
+
focusContainers.flatMap((focusContainer) =>
|
|
215
|
+
Array.from(
|
|
216
|
+
focusContainer.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
).filter((element) => !element.hasAttribute('disabled'));
|
|
221
|
+
|
|
222
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
223
|
+
if (event.key !== 'Tab') return;
|
|
224
|
+
|
|
225
|
+
const elements = getFocusableElements();
|
|
226
|
+
if (elements.length === 0) {
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
container.focus();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const first = elements[0];
|
|
233
|
+
const last = elements[elements.length - 1];
|
|
234
|
+
const active = document.activeElement as HTMLElement | null;
|
|
235
|
+
|
|
236
|
+
if (!active || !elements.includes(active)) {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
first.focus();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (event.shiftKey && active === first) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
last.focus();
|
|
245
|
+
} else if (!event.shiftKey && active === last) {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
first.focus();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
252
|
+
|
|
253
|
+
return () => {
|
|
254
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
255
|
+
};
|
|
256
|
+
}, [
|
|
257
|
+
contentRef,
|
|
258
|
+
drawerBoundaryRef,
|
|
259
|
+
drawerBoundarySelector,
|
|
260
|
+
isDrawer,
|
|
261
|
+
isOpen,
|
|
262
|
+
isTabletAndUp,
|
|
263
|
+
triggerRef,
|
|
264
|
+
]);
|
|
265
|
+
|
|
134
266
|
useEffect(() => {
|
|
135
267
|
if (typeof window === 'undefined') return;
|
|
136
268
|
if (!isTabletAndUp || !isOpen || isDrawer) {
|
|
@@ -261,8 +393,8 @@ const DropdownContent = ({
|
|
|
261
393
|
pointer-events: none;
|
|
262
394
|
transform: translateX(100%);
|
|
263
395
|
transition:
|
|
264
|
-
transform
|
|
265
|
-
visibility 0ms linear
|
|
396
|
+
transform ${DRAWER_TRANSITION_MS}ms ease-in,
|
|
397
|
+
visibility 0ms linear ${DRAWER_TRANSITION_MS}ms;
|
|
266
398
|
will-change: transform;
|
|
267
399
|
|
|
268
400
|
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
@@ -315,7 +447,7 @@ const DropdownContent = ({
|
|
|
315
447
|
visibility: visible;
|
|
316
448
|
pointer-events: auto;
|
|
317
449
|
transform: translateX(0);
|
|
318
|
-
transition: transform
|
|
450
|
+
transition: transform ${DRAWER_TRANSITION_MS}ms ease-out;
|
|
319
451
|
`;
|
|
320
452
|
|
|
321
453
|
const style = cx(
|
|
@@ -206,36 +206,48 @@ const Footer = ({
|
|
|
206
206
|
<a
|
|
207
207
|
className={legalLinkStyle}
|
|
208
208
|
href={disclaimer}
|
|
209
|
+
target="_blank"
|
|
210
|
+
rel="noopener noreferrer"
|
|
209
211
|
>
|
|
210
212
|
Disclaimer
|
|
211
213
|
</a>
|
|
212
214
|
<a
|
|
213
215
|
className={legalLinkStyle}
|
|
214
216
|
href={freedomOfInformation}
|
|
217
|
+
target="_blank"
|
|
218
|
+
rel="noopener noreferrer"
|
|
215
219
|
>
|
|
216
220
|
Freedom of Information
|
|
217
221
|
</a>
|
|
218
222
|
<a
|
|
219
223
|
className={legalLinkStyle}
|
|
220
224
|
href={accessibility}
|
|
225
|
+
target="_blank"
|
|
226
|
+
rel="noopener noreferrer"
|
|
221
227
|
>
|
|
222
228
|
Accessibility
|
|
223
229
|
</a>
|
|
224
230
|
<a
|
|
225
231
|
className={legalLinkStyle}
|
|
226
232
|
href={cookies}
|
|
233
|
+
target="_blank"
|
|
234
|
+
rel="noopener noreferrer"
|
|
227
235
|
>
|
|
228
236
|
Cookies
|
|
229
237
|
</a>
|
|
230
238
|
<a
|
|
231
239
|
className={legalLinkStyle}
|
|
232
240
|
href={privacy}
|
|
241
|
+
target="_blank"
|
|
242
|
+
rel="noopener noreferrer"
|
|
233
243
|
>
|
|
234
244
|
Privacy
|
|
235
245
|
</a>
|
|
236
246
|
<a
|
|
237
247
|
className={legalLinkStyle}
|
|
238
248
|
href={slaveryStatement}
|
|
249
|
+
target="_blank"
|
|
250
|
+
rel="noopener noreferrer"
|
|
239
251
|
>
|
|
240
252
|
Slavery statement
|
|
241
253
|
</a>
|
|
@@ -254,36 +254,48 @@ exports[`Footer > snapshot: footer links provided 1`] = `
|
|
|
254
254
|
<a
|
|
255
255
|
class="css-16lix8i"
|
|
256
256
|
href="https://dictionary.cambridge.org/dictionary/english/disclaimer"
|
|
257
|
+
rel="noopener noreferrer"
|
|
258
|
+
target="_blank"
|
|
257
259
|
>
|
|
258
260
|
Disclaimer
|
|
259
261
|
</a>
|
|
260
262
|
<a
|
|
261
263
|
class="css-16lix8i"
|
|
262
264
|
href="https://www.legislation.gov.uk/ukpga/2000/36/contents"
|
|
265
|
+
rel="noopener noreferrer"
|
|
266
|
+
target="_blank"
|
|
263
267
|
>
|
|
264
268
|
Freedom of Information
|
|
265
269
|
</a>
|
|
266
270
|
<a
|
|
267
271
|
class="css-16lix8i"
|
|
268
272
|
href="https://developer.mozilla.org/en-US/docs/Web/Accessibility"
|
|
273
|
+
rel="noopener noreferrer"
|
|
274
|
+
target="_blank"
|
|
269
275
|
>
|
|
270
276
|
Accessibility
|
|
271
277
|
</a>
|
|
272
278
|
<a
|
|
273
279
|
class="css-16lix8i"
|
|
274
280
|
href="https://en.wikipedia.org/wiki/HTTP_cookie"
|
|
281
|
+
rel="noopener noreferrer"
|
|
282
|
+
target="_blank"
|
|
275
283
|
>
|
|
276
284
|
Cookies
|
|
277
285
|
</a>
|
|
278
286
|
<a
|
|
279
287
|
class="css-16lix8i"
|
|
280
288
|
href="https://dictionary.cambridge.org/dictionary/english/privacy"
|
|
289
|
+
rel="noopener noreferrer"
|
|
290
|
+
target="_blank"
|
|
281
291
|
>
|
|
282
292
|
Privacy
|
|
283
293
|
</a>
|
|
284
294
|
<a
|
|
285
295
|
class="css-16lix8i"
|
|
286
296
|
href="https://modern-slavery-statement-registry.service.gov.uk/"
|
|
297
|
+
rel="noopener noreferrer"
|
|
298
|
+
target="_blank"
|
|
287
299
|
>
|
|
288
300
|
Slavery statement
|
|
289
301
|
</a>
|
|
@@ -595,36 +607,48 @@ exports[`Footer > snapshot: nav links 1`] = `
|
|
|
595
607
|
<a
|
|
596
608
|
class="css-16lix8i"
|
|
597
609
|
href="https://www.ucl.ac.uk/legal-services/disclaimer"
|
|
610
|
+
rel="noopener noreferrer"
|
|
611
|
+
target="_blank"
|
|
598
612
|
>
|
|
599
613
|
Disclaimer
|
|
600
614
|
</a>
|
|
601
615
|
<a
|
|
602
616
|
class="css-16lix8i"
|
|
603
617
|
href="https://www.ucl.ac.uk/foi"
|
|
618
|
+
rel="noopener noreferrer"
|
|
619
|
+
target="_blank"
|
|
604
620
|
>
|
|
605
621
|
Freedom of Information
|
|
606
622
|
</a>
|
|
607
623
|
<a
|
|
608
624
|
class="css-16lix8i"
|
|
609
625
|
href="https://www.ucl.ac.uk/accessibility"
|
|
626
|
+
rel="noopener noreferrer"
|
|
627
|
+
target="_blank"
|
|
610
628
|
>
|
|
611
629
|
Accessibility
|
|
612
630
|
</a>
|
|
613
631
|
<a
|
|
614
632
|
class="css-16lix8i"
|
|
615
633
|
href="https://www.ucl.ac.uk/legal-services/privacy/cookie-policy"
|
|
634
|
+
rel="noopener noreferrer"
|
|
635
|
+
target="_blank"
|
|
616
636
|
>
|
|
617
637
|
Cookies
|
|
618
638
|
</a>
|
|
619
639
|
<a
|
|
620
640
|
class="css-16lix8i"
|
|
621
641
|
href="https://www.ucl.ac.uk/legal-services/privacy"
|
|
642
|
+
rel="noopener noreferrer"
|
|
643
|
+
target="_blank"
|
|
622
644
|
>
|
|
623
645
|
Privacy
|
|
624
646
|
</a>
|
|
625
647
|
<a
|
|
626
648
|
class="css-16lix8i"
|
|
627
649
|
href="https://www.ucl.ac.uk/commercial-procurement/modern-day-slavery-statement"
|
|
650
|
+
rel="noopener noreferrer"
|
|
651
|
+
target="_blank"
|
|
628
652
|
>
|
|
629
653
|
Slavery statement
|
|
630
654
|
</a>
|
|
@@ -887,36 +911,48 @@ exports[`Footer > snapshot: no nav links 1`] = `
|
|
|
887
911
|
<a
|
|
888
912
|
class="css-16lix8i"
|
|
889
913
|
href="https://www.ucl.ac.uk/legal-services/disclaimer"
|
|
914
|
+
rel="noopener noreferrer"
|
|
915
|
+
target="_blank"
|
|
890
916
|
>
|
|
891
917
|
Disclaimer
|
|
892
918
|
</a>
|
|
893
919
|
<a
|
|
894
920
|
class="css-16lix8i"
|
|
895
921
|
href="https://www.ucl.ac.uk/foi"
|
|
922
|
+
rel="noopener noreferrer"
|
|
923
|
+
target="_blank"
|
|
896
924
|
>
|
|
897
925
|
Freedom of Information
|
|
898
926
|
</a>
|
|
899
927
|
<a
|
|
900
928
|
class="css-16lix8i"
|
|
901
929
|
href="https://www.ucl.ac.uk/accessibility"
|
|
930
|
+
rel="noopener noreferrer"
|
|
931
|
+
target="_blank"
|
|
902
932
|
>
|
|
903
933
|
Accessibility
|
|
904
934
|
</a>
|
|
905
935
|
<a
|
|
906
936
|
class="css-16lix8i"
|
|
907
937
|
href="https://www.ucl.ac.uk/legal-services/privacy/cookie-policy"
|
|
938
|
+
rel="noopener noreferrer"
|
|
939
|
+
target="_blank"
|
|
908
940
|
>
|
|
909
941
|
Cookies
|
|
910
942
|
</a>
|
|
911
943
|
<a
|
|
912
944
|
class="css-16lix8i"
|
|
913
945
|
href="https://www.ucl.ac.uk/legal-services/privacy"
|
|
946
|
+
rel="noopener noreferrer"
|
|
947
|
+
target="_blank"
|
|
914
948
|
>
|
|
915
949
|
Privacy
|
|
916
950
|
</a>
|
|
917
951
|
<a
|
|
918
952
|
class="css-16lix8i"
|
|
919
953
|
href="https://www.ucl.ac.uk/commercial-procurement/modern-day-slavery-statement"
|
|
954
|
+
rel="noopener noreferrer"
|
|
955
|
+
target="_blank"
|
|
920
956
|
>
|
|
921
957
|
Slavery statement
|
|
922
958
|
</a>
|
|
@@ -100,36 +100,48 @@ const LegalAndCopyright = ({
|
|
|
100
100
|
<a
|
|
101
101
|
className={linkStyle}
|
|
102
102
|
href={disclaimer}
|
|
103
|
+
target="_blank"
|
|
104
|
+
rel="noopener noreferrer"
|
|
103
105
|
>
|
|
104
106
|
Disclaimer
|
|
105
107
|
</a>
|
|
106
108
|
<a
|
|
107
109
|
className={linkStyle}
|
|
108
110
|
href={freedomOfInformation}
|
|
111
|
+
target="_blank"
|
|
112
|
+
rel="noopener noreferrer"
|
|
109
113
|
>
|
|
110
114
|
Freedom of Information
|
|
111
115
|
</a>
|
|
112
116
|
<a
|
|
113
117
|
className={linkStyle}
|
|
114
118
|
href={accessibility}
|
|
119
|
+
target="_blank"
|
|
120
|
+
rel="noopener noreferrer"
|
|
115
121
|
>
|
|
116
122
|
Accessibility
|
|
117
123
|
</a>
|
|
118
124
|
<a
|
|
119
125
|
className={linkStyle}
|
|
120
126
|
href={cookies}
|
|
127
|
+
target="_blank"
|
|
128
|
+
rel="noopener noreferrer"
|
|
121
129
|
>
|
|
122
130
|
Cookies
|
|
123
131
|
</a>
|
|
124
132
|
<a
|
|
125
133
|
className={linkStyle}
|
|
126
134
|
href={privacy}
|
|
135
|
+
target="_blank"
|
|
136
|
+
rel="noopener noreferrer"
|
|
127
137
|
>
|
|
128
138
|
Privacy
|
|
129
139
|
</a>
|
|
130
140
|
<a
|
|
131
141
|
className={linkStyle}
|
|
132
142
|
href={slaveryStatement}
|
|
143
|
+
target="_blank"
|
|
144
|
+
rel="noopener noreferrer"
|
|
133
145
|
>
|
|
134
146
|
Slavery statement
|
|
135
147
|
</a>
|
|
@@ -137,6 +149,8 @@ const LegalAndCopyright = ({
|
|
|
137
149
|
<a
|
|
138
150
|
className={linkStyle}
|
|
139
151
|
href={login}
|
|
152
|
+
target="_blank"
|
|
153
|
+
rel="noopener noreferrer"
|
|
140
154
|
>
|
|
141
155
|
Login
|
|
142
156
|
</a>
|
|
@@ -306,36 +306,48 @@ exports[`Footer > snapshot: footer links provided 1`] = `
|
|
|
306
306
|
<a
|
|
307
307
|
class="css-19zp1gp"
|
|
308
308
|
href="https://dictionary.cambridge.org/dictionary/english/disclaimer"
|
|
309
|
+
rel="noopener noreferrer"
|
|
310
|
+
target="_blank"
|
|
309
311
|
>
|
|
310
312
|
Disclaimer
|
|
311
313
|
</a>
|
|
312
314
|
<a
|
|
313
315
|
class="css-19zp1gp"
|
|
314
316
|
href="https://www.legislation.gov.uk/ukpga/2000/36/contents"
|
|
317
|
+
rel="noopener noreferrer"
|
|
318
|
+
target="_blank"
|
|
315
319
|
>
|
|
316
320
|
Freedom of Information
|
|
317
321
|
</a>
|
|
318
322
|
<a
|
|
319
323
|
class="css-19zp1gp"
|
|
320
324
|
href="https://developer.mozilla.org/en-US/docs/Web/Accessibility"
|
|
325
|
+
rel="noopener noreferrer"
|
|
326
|
+
target="_blank"
|
|
321
327
|
>
|
|
322
328
|
Accessibility
|
|
323
329
|
</a>
|
|
324
330
|
<a
|
|
325
331
|
class="css-19zp1gp"
|
|
326
332
|
href="https://en.wikipedia.org/wiki/HTTP_cookie"
|
|
333
|
+
rel="noopener noreferrer"
|
|
334
|
+
target="_blank"
|
|
327
335
|
>
|
|
328
336
|
Cookies
|
|
329
337
|
</a>
|
|
330
338
|
<a
|
|
331
339
|
class="css-19zp1gp"
|
|
332
340
|
href="https://dictionary.cambridge.org/dictionary/english/privacy"
|
|
341
|
+
rel="noopener noreferrer"
|
|
342
|
+
target="_blank"
|
|
333
343
|
>
|
|
334
344
|
Privacy
|
|
335
345
|
</a>
|
|
336
346
|
<a
|
|
337
347
|
class="css-19zp1gp"
|
|
338
348
|
href="https://modern-slavery-statement-registry.service.gov.uk/"
|
|
349
|
+
rel="noopener noreferrer"
|
|
350
|
+
target="_blank"
|
|
339
351
|
>
|
|
340
352
|
Slavery statement
|
|
341
353
|
</a>
|
|
@@ -710,36 +722,48 @@ exports[`Footer > snapshot: nav links 1`] = `
|
|
|
710
722
|
<a
|
|
711
723
|
class="css-19zp1gp"
|
|
712
724
|
href="https://www.ucl.ac.uk/legal-services/disclaimer"
|
|
725
|
+
rel="noopener noreferrer"
|
|
726
|
+
target="_blank"
|
|
713
727
|
>
|
|
714
728
|
Disclaimer
|
|
715
729
|
</a>
|
|
716
730
|
<a
|
|
717
731
|
class="css-19zp1gp"
|
|
718
732
|
href="https://www.ucl.ac.uk/foi"
|
|
733
|
+
rel="noopener noreferrer"
|
|
734
|
+
target="_blank"
|
|
719
735
|
>
|
|
720
736
|
Freedom of Information
|
|
721
737
|
</a>
|
|
722
738
|
<a
|
|
723
739
|
class="css-19zp1gp"
|
|
724
740
|
href="https://www.ucl.ac.uk/accessibility"
|
|
741
|
+
rel="noopener noreferrer"
|
|
742
|
+
target="_blank"
|
|
725
743
|
>
|
|
726
744
|
Accessibility
|
|
727
745
|
</a>
|
|
728
746
|
<a
|
|
729
747
|
class="css-19zp1gp"
|
|
730
748
|
href="https://www.ucl.ac.uk/legal-services/privacy/cookie-policy"
|
|
749
|
+
rel="noopener noreferrer"
|
|
750
|
+
target="_blank"
|
|
731
751
|
>
|
|
732
752
|
Cookies
|
|
733
753
|
</a>
|
|
734
754
|
<a
|
|
735
755
|
class="css-19zp1gp"
|
|
736
756
|
href="https://www.ucl.ac.uk/legal-services/privacy"
|
|
757
|
+
rel="noopener noreferrer"
|
|
758
|
+
target="_blank"
|
|
737
759
|
>
|
|
738
760
|
Privacy
|
|
739
761
|
</a>
|
|
740
762
|
<a
|
|
741
763
|
class="css-19zp1gp"
|
|
742
764
|
href="https://www.ucl.ac.uk/commercial-procurement/modern-day-slavery-statement"
|
|
765
|
+
rel="noopener noreferrer"
|
|
766
|
+
target="_blank"
|
|
743
767
|
>
|
|
744
768
|
Slavery statement
|
|
745
769
|
</a>
|
|
@@ -1061,36 +1085,48 @@ exports[`Footer > snapshot: no nav links 1`] = `
|
|
|
1061
1085
|
<a
|
|
1062
1086
|
class="css-19zp1gp"
|
|
1063
1087
|
href="https://www.ucl.ac.uk/legal-services/disclaimer"
|
|
1088
|
+
rel="noopener noreferrer"
|
|
1089
|
+
target="_blank"
|
|
1064
1090
|
>
|
|
1065
1091
|
Disclaimer
|
|
1066
1092
|
</a>
|
|
1067
1093
|
<a
|
|
1068
1094
|
class="css-19zp1gp"
|
|
1069
1095
|
href="https://www.ucl.ac.uk/foi"
|
|
1096
|
+
rel="noopener noreferrer"
|
|
1097
|
+
target="_blank"
|
|
1070
1098
|
>
|
|
1071
1099
|
Freedom of Information
|
|
1072
1100
|
</a>
|
|
1073
1101
|
<a
|
|
1074
1102
|
class="css-19zp1gp"
|
|
1075
1103
|
href="https://www.ucl.ac.uk/accessibility"
|
|
1104
|
+
rel="noopener noreferrer"
|
|
1105
|
+
target="_blank"
|
|
1076
1106
|
>
|
|
1077
1107
|
Accessibility
|
|
1078
1108
|
</a>
|
|
1079
1109
|
<a
|
|
1080
1110
|
class="css-19zp1gp"
|
|
1081
1111
|
href="https://www.ucl.ac.uk/legal-services/privacy/cookie-policy"
|
|
1112
|
+
rel="noopener noreferrer"
|
|
1113
|
+
target="_blank"
|
|
1082
1114
|
>
|
|
1083
1115
|
Cookies
|
|
1084
1116
|
</a>
|
|
1085
1117
|
<a
|
|
1086
1118
|
class="css-19zp1gp"
|
|
1087
1119
|
href="https://www.ucl.ac.uk/legal-services/privacy"
|
|
1120
|
+
rel="noopener noreferrer"
|
|
1121
|
+
target="_blank"
|
|
1088
1122
|
>
|
|
1089
1123
|
Privacy
|
|
1090
1124
|
</a>
|
|
1091
1125
|
<a
|
|
1092
1126
|
class="css-19zp1gp"
|
|
1093
1127
|
href="https://www.ucl.ac.uk/commercial-procurement/modern-day-slavery-statement"
|
|
1128
|
+
rel="noopener noreferrer"
|
|
1129
|
+
target="_blank"
|
|
1094
1130
|
>
|
|
1095
1131
|
Slavery statement
|
|
1096
1132
|
</a>
|