uikit-react-public 0.44.4 → 0.45.0
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/Badge/index.d.ts +1 -0
- 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/index.d.ts +4 -1
- package/dist/index.js +5367 -4856
- package/lib/components/Badge/index.ts +1 -0
- 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/index.ts +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MouseEvent as ReactMouseEvent,
|
|
3
|
+
MouseEventHandler,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useId,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { css, cx } from '@emotion/css';
|
|
9
|
+
import useTheme from '../../theme/useTheme';
|
|
10
|
+
import BaseDialog, { BaseDialogProps } from '../Dialog/BaseDialog';
|
|
11
|
+
import Heading from '../Heading';
|
|
12
|
+
import Paragraph from '../Paragraph';
|
|
13
|
+
import Button from '../Button';
|
|
14
|
+
import Icon from '../Icon';
|
|
15
|
+
import IconButton from '../IconButton';
|
|
16
|
+
import Link from '../Link';
|
|
17
|
+
import StandaloneLink from '../StandaloneLink';
|
|
18
|
+
import Toggle from '../Toggle';
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_COOKIE_CATEGORIES,
|
|
21
|
+
DEFAULT_DESCRIPTION,
|
|
22
|
+
DEFAULT_DESCRIPTION_SUFFIX,
|
|
23
|
+
DEFAULT_POLICY_HREF,
|
|
24
|
+
} from './defaults';
|
|
25
|
+
import { CookieCategory, CookiePreferences } from './types';
|
|
26
|
+
|
|
27
|
+
const NAME = 'ucl-uikit-cookie-settings';
|
|
28
|
+
|
|
29
|
+
interface CookieSettingsProps extends Omit<
|
|
30
|
+
BaseDialogProps,
|
|
31
|
+
'children' | 'onChange' | 'onClose' | 'size' | 'modal'
|
|
32
|
+
> {
|
|
33
|
+
cookies: CookieCategory[];
|
|
34
|
+
preferences: CookiePreferences;
|
|
35
|
+
description?: ReactNode;
|
|
36
|
+
policyHref?: string;
|
|
37
|
+
policyLabel?: ReactNode;
|
|
38
|
+
onClose?: (ev?: ReactMouseEvent | KeyboardEvent) => void;
|
|
39
|
+
onPreferenceToggle: (cookie: CookieCategory) => void;
|
|
40
|
+
onSavePreferences?: () => void;
|
|
41
|
+
saveButtonLabel?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const CookieSettings = ({
|
|
45
|
+
open = false,
|
|
46
|
+
cookies = DEFAULT_COOKIE_CATEGORIES,
|
|
47
|
+
preferences,
|
|
48
|
+
description,
|
|
49
|
+
policyHref = DEFAULT_POLICY_HREF,
|
|
50
|
+
policyLabel = 'UCL privacy policy.',
|
|
51
|
+
onClose,
|
|
52
|
+
onPreferenceToggle,
|
|
53
|
+
onSavePreferences,
|
|
54
|
+
saveButtonLabel = 'Save preferences',
|
|
55
|
+
testId = NAME,
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
58
|
+
}: CookieSettingsProps) => {
|
|
59
|
+
const [theme] = useTheme();
|
|
60
|
+
const headingId = useId();
|
|
61
|
+
const descriptionId = useId();
|
|
62
|
+
|
|
63
|
+
const handleSave = useCallback(() => {
|
|
64
|
+
onSavePreferences?.();
|
|
65
|
+
}, [onSavePreferences]);
|
|
66
|
+
|
|
67
|
+
const handleCloseClick: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
68
|
+
(ev) => {
|
|
69
|
+
onClose?.(ev);
|
|
70
|
+
},
|
|
71
|
+
[onClose]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const rootStyle = css`
|
|
75
|
+
background-color: ${theme.colour.surface.default};
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const headerStyle = css`
|
|
79
|
+
display: flex;
|
|
80
|
+
justify-content: space-between;
|
|
81
|
+
align-items: flex-start;
|
|
82
|
+
gap: ${theme.padding.p16};
|
|
83
|
+
padding: ${theme.padding.p32} ${theme.padding.p24} 0 ${theme.padding.p32};
|
|
84
|
+
|
|
85
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
86
|
+
padding-right: ${theme.padding.p32};
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const headingStyle = css`
|
|
91
|
+
padding-top: ${theme.padding.p32};
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const closeButtonStyle = css`
|
|
95
|
+
flex: 0 0 auto;
|
|
96
|
+
color: ${theme.colour.icon.secondary};
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const bodyStyle = css`
|
|
100
|
+
padding: ${theme.padding.p24} ${theme.padding.p32};
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
const contentStyle = css`
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
gap: ${theme.padding.p24};
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const policyLinkStyle = css`
|
|
110
|
+
color: ${theme.colour.text.default};
|
|
111
|
+
|
|
112
|
+
&:visited {
|
|
113
|
+
color: ${theme.colour.text.default};
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
const listStyle = css`
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
gap: ${theme.padding.p24};
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const cookieStyle = css`
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: ${theme.padding.p16};
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const cookieHeaderStyle = css`
|
|
130
|
+
display: flex;
|
|
131
|
+
justify-content: space-between;
|
|
132
|
+
align-items: flex-start;
|
|
133
|
+
gap: ${theme.padding.p16};
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const cookieTextStyle = css`
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
gap: ${theme.padding.p16};
|
|
140
|
+
min-width: 0;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const toggleStyle = css`
|
|
144
|
+
flex: 0 0 auto;
|
|
145
|
+
margin-top: ${theme.margin.m2};
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const learnMoreStyle = css`
|
|
149
|
+
width: fit-content;
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const footerStyle = css`
|
|
153
|
+
padding: 0 ${theme.padding.p32} ${theme.padding.p32};
|
|
154
|
+
display: flex;
|
|
155
|
+
justify-content: stretch;
|
|
156
|
+
|
|
157
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
158
|
+
justify-content: flex-end;
|
|
159
|
+
padding-top: ${theme.padding.p16};
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const saveButtonStyle = css`
|
|
164
|
+
width: 100%;
|
|
165
|
+
|
|
166
|
+
@media (min-width: ${theme.breakpoints.tablet}px) {
|
|
167
|
+
width: auto;
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
const style = cx(NAME, rootStyle, className);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<BaseDialog
|
|
175
|
+
open={open}
|
|
176
|
+
modal
|
|
177
|
+
size='large'
|
|
178
|
+
onClose={onClose}
|
|
179
|
+
className={style}
|
|
180
|
+
aria-labelledby={headingId}
|
|
181
|
+
aria-describedby={descriptionId}
|
|
182
|
+
testId={testId}
|
|
183
|
+
{...props}
|
|
184
|
+
>
|
|
185
|
+
<header className={cx(`${NAME}__header`, headerStyle)}>
|
|
186
|
+
<Heading
|
|
187
|
+
id={headingId}
|
|
188
|
+
level='lg'
|
|
189
|
+
noMargins
|
|
190
|
+
className={cx(`${NAME}__heading`, headingStyle)}
|
|
191
|
+
>
|
|
192
|
+
Privacy preferences
|
|
193
|
+
</Heading>
|
|
194
|
+
<IconButton
|
|
195
|
+
aria-label='Close cookie settings'
|
|
196
|
+
className={cx(`${NAME}__close-button`, closeButtonStyle)}
|
|
197
|
+
onClick={handleCloseClick}
|
|
198
|
+
>
|
|
199
|
+
<Icon.X
|
|
200
|
+
role='presentation'
|
|
201
|
+
aria-hidden='true'
|
|
202
|
+
/>
|
|
203
|
+
</IconButton>
|
|
204
|
+
</header>
|
|
205
|
+
|
|
206
|
+
<div className={cx(`${NAME}__body`, bodyStyle)}>
|
|
207
|
+
<div className={cx(`${NAME}__content`, contentStyle)}>
|
|
208
|
+
{description !== undefined && (
|
|
209
|
+
<Paragraph
|
|
210
|
+
id={descriptionId}
|
|
211
|
+
level='md'
|
|
212
|
+
noMargins
|
|
213
|
+
>
|
|
214
|
+
{description}
|
|
215
|
+
</Paragraph>
|
|
216
|
+
)}
|
|
217
|
+
{description === undefined && (
|
|
218
|
+
<Paragraph
|
|
219
|
+
id={descriptionId}
|
|
220
|
+
level='md'
|
|
221
|
+
noMargins
|
|
222
|
+
>
|
|
223
|
+
{DEFAULT_DESCRIPTION}
|
|
224
|
+
<Link
|
|
225
|
+
href={policyHref}
|
|
226
|
+
className={policyLinkStyle}
|
|
227
|
+
noVisited
|
|
228
|
+
>
|
|
229
|
+
{policyLabel}
|
|
230
|
+
</Link>
|
|
231
|
+
{DEFAULT_DESCRIPTION_SUFFIX}
|
|
232
|
+
</Paragraph>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
<div className={cx(`${NAME}__cookies`, listStyle)}>
|
|
236
|
+
{cookies.map((cookie) => (
|
|
237
|
+
<section
|
|
238
|
+
key={cookie.id}
|
|
239
|
+
className={cx(`${NAME}__cookie`, cookieStyle)}
|
|
240
|
+
>
|
|
241
|
+
<div
|
|
242
|
+
className={cx(`${NAME}__cookie-header`, cookieHeaderStyle)}
|
|
243
|
+
>
|
|
244
|
+
<div className={cx(`${NAME}__cookie-text`, cookieTextStyle)}>
|
|
245
|
+
<Heading
|
|
246
|
+
level='md'
|
|
247
|
+
as='h3'
|
|
248
|
+
noMargins
|
|
249
|
+
>
|
|
250
|
+
{cookie.title}
|
|
251
|
+
</Heading>
|
|
252
|
+
<Paragraph
|
|
253
|
+
level='md'
|
|
254
|
+
noMargins
|
|
255
|
+
>
|
|
256
|
+
{cookie.description}
|
|
257
|
+
</Paragraph>
|
|
258
|
+
</div>
|
|
259
|
+
<Toggle
|
|
260
|
+
checked={preferences[cookie.id]}
|
|
261
|
+
disabled={cookie.required}
|
|
262
|
+
aria-label={
|
|
263
|
+
cookie.toggleLabel ??
|
|
264
|
+
(typeof cookie.title === 'string'
|
|
265
|
+
? `Allow ${cookie.title}`
|
|
266
|
+
: `Allow ${cookie.id} cookies`)
|
|
267
|
+
}
|
|
268
|
+
className={cx(`${NAME}__cookie-toggle`, toggleStyle)}
|
|
269
|
+
onClick={() => onPreferenceToggle(cookie)}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
{cookie.learnMoreHref && (
|
|
273
|
+
<StandaloneLink
|
|
274
|
+
href={cookie.learnMoreHref}
|
|
275
|
+
variant='primary'
|
|
276
|
+
icon={<Icon.ChevronRight />}
|
|
277
|
+
iconPosition='right'
|
|
278
|
+
noVisited
|
|
279
|
+
className={cx(`${NAME}__learn-more`, learnMoreStyle)}
|
|
280
|
+
>
|
|
281
|
+
{cookie.learnMoreLabel ?? 'Learn more'}
|
|
282
|
+
</StandaloneLink>
|
|
283
|
+
)}
|
|
284
|
+
</section>
|
|
285
|
+
))}
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<footer className={cx(`${NAME}__footer`, footerStyle)}>
|
|
291
|
+
<Button
|
|
292
|
+
variant='accent'
|
|
293
|
+
size='medium'
|
|
294
|
+
className={cx(`${NAME}__save-button`, saveButtonStyle)}
|
|
295
|
+
onClick={handleSave}
|
|
296
|
+
>
|
|
297
|
+
{saveButtonLabel}
|
|
298
|
+
</Button>
|
|
299
|
+
</footer>
|
|
300
|
+
</BaseDialog>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export default CookieSettings;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import CookiesNotice from '.';
|
|
3
|
+
|
|
4
|
+
const cookies = [
|
|
5
|
+
{
|
|
6
|
+
id: 'necessary',
|
|
7
|
+
title: 'Necessary Cookies',
|
|
8
|
+
description:
|
|
9
|
+
"These cookies are necessary for the website to function and can't be switched off in our systems.",
|
|
10
|
+
required: true,
|
|
11
|
+
defaultEnabled: true,
|
|
12
|
+
learnMoreHref: 'https://www.ucl.ac.uk/legal-services/privacy/cookie-policy',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'analytics',
|
|
16
|
+
title: 'Analytics',
|
|
17
|
+
description:
|
|
18
|
+
'These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site.',
|
|
19
|
+
defaultEnabled: true,
|
|
20
|
+
learnMoreHref: 'https://www.ucl.ac.uk/legal-services/privacy/cookie-policy',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'marketing',
|
|
24
|
+
title: 'Marketing',
|
|
25
|
+
description:
|
|
26
|
+
'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.',
|
|
27
|
+
defaultEnabled: true,
|
|
28
|
+
learnMoreHref: 'https://www.ucl.ac.uk/legal-services/privacy/cookie-policy',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const meta = {
|
|
33
|
+
title: 'Components/CookiesNotice',
|
|
34
|
+
component: CookiesNotice,
|
|
35
|
+
parameters: {
|
|
36
|
+
layout: 'centered',
|
|
37
|
+
},
|
|
38
|
+
tags: ['autodocs'],
|
|
39
|
+
} satisfies Meta<typeof CookiesNotice>;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const NonModal: Story = {
|
|
45
|
+
name: 'Non-modal',
|
|
46
|
+
args: {
|
|
47
|
+
cookies,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Modal: Story = {
|
|
52
|
+
name: 'Modal',
|
|
53
|
+
args: {
|
|
54
|
+
cookies,
|
|
55
|
+
modal: true,
|
|
56
|
+
},
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'fullscreen',
|
|
59
|
+
},
|
|
60
|
+
};
|