uikit-react-public 0.44.5 → 0.45.1
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/CookiesNotice/CookieSettings.d.ts +16 -0
- package/dist/components/CookiesNotice/CookiesNotice.d.ts +26 -0
- package/dist/components/CookiesNotice/CookiesNotice.stories.d.ts +13 -0
- package/dist/components/CookiesNotice/__tests__/CookiesNotice.test.d.ts +1 -0
- package/dist/components/CookiesNotice/defaults.d.ts +6 -0
- package/dist/components/CookiesNotice/index.d.ts +3 -0
- package/dist/components/CookiesNotice/types.d.ts +12 -0
- package/dist/components/Paragraph/Paragraph.d.ts +1 -1
- package/dist/components/Text/Text.d.ts +1 -1
- package/dist/components/Text/__tests__/Text.test.d.ts +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/index.js +6528 -5923
- package/lib/components/Button/Button.tsx +1 -1
- package/lib/components/Button/__tests__/__snapshots__/Button.test.tsx.snap +8 -8
- package/lib/components/CookiesNotice/CookieSettings.tsx +304 -0
- package/lib/components/CookiesNotice/CookiesNotice.stories.tsx +60 -0
- package/lib/components/CookiesNotice/CookiesNotice.tsx +379 -0
- package/lib/components/CookiesNotice/__tests__/CookiesNotice.test.tsx +180 -0
- package/lib/components/CookiesNotice/defaults.ts +40 -0
- package/lib/components/CookiesNotice/index.ts +6 -0
- package/lib/components/CookiesNotice/types.ts +14 -0
- package/lib/components/Input/Input.tsx +1 -0
- package/lib/components/Input/__tests__/__snapshots__/Input.test.tsx.snap +4 -4
- package/lib/components/Paragraph/Paragraph.tsx +13 -1
- package/lib/components/Paragraph/__tests__/Paragraph.test.tsx +24 -0
- package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +2 -2
- package/lib/components/Text/Text.tsx +13 -0
- package/lib/components/Text/__tests__/Text.test.tsx +41 -0
- package/lib/components/Textarea/Textarea.tsx +2 -3
- package/lib/components/Textarea/__tests__/__snapshots__/Textarea.test.tsx.snap +4 -4
- package/lib/components/index.ts +9 -0
- package/lib/theme/common/themeCommon.ts +305 -327
- package/package.json +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HTMLAttributes,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { css, cx } from '@emotion/css';
|
|
11
|
+
import useTheme from '../../theme/useTheme';
|
|
12
|
+
import marginsStyle, { MarginProps } from '../common/marginsStyle';
|
|
13
|
+
import Heading from '../Heading';
|
|
14
|
+
import Paragraph from '../Paragraph';
|
|
15
|
+
import Button from '../Button';
|
|
16
|
+
import Link from '../Link';
|
|
17
|
+
import BaseDialog from '../Dialog/BaseDialog';
|
|
18
|
+
import CookieSettings from './CookieSettings';
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_COOKIE_CATEGORIES,
|
|
21
|
+
DEFAULT_DESCRIPTION,
|
|
22
|
+
DEFAULT_DESCRIPTION_SUFFIX,
|
|
23
|
+
DEFAULT_HEADING,
|
|
24
|
+
DEFAULT_POLICY_HREF,
|
|
25
|
+
} from './defaults';
|
|
26
|
+
import { CookieCategory, CookiePreferences } from './types';
|
|
27
|
+
|
|
28
|
+
export const NAME = 'ucl-uikit-cookies-notice';
|
|
29
|
+
const DESKTOP_NOTICE_CONTENT_MAX_WIDTH = 944;
|
|
30
|
+
|
|
31
|
+
export interface CookiesNoticeBaseProps extends HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
heading?: ReactNode;
|
|
33
|
+
description?: ReactNode;
|
|
34
|
+
policyHref?: string;
|
|
35
|
+
policyLabel?: ReactNode;
|
|
36
|
+
cookies?: CookieCategory[];
|
|
37
|
+
preferences?: CookiePreferences;
|
|
38
|
+
modal?: boolean;
|
|
39
|
+
manageCookiesLabel?: ReactNode;
|
|
40
|
+
acceptAllLabel?: ReactNode;
|
|
41
|
+
acceptNecessaryLabel?: ReactNode;
|
|
42
|
+
onAcceptAll?: (preferences: CookiePreferences) => void;
|
|
43
|
+
onAcceptNecessary?: (preferences: CookiePreferences) => void;
|
|
44
|
+
onManageCookies?: () => void;
|
|
45
|
+
onPreferencesChange?: (preferences: CookiePreferences) => void;
|
|
46
|
+
onSavePreferences?: (preferences: CookiePreferences) => void;
|
|
47
|
+
onSettingsClose?: () => void;
|
|
48
|
+
testId?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type CookiesNoticeProps = CookiesNoticeBaseProps & MarginProps;
|
|
52
|
+
|
|
53
|
+
const getAllPreferences = (cookies: CookieCategory[]): CookiePreferences =>
|
|
54
|
+
cookies.reduce<CookiePreferences>((acc, cookie) => {
|
|
55
|
+
acc[cookie.id] = true;
|
|
56
|
+
return acc;
|
|
57
|
+
}, {});
|
|
58
|
+
|
|
59
|
+
const getNecessaryPreferences = (
|
|
60
|
+
cookies: CookieCategory[]
|
|
61
|
+
): CookiePreferences =>
|
|
62
|
+
cookies.reduce<CookiePreferences>((acc, cookie) => {
|
|
63
|
+
acc[cookie.id] = !!cookie.required;
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
|
|
67
|
+
const getRequiredPreferences = (cookies: CookieCategory[]): CookiePreferences =>
|
|
68
|
+
cookies.reduce<CookiePreferences>((acc, cookie) => {
|
|
69
|
+
if (cookie.required) {
|
|
70
|
+
acc[cookie.id] = true;
|
|
71
|
+
}
|
|
72
|
+
return acc;
|
|
73
|
+
}, {});
|
|
74
|
+
|
|
75
|
+
const getDefaultPreferences = (cookies: CookieCategory[]): CookiePreferences =>
|
|
76
|
+
cookies.reduce<CookiePreferences>((acc, cookie) => {
|
|
77
|
+
acc[cookie.id] = cookie.required ? true : (cookie.defaultEnabled ?? false);
|
|
78
|
+
return acc;
|
|
79
|
+
}, {});
|
|
80
|
+
|
|
81
|
+
const CookiesNotice = ({
|
|
82
|
+
heading = DEFAULT_HEADING,
|
|
83
|
+
description,
|
|
84
|
+
policyHref = DEFAULT_POLICY_HREF,
|
|
85
|
+
policyLabel = 'UCL privacy policy',
|
|
86
|
+
cookies = DEFAULT_COOKIE_CATEGORIES,
|
|
87
|
+
preferences,
|
|
88
|
+
modal = false,
|
|
89
|
+
manageCookiesLabel = 'Manage cookies',
|
|
90
|
+
acceptAllLabel = 'Accept all cookies',
|
|
91
|
+
acceptNecessaryLabel = 'Accept necessary only',
|
|
92
|
+
onAcceptAll,
|
|
93
|
+
onAcceptNecessary,
|
|
94
|
+
onManageCookies,
|
|
95
|
+
onPreferencesChange,
|
|
96
|
+
onSavePreferences,
|
|
97
|
+
onSettingsClose,
|
|
98
|
+
testId = NAME,
|
|
99
|
+
className,
|
|
100
|
+
...props
|
|
101
|
+
}: CookiesNoticeProps) => {
|
|
102
|
+
const [theme] = useTheme();
|
|
103
|
+
const noticeHeadingId = useId();
|
|
104
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
105
|
+
const initialPreferences = useMemo(
|
|
106
|
+
() => ({
|
|
107
|
+
...getDefaultPreferences(cookies),
|
|
108
|
+
...preferences,
|
|
109
|
+
...getRequiredPreferences(cookies),
|
|
110
|
+
}),
|
|
111
|
+
[cookies, preferences]
|
|
112
|
+
);
|
|
113
|
+
const [savedPreferences, setSavedPreferences] =
|
|
114
|
+
useState<CookiePreferences>(initialPreferences);
|
|
115
|
+
const [draftPreferences, setDraftPreferences] =
|
|
116
|
+
useState<CookiePreferences>(initialPreferences);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
setSavedPreferences(initialPreferences);
|
|
120
|
+
setDraftPreferences(initialPreferences);
|
|
121
|
+
}, [initialPreferences]);
|
|
122
|
+
|
|
123
|
+
const handlePreferenceToggle = useCallback(
|
|
124
|
+
(cookie: CookieCategory) => {
|
|
125
|
+
if (cookie.required) return;
|
|
126
|
+
|
|
127
|
+
const nextPreferences = {
|
|
128
|
+
...draftPreferences,
|
|
129
|
+
[cookie.id]: !draftPreferences[cookie.id],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
setDraftPreferences(nextPreferences);
|
|
133
|
+
onPreferencesChange?.(nextPreferences);
|
|
134
|
+
},
|
|
135
|
+
[draftPreferences, onPreferencesChange]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const handleAcceptAll = useCallback(() => {
|
|
139
|
+
const nextPreferences = getAllPreferences(cookies);
|
|
140
|
+
|
|
141
|
+
setSavedPreferences(nextPreferences);
|
|
142
|
+
setDraftPreferences(nextPreferences);
|
|
143
|
+
onAcceptAll?.(nextPreferences);
|
|
144
|
+
}, [cookies, onAcceptAll]);
|
|
145
|
+
|
|
146
|
+
const handleAcceptNecessary = useCallback(() => {
|
|
147
|
+
const nextPreferences = getNecessaryPreferences(cookies);
|
|
148
|
+
|
|
149
|
+
setSavedPreferences(nextPreferences);
|
|
150
|
+
setDraftPreferences(nextPreferences);
|
|
151
|
+
onAcceptNecessary?.(nextPreferences);
|
|
152
|
+
}, [cookies, onAcceptNecessary]);
|
|
153
|
+
|
|
154
|
+
const handleManageCookies = useCallback(() => {
|
|
155
|
+
onManageCookies?.();
|
|
156
|
+
setDraftPreferences(savedPreferences);
|
|
157
|
+
setSettingsOpen(true);
|
|
158
|
+
}, [onManageCookies, savedPreferences]);
|
|
159
|
+
|
|
160
|
+
const handleSettingsClose = useCallback(() => {
|
|
161
|
+
setSettingsOpen(false);
|
|
162
|
+
onSettingsClose?.();
|
|
163
|
+
}, [onSettingsClose]);
|
|
164
|
+
|
|
165
|
+
const handleSavePreferences = useCallback(() => {
|
|
166
|
+
setSavedPreferences(draftPreferences);
|
|
167
|
+
onSavePreferences?.(draftPreferences);
|
|
168
|
+
setSettingsOpen(false);
|
|
169
|
+
}, [draftPreferences, onSavePreferences]);
|
|
170
|
+
|
|
171
|
+
const baseStyle = css`
|
|
172
|
+
box-sizing: border-box;
|
|
173
|
+
width: 100%;
|
|
174
|
+
padding: ${theme.padding.p24} ${theme.padding.p16};
|
|
175
|
+
background-color: ${theme.colour.surface.primary};
|
|
176
|
+
color: ${theme.colour.text.default};
|
|
177
|
+
border-radius: ${theme.radius.r4} ${theme.radius.r4} 0 0;
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
gap: ${theme.padding.p24};
|
|
181
|
+
|
|
182
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
183
|
+
padding: ${theme.padding.p48} ${theme.padding.p32};
|
|
184
|
+
border-radius: ${theme.radius.r4};
|
|
185
|
+
gap: ${theme.padding.p32};
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
const contentStyle = css`
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
gap: ${theme.padding.p16};
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
const headingStyle = css`
|
|
196
|
+
text-align: center;
|
|
197
|
+
|
|
198
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
199
|
+
text-align: left;
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
const policyLinkStyle = css`
|
|
204
|
+
color: ${theme.colour.text.default};
|
|
205
|
+
|
|
206
|
+
&:visited {
|
|
207
|
+
color: ${theme.colour.text.default};
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const actionsStyle = css`
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
gap: ${theme.padding.p16};
|
|
215
|
+
|
|
216
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
217
|
+
flex-direction: row;
|
|
218
|
+
justify-content: space-between;
|
|
219
|
+
align-items: center;
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const actionGroupStyle = css`
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
gap: ${theme.padding.p16};
|
|
227
|
+
|
|
228
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
229
|
+
flex-direction: row;
|
|
230
|
+
align-items: center;
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
const manageButtonStyle = css`
|
|
235
|
+
order: 3;
|
|
236
|
+
width: 100%;
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
|
|
239
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
240
|
+
order: 0;
|
|
241
|
+
width: auto;
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
const actionButtonStyle = css`
|
|
246
|
+
width: 100%;
|
|
247
|
+
white-space: nowrap;
|
|
248
|
+
|
|
249
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
250
|
+
width: auto;
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
const dialogStyle = css`
|
|
255
|
+
height: fit-content;
|
|
256
|
+
max-height: calc(100vh - ${theme.margin.m32});
|
|
257
|
+
margin: auto auto 0;
|
|
258
|
+
background-color: transparent;
|
|
259
|
+
box-shadow: ${theme.boxShadow.x2y4};
|
|
260
|
+
|
|
261
|
+
&:modal {
|
|
262
|
+
max-height: calc(100vh - ${theme.margin.m32});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
266
|
+
width: calc(100vw - ${theme.margin.m64});
|
|
267
|
+
max-width: calc(
|
|
268
|
+
${DESKTOP_NOTICE_CONTENT_MAX_WIDTH}px + ${theme.padding.p64}
|
|
269
|
+
);
|
|
270
|
+
margin: auto;
|
|
271
|
+
|
|
272
|
+
&:modal {
|
|
273
|
+
max-width: min(
|
|
274
|
+
calc(${DESKTOP_NOTICE_CONTENT_MAX_WIDTH}px + ${theme.padding.p64}),
|
|
275
|
+
calc(100vw - ${theme.margin.m64})
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
const style = cx(NAME, baseStyle, marginsStyle(props, theme), className);
|
|
282
|
+
|
|
283
|
+
const notice = (
|
|
284
|
+
<div
|
|
285
|
+
className={style}
|
|
286
|
+
data-testid={testId}
|
|
287
|
+
{...props}
|
|
288
|
+
>
|
|
289
|
+
<div className={cx(`${NAME}__content`, contentStyle)}>
|
|
290
|
+
<Heading
|
|
291
|
+
id={noticeHeadingId}
|
|
292
|
+
level='md'
|
|
293
|
+
as='h2'
|
|
294
|
+
noMargins
|
|
295
|
+
className={cx(`${NAME}__heading`, headingStyle)}
|
|
296
|
+
>
|
|
297
|
+
{heading}
|
|
298
|
+
</Heading>
|
|
299
|
+
<Paragraph
|
|
300
|
+
level='md'
|
|
301
|
+
noMargins
|
|
302
|
+
>
|
|
303
|
+
{description ?? (
|
|
304
|
+
<>
|
|
305
|
+
{DEFAULT_DESCRIPTION}
|
|
306
|
+
<Link
|
|
307
|
+
href={policyHref}
|
|
308
|
+
className={policyLinkStyle}
|
|
309
|
+
noVisited
|
|
310
|
+
>
|
|
311
|
+
{policyLabel}
|
|
312
|
+
</Link>
|
|
313
|
+
.{DEFAULT_DESCRIPTION_SUFFIX}
|
|
314
|
+
</>
|
|
315
|
+
)}
|
|
316
|
+
</Paragraph>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div className={cx(`${NAME}__actions`, actionsStyle)}>
|
|
320
|
+
<Button
|
|
321
|
+
variant='secondary'
|
|
322
|
+
fullWidth
|
|
323
|
+
className={cx(`${NAME}__manage-button`, manageButtonStyle)}
|
|
324
|
+
onClick={handleManageCookies}
|
|
325
|
+
>
|
|
326
|
+
{manageCookiesLabel}
|
|
327
|
+
</Button>
|
|
328
|
+
<div className={cx(`${NAME}__accept-actions`, actionGroupStyle)}>
|
|
329
|
+
<Button
|
|
330
|
+
variant='accent'
|
|
331
|
+
fullWidth
|
|
332
|
+
className={cx(
|
|
333
|
+
`${NAME}__accept-necessary-button`,
|
|
334
|
+
actionButtonStyle
|
|
335
|
+
)}
|
|
336
|
+
onClick={handleAcceptNecessary}
|
|
337
|
+
>
|
|
338
|
+
{acceptNecessaryLabel}
|
|
339
|
+
</Button>
|
|
340
|
+
<Button
|
|
341
|
+
variant='accent'
|
|
342
|
+
fullWidth
|
|
343
|
+
className={cx(`${NAME}__accept-all-button`, actionButtonStyle)}
|
|
344
|
+
onClick={handleAcceptAll}
|
|
345
|
+
>
|
|
346
|
+
{acceptAllLabel}
|
|
347
|
+
</Button>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<>
|
|
355
|
+
<BaseDialog
|
|
356
|
+
open
|
|
357
|
+
modal={modal}
|
|
358
|
+
size='large'
|
|
359
|
+
closeOnClickOutside={false}
|
|
360
|
+
className={cx(`${NAME}__dialog`, dialogStyle)}
|
|
361
|
+
aria-labelledby={noticeHeadingId}
|
|
362
|
+
>
|
|
363
|
+
{notice}
|
|
364
|
+
</BaseDialog>
|
|
365
|
+
<CookieSettings
|
|
366
|
+
open={settingsOpen}
|
|
367
|
+
cookies={cookies}
|
|
368
|
+
preferences={draftPreferences}
|
|
369
|
+
policyHref={policyHref}
|
|
370
|
+
policyLabel='UCL privacy policy.'
|
|
371
|
+
onClose={handleSettingsClose}
|
|
372
|
+
onPreferenceToggle={handlePreferenceToggle}
|
|
373
|
+
onSavePreferences={handleSavePreferences}
|
|
374
|
+
/>
|
|
375
|
+
</>
|
|
376
|
+
);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export default CookiesNotice;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
4
|
+
import CookiesNotice from '..';
|
|
5
|
+
import { CookieCategory } from '../types';
|
|
6
|
+
|
|
7
|
+
const originalShowModal = HTMLDialogElement.prototype.showModal;
|
|
8
|
+
const originalShow = HTMLDialogElement.prototype.show;
|
|
9
|
+
const originalClose = HTMLDialogElement.prototype.close;
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
13
|
+
this: HTMLDialogElement
|
|
14
|
+
) {
|
|
15
|
+
this.setAttribute('open', '');
|
|
16
|
+
});
|
|
17
|
+
HTMLDialogElement.prototype.show = vi.fn(function (this: HTMLDialogElement) {
|
|
18
|
+
this.setAttribute('open', '');
|
|
19
|
+
});
|
|
20
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
21
|
+
this.removeAttribute('open');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
HTMLDialogElement.prototype.showModal = originalShowModal;
|
|
27
|
+
HTMLDialogElement.prototype.show = originalShow;
|
|
28
|
+
HTMLDialogElement.prototype.close = originalClose;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const cookies: CookieCategory[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'necessary',
|
|
34
|
+
title: 'Necessary',
|
|
35
|
+
description: 'Required for the site to work.',
|
|
36
|
+
required: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'analytics',
|
|
40
|
+
title: 'Analytics',
|
|
41
|
+
description: 'Helps us improve the service.',
|
|
42
|
+
defaultEnabled: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'maps',
|
|
46
|
+
title: 'Maps',
|
|
47
|
+
description: 'Embeds third-party map services.',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const renderWithTheme = (ui: React.ReactNode) =>
|
|
52
|
+
render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
|
|
53
|
+
|
|
54
|
+
describe('CookiesNotice', () => {
|
|
55
|
+
test('renders custom cookie content and calls accept callbacks with preferences', () => {
|
|
56
|
+
const onAcceptAll = vi.fn();
|
|
57
|
+
const onAcceptNecessary = vi.fn();
|
|
58
|
+
|
|
59
|
+
renderWithTheme(
|
|
60
|
+
<CookiesNotice
|
|
61
|
+
cookies={cookies}
|
|
62
|
+
onAcceptAll={onAcceptAll}
|
|
63
|
+
onAcceptNecessary={onAcceptNecessary}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText('Help us improve your experience')).toBeDefined();
|
|
68
|
+
expect(
|
|
69
|
+
screen.getByRole('dialog', { name: 'Help us improve your experience' })
|
|
70
|
+
).toHaveAttribute('aria-modal', 'false');
|
|
71
|
+
|
|
72
|
+
fireEvent.click(screen.getByRole('button', { name: 'Accept all cookies' }));
|
|
73
|
+
fireEvent.click(
|
|
74
|
+
screen.getByRole('button', { name: 'Accept necessary only' })
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(onAcceptAll).toHaveBeenCalledWith({
|
|
78
|
+
necessary: true,
|
|
79
|
+
analytics: true,
|
|
80
|
+
maps: true,
|
|
81
|
+
});
|
|
82
|
+
expect(onAcceptNecessary).toHaveBeenCalledWith({
|
|
83
|
+
necessary: true,
|
|
84
|
+
analytics: false,
|
|
85
|
+
maps: false,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('renders modal notice as a dialog', () => {
|
|
90
|
+
renderWithTheme(
|
|
91
|
+
<CookiesNotice
|
|
92
|
+
modal
|
|
93
|
+
cookies={cookies}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(
|
|
98
|
+
screen.getByRole('dialog', { name: 'Help us improve your experience' })
|
|
99
|
+
).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('opens settings and saves selected preferences', () => {
|
|
103
|
+
const onManageCookies = vi.fn();
|
|
104
|
+
const onSavePreferences = vi.fn();
|
|
105
|
+
|
|
106
|
+
renderWithTheme(
|
|
107
|
+
<CookiesNotice
|
|
108
|
+
cookies={cookies}
|
|
109
|
+
onManageCookies={onManageCookies}
|
|
110
|
+
onSavePreferences={onSavePreferences}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
fireEvent.click(screen.getByRole('button', { name: 'Manage cookies' }));
|
|
115
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Allow Analytics' }));
|
|
116
|
+
fireEvent.click(screen.getByRole('button', { name: 'Save preferences' }));
|
|
117
|
+
|
|
118
|
+
expect(onManageCookies).toHaveBeenCalledTimes(1);
|
|
119
|
+
expect(onSavePreferences).toHaveBeenCalledWith({
|
|
120
|
+
necessary: true,
|
|
121
|
+
analytics: false,
|
|
122
|
+
maps: false,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('keeps saved preferences when settings is reopened', () => {
|
|
127
|
+
const onSavePreferences = vi.fn();
|
|
128
|
+
|
|
129
|
+
renderWithTheme(
|
|
130
|
+
<CookiesNotice
|
|
131
|
+
cookies={cookies}
|
|
132
|
+
onSavePreferences={onSavePreferences}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
fireEvent.click(screen.getByRole('button', { name: 'Manage cookies' }));
|
|
137
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Allow Analytics' }));
|
|
138
|
+
fireEvent.click(screen.getByRole('button', { name: 'Save preferences' }));
|
|
139
|
+
|
|
140
|
+
fireEvent.click(screen.getByRole('button', { name: 'Manage cookies' }));
|
|
141
|
+
fireEvent.click(screen.getByRole('button', { name: 'Save preferences' }));
|
|
142
|
+
|
|
143
|
+
expect(onSavePreferences).toHaveBeenLastCalledWith({
|
|
144
|
+
necessary: true,
|
|
145
|
+
analytics: false,
|
|
146
|
+
maps: false,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('passes existing preferences into managed settings', () => {
|
|
151
|
+
const onSavePreferences = vi.fn();
|
|
152
|
+
|
|
153
|
+
renderWithTheme(
|
|
154
|
+
<CookiesNotice
|
|
155
|
+
cookies={cookies}
|
|
156
|
+
preferences={{
|
|
157
|
+
necessary: true,
|
|
158
|
+
analytics: false,
|
|
159
|
+
maps: true,
|
|
160
|
+
}}
|
|
161
|
+
onSavePreferences={onSavePreferences}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
fireEvent.click(screen.getByRole('button', { name: 'Manage cookies' }));
|
|
166
|
+
|
|
167
|
+
expect(screen.getByRole('dialog', { name: 'Privacy preferences' }));
|
|
168
|
+
expect(screen.getByText('Embeds third-party map services.')).toBeDefined();
|
|
169
|
+
|
|
170
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Allow Analytics' }));
|
|
171
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Allow Maps' }));
|
|
172
|
+
fireEvent.click(screen.getByRole('button', { name: 'Save preferences' }));
|
|
173
|
+
|
|
174
|
+
expect(onSavePreferences).toHaveBeenCalledWith({
|
|
175
|
+
necessary: true,
|
|
176
|
+
analytics: true,
|
|
177
|
+
maps: false,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CookieCategory } from './types';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_POLICY_HREF =
|
|
4
|
+
'https://www.ucl.ac.uk/legal-services/privacy/cookie-policy';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_HEADING = 'Help us improve your experience';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_DESCRIPTION =
|
|
9
|
+
'We use cookies for authentication, analytics, and third-party services like Google Maps. Some cookies are required for the site to function and cannot be turned off. By accepting, you agree to the ';
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_DESCRIPTION_SUFFIX =
|
|
12
|
+
' You can update your preferences any time.';
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_COOKIE_CATEGORIES: CookieCategory[] = [
|
|
15
|
+
{
|
|
16
|
+
id: 'necessary',
|
|
17
|
+
title: 'Necessary Cookies',
|
|
18
|
+
description:
|
|
19
|
+
"These cookies are necessary for the website to function and can't be switched off in our systems.",
|
|
20
|
+
required: true,
|
|
21
|
+
defaultEnabled: true,
|
|
22
|
+
learnMoreHref: DEFAULT_POLICY_HREF,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'analytics',
|
|
26
|
+
title: 'Analytics',
|
|
27
|
+
description:
|
|
28
|
+
'These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site.',
|
|
29
|
+
defaultEnabled: true,
|
|
30
|
+
learnMoreHref: DEFAULT_POLICY_HREF,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'marketing',
|
|
34
|
+
title: 'Marketing',
|
|
35
|
+
description:
|
|
36
|
+
'These cookies are set through our site by our advertising partners. They may be used to build a profile of your interests and show you relevant ads.',
|
|
37
|
+
defaultEnabled: true,
|
|
38
|
+
learnMoreHref: DEFAULT_POLICY_HREF,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type CookiePreferences = Record<string, boolean>;
|
|
4
|
+
|
|
5
|
+
export interface CookieCategory {
|
|
6
|
+
id: string;
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
description: ReactNode;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
defaultEnabled?: boolean;
|
|
11
|
+
toggleLabel?: string;
|
|
12
|
+
learnMoreHref?: string;
|
|
13
|
+
learnMoreLabel?: ReactNode;
|
|
14
|
+
}
|
|
@@ -60,6 +60,7 @@ const Input = forwardRef<Ref, InputProps>(
|
|
|
60
60
|
color: ${theme.colour.text.default};
|
|
61
61
|
background-color: ${theme.colour.fill.inverse};
|
|
62
62
|
border: ${theme.border.b1} solid ${theme.colour.speciality.inputDefault};
|
|
63
|
+
border-radius: ${theme.radius.r4};
|
|
63
64
|
outline: none;
|
|
64
65
|
|
|
65
66
|
&::placeholder {
|
|
@@ -5,7 +5,7 @@ exports[`Input > snapshot: no props 1`] = `
|
|
|
5
5
|
class="ucl-uikit-input css-1bpb1dw"
|
|
6
6
|
>
|
|
7
7
|
<input
|
|
8
|
-
class="ucl-uikit-input__input css-
|
|
8
|
+
class="ucl-uikit-input__input css-i99dnf"
|
|
9
9
|
data-testid="ucl-uikit-input"
|
|
10
10
|
id=""
|
|
11
11
|
/>
|
|
@@ -17,7 +17,7 @@ exports[`Input > snapshot: placeholder prop 1`] = `
|
|
|
17
17
|
class="ucl-uikit-input css-1bpb1dw"
|
|
18
18
|
>
|
|
19
19
|
<input
|
|
20
|
-
class="ucl-uikit-input__input css-
|
|
20
|
+
class="ucl-uikit-input__input css-i99dnf"
|
|
21
21
|
data-testid="ucl-uikit-input"
|
|
22
22
|
id=""
|
|
23
23
|
placeholder="Type your value..."
|
|
@@ -30,7 +30,7 @@ exports[`Input > snapshot: testId prop 1`] = `
|
|
|
30
30
|
class="ucl-uikit-input css-1bpb1dw"
|
|
31
31
|
>
|
|
32
32
|
<input
|
|
33
|
-
class="ucl-uikit-input__input css-
|
|
33
|
+
class="ucl-uikit-input__input css-i99dnf"
|
|
34
34
|
data-testid="test123"
|
|
35
35
|
id=""
|
|
36
36
|
/>
|
|
@@ -42,7 +42,7 @@ exports[`Input > snapshot: value prop 1`] = `
|
|
|
42
42
|
class="ucl-uikit-input css-1bpb1dw"
|
|
43
43
|
>
|
|
44
44
|
<input
|
|
45
|
-
class="ucl-uikit-input__input css-
|
|
45
|
+
class="ucl-uikit-input__input css-i99dnf"
|
|
46
46
|
data-testid="ucl-uikit-input"
|
|
47
47
|
id=""
|
|
48
48
|
value="abc"
|
|
@@ -13,7 +13,7 @@ export type ParagraphLevel =
|
|
|
13
13
|
| 'md'
|
|
14
14
|
| 'md-medium'
|
|
15
15
|
| 'md-semibold'
|
|
16
|
-
| 'md-
|
|
16
|
+
| 'md-numeric'
|
|
17
17
|
| 'md-medium-numeric'
|
|
18
18
|
| 'sm'
|
|
19
19
|
| 'sm-medium'
|
|
@@ -57,6 +57,7 @@ const Paragraph = ({
|
|
|
57
57
|
md,
|
|
58
58
|
mdMedium,
|
|
59
59
|
mdSemibold,
|
|
60
|
+
mdNumeric,
|
|
60
61
|
mdMediumNumeric,
|
|
61
62
|
sm,
|
|
62
63
|
smMedium,
|
|
@@ -134,7 +135,17 @@ const Paragraph = ({
|
|
|
134
135
|
line-height: ${mdSemibold.lineHeight}%;
|
|
135
136
|
`;
|
|
136
137
|
|
|
138
|
+
const mdNumericStyle = css`
|
|
139
|
+
font-family: ${mdNumeric.fontFamily};
|
|
140
|
+
font-feature-settings: ${mdNumeric.fontSettings};
|
|
141
|
+
font-size: ${mdNumeric.fontSize}px;
|
|
142
|
+
font-weight: ${mdNumeric.fontWeight};
|
|
143
|
+
line-height: ${mdNumeric.lineHeight}%;
|
|
144
|
+
`;
|
|
145
|
+
|
|
137
146
|
const mdMediumNumericStyle = css`
|
|
147
|
+
font-family: ${mdMediumNumeric.fontFamily};
|
|
148
|
+
font-feature-settings: ${mdMediumNumeric.fontSettings};
|
|
138
149
|
font-size: ${mdMediumNumeric.fontSize}px;
|
|
139
150
|
font-weight: ${mdMediumNumeric.fontWeight};
|
|
140
151
|
line-height: ${mdMediumNumeric.lineHeight}%;
|
|
@@ -180,6 +191,7 @@ const Paragraph = ({
|
|
|
180
191
|
mappedLevel === 'md' && mdStyle,
|
|
181
192
|
mappedLevel === 'md-medium' && mdMediumStyle,
|
|
182
193
|
mappedLevel === 'md-semibold' && mdSemiboldStyle,
|
|
194
|
+
mappedLevel === 'md-numeric' && mdNumericStyle,
|
|
183
195
|
mappedLevel === 'md-medium-numeric' && mdMediumNumericStyle,
|
|
184
196
|
mappedLevel === 'sm' && smStyle,
|
|
185
197
|
mappedLevel === 'sm-medium' && smMediumStyle,
|