@redacto.io/consent-sdk-react 7.3.0 → 8.1.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.
Files changed (27) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/chunk-6A665OPR.mjs +30 -0
  3. package/dist/index.mjs +85 -108
  4. package/dist/privacy-center.js +3519 -3281
  5. package/dist/privacy-center.mjs +3521 -3295
  6. package/package.json +1 -1
  7. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +3 -3
  8. package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +204 -0
  9. package/src/RedactoPrivacyCenter/components/AppShell/MobileNavDrawer.tsx +218 -0
  10. package/src/RedactoPrivacyCenter/components/AppShell/NavItem.tsx +205 -0
  11. package/src/RedactoPrivacyCenter/components/AppShell/PageHeader.tsx +107 -0
  12. package/src/RedactoPrivacyCenter/components/AppShell/SidebarNav.tsx +100 -0
  13. package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +338 -0
  14. package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +224 -0
  15. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useIsTruncated.ts +28 -0
  16. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useKeyboardShortcut.ts +40 -0
  17. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useSidebarState.ts +62 -0
  18. package/src/RedactoPrivacyCenter/components/Form.tsx +1 -5
  19. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +5 -27
  20. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +0 -19
  21. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +42 -5
  22. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +13 -2
  23. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +0 -350
  24. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +0 -489
  25. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +0 -408
  26. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +0 -1
  27. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +0 -38
@@ -1,489 +0,0 @@
1
- import { cn } from '../../utils/cn';
2
- import SimpleBar from '../../ui/SimpleBar';
3
- import { PCText, PCAvatar, PCButton, PCActionIcon } from '../../ui';
4
- import { PCTooltip } from '../../ui/PCTooltip';
5
- import { pcStyles } from '../../styles/pcStyles';
6
- import { useState, useEffect, useCallback, useMemo, type CSSProperties } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import {
9
- IoArrowBackOutline,
10
- IoChevronBackOutline,
11
- IoChevronForwardOutline,
12
- IoCloseOutline,
13
- IoLogOutOutline,
14
- IoMenuOutline,
15
- } from 'react-icons/io5';
16
-
17
- const getVar = (varName: string): string => `var(${varName})`;
18
- import { SideMenuPC } from './SideMenuPC';
19
- import { useMenuItemsPC } from '../../lib/constants';
20
- import { useOrganization } from '../../context/OrganizationContext';
21
- import { useBranding } from '../../context/BrandingContext';
22
- import type { SideBarPCProps } from './types';
23
- import { signOutPrivacyCenter } from '../../lib/utils';
24
-
25
- export default function SideBarPC({
26
- className,
27
- isCompact = false,
28
- onBack,
29
- }: SideBarPCProps) {
30
- const { t } = useTranslation();
31
- const menuItems = useMenuItemsPC();
32
- const [isCollapsed, setIsCollapsed] = useState(true); // Always start collapsed
33
- const [isMobileOpen, setIsMobileOpen] = useState(false);
34
- const [isDesktopHovered, setIsDesktopHovered] = useState(false);
35
- const [email, setEmail] = useState<string>('');
36
- // const [isSigningOut, setIsSigningOut] = useState(false);
37
- const { organization } = useOrganization();
38
- const branding = useBranding();
39
-
40
- useEffect(() => {
41
- setEmail(sessionStorage.getItem('email') || '');
42
- }, []);
43
-
44
- // const handleSignOut = useCallback(async () => {
45
- // setIsSigningOut(true);
46
- // signOutPrivacyCenter();
47
- // // Sign out handled - parent component should handle navigation/error
48
- // setIsSigningOut(false);
49
- // }, []);
50
-
51
- const handleDesktopToggle = useCallback(() => {
52
- if (!isCompact && !isMobileOpen) {
53
- setIsCollapsed((prev) => !prev);
54
- }
55
- }, [isCompact, isMobileOpen]);
56
-
57
- const handleMobileToggle = useCallback(() => {
58
- setIsMobileOpen((prev) => !prev);
59
- }, []);
60
-
61
- const handleMobileClose = useCallback(() => {
62
- setIsMobileOpen(false);
63
- }, []);
64
-
65
- const handleMenuItemSelect = useCallback(() => {
66
- if (isMobileOpen) {
67
- setIsMobileOpen(false);
68
- return;
69
- }
70
-
71
- if (!isCompact) {
72
- setIsCollapsed(true);
73
- }
74
- }, [isCompact, isMobileOpen]);
75
-
76
- const handleDesktopMouseEnter = useCallback(() => {
77
- if (!isMobileOpen) {
78
- setIsDesktopHovered(true);
79
- }
80
- }, [isMobileOpen]);
81
-
82
- const handleDesktopMouseLeave = useCallback(() => {
83
- setIsDesktopHovered(false);
84
- }, []);
85
-
86
- // Close mobile menu when clicking outside
87
- useEffect(() => {
88
- const handleClickOutside = (event: MouseEvent) => {
89
- const target = event.target as HTMLElement;
90
- if (
91
- isMobileOpen &&
92
- !target.closest('aside') &&
93
- !target.closest('[data-mobile-menu-button]')
94
- ) {
95
- setIsMobileOpen(false);
96
- }
97
- };
98
-
99
- if (isMobileOpen) {
100
- document.addEventListener('mousedown', handleClickOutside);
101
- // Prevent body scroll when mobile menu is open
102
- document.body.style.overflow = 'hidden';
103
- } else {
104
- document.body.style.overflow = '';
105
- }
106
-
107
- return () => {
108
- document.removeEventListener('mousedown', handleClickOutside);
109
- document.body.style.overflow = '';
110
- };
111
- }, [isMobileOpen]);
112
-
113
- const organizationName = organization?.name || '';
114
- const displayName = branding?.brand_name || organizationName;
115
- const isExpanded = isMobileOpen || !isCollapsed;
116
-
117
- const sidebarClassName = useMemo(
118
- () =>
119
- cn(
120
- className,
121
- isCollapsed && 'pc-sidebar-collapsed'
122
- ),
123
- [isCollapsed, className]
124
- );
125
-
126
- const sidebarStyle = useMemo<CSSProperties>(
127
- () => ({
128
- position: 'fixed',
129
- top: 0,
130
- bottom: 0,
131
- left: 0,
132
- zIndex: 1100,
133
- backgroundColor: getVar('--pc-bg-shell'),
134
- boxShadow: getVar('--pc-sidebar-shadow'),
135
- transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease, border-color 0.3s ease',
136
- display: 'flex',
137
- flexDirection: 'column',
138
- overflowY: 'hidden',
139
- overflowX: 'visible',
140
- boxSizing: 'border-box',
141
- height: '100vh',
142
- willChange: 'width',
143
- // Width will be controlled by CSS classes, but set default inline for non-desktop
144
- width: isMobileOpen
145
- ? getVar('--pc-sidebar-mobile-width')
146
- : isCollapsed
147
- ? getVar('--pc-sidebar-collapsed-width')
148
- : getVar('--pc-sidebar-expanded-width'),
149
- }),
150
- [isCollapsed, isMobileOpen]
151
- );
152
-
153
- const headerTextStyle = useMemo(
154
- () => ({
155
- overflow: 'hidden',
156
- transition: 'opacity 0.3s ease, width 0.3s ease, max-width 0.3s ease, margin-left 0.3s ease',
157
- flex: 1,
158
- minWidth: 0,
159
- willChange: 'opacity, width, max-width, margin-left',
160
- ...(isMobileOpen || !isCollapsed ? {
161
- opacity: 1,
162
- maxWidth: '100%',
163
- width: 'auto',
164
- marginLeft: '0.75rem',
165
- } : {
166
- width: 0,
167
- opacity: 0,
168
- maxWidth: 0,
169
- marginLeft: 0,
170
- }),
171
- }),
172
- [isCollapsed, isMobileOpen]
173
- );
174
-
175
- const emailContainerStyle = useMemo(
176
- () => ({
177
- overflow: 'hidden',
178
- transition: 'all 0.3s',
179
- ...(isMobileOpen || !isCollapsed ? {
180
- height: email ? 'auto' : 0,
181
- opacity: email ? 1 : 0,
182
- marginBottom: email ? '0.5rem' : 0,
183
- } : {
184
- height: 0,
185
- opacity: 0,
186
- marginBottom: 0,
187
- }),
188
- }),
189
- [isCollapsed, isMobileOpen, email]
190
- );
191
-
192
- const buttonStyle = useMemo(
193
- () => ({
194
- height: 'auto',
195
- minHeight: '2.25rem',
196
- width: '100%',
197
- justifyContent: (isMobileOpen || !isCollapsed) ? 'flex-start' : 'center',
198
- padding: 0,
199
- fontWeight: pcStyles.fontWeightMedium.fontWeight,
200
- color: getVar('--pc-text-primary'),
201
- outline: 'none',
202
- transition: 'all 0.3s',
203
- alignItems: 'center',
204
- display: 'flex',
205
- }),
206
- [isCollapsed, isMobileOpen]
207
- );
208
-
209
- const handleBack = useCallback(() => {
210
- if (onBack === 'back') {
211
- window.history.back();
212
- } else if (onBack === 'signout') {
213
- signOutPrivacyCenter();
214
- // Redirect to previous page after clearing cookies and sessionStorage
215
- window.history.back();
216
- }
217
- }, [onBack]);
218
-
219
- const footerActionLabel = onBack === 'back' ? t('back') : t('signOut');
220
- const footerActionIconSize = !isMobileOpen && isCollapsed ? 19 : 18;
221
- const footerActionIcon =
222
- onBack === 'back' ? (
223
- <IoArrowBackOutline size={footerActionIconSize} style={{ flexShrink: 0 }} />
224
- ) : (
225
- <IoLogOutOutline size={footerActionIconSize} style={{ flexShrink: 0 }} />
226
- );
227
-
228
- return (
229
- <>
230
- {!isCompact && !isMobileOpen && (
231
- <PCActionIcon
232
- variant="outline"
233
- aria-label={isCollapsed ? t('expandSidebar') : t('collapseSidebar')}
234
- className="pc-sidebar-toggle-desktop pc-hidden-xs pc-hidden-sm pc-flex-md"
235
- data-collapsed={isCollapsed ? 'true' : 'false'}
236
- data-visible={(!isCollapsed || isDesktopHovered) ? 'true' : 'false'}
237
- style={{
238
- position: 'fixed',
239
- top: '28px',
240
- zIndex: 2000,
241
- width: '28px',
242
- height: '28px',
243
- borderRadius: '9999px',
244
- borderColor: getVar('--pc-border-color'),
245
- backgroundColor: getVar('--pc-bg-primary'),
246
- color: getVar('--pc-text-secondary'),
247
- boxShadow: '0 10px 24px rgba(15, 23, 42, 0.14)',
248
- }}
249
- onMouseEnter={handleDesktopMouseEnter}
250
- onMouseLeave={handleDesktopMouseLeave}
251
- onClick={handleDesktopToggle}
252
- >
253
- {isCollapsed ? (
254
- <IoChevronForwardOutline size={14} />
255
- ) : (
256
- <IoChevronBackOutline size={14} />
257
- )}
258
- </PCActionIcon>
259
- )}
260
-
261
- {/* Mobile Menu Button - visible only on mobile when sidebar is closed */}
262
- {!isMobileOpen && (
263
- <PCActionIcon
264
- data-mobile-menu-button
265
- aria-label={t('openSidebarMenu')}
266
- variant="default"
267
- style={{
268
- position: 'fixed',
269
- left: '0.75rem',
270
- top: '0.75rem',
271
- zIndex: 992,
272
- height: '2.25rem',
273
- width: '2.25rem',
274
- padding: 0,
275
- color: getVar('--pc-text-primary'),
276
- }}
277
- className="pc-hidden-md"
278
- onClick={handleMobileToggle}
279
- >
280
- <IoMenuOutline size={20} />
281
- </PCActionIcon>
282
- )}
283
-
284
- {/* Mobile Overlay */}
285
- <div
286
- className="pc-hidden-md pc-mobile-overlay"
287
- data-open={isMobileOpen}
288
- style={{
289
- position: 'fixed',
290
- inset: 0,
291
- zIndex: 990,
292
- backgroundColor: getVar('--pc-bg-overlay'),
293
- }}
294
- onClick={handleMobileClose}
295
- aria-hidden={!isMobileOpen}
296
- />
297
-
298
- {/* Sidebar */}
299
- <aside
300
- style={sidebarStyle}
301
- className={cn(sidebarClassName, 'pc-sidebar')}
302
- data-mobile-open={isMobileOpen}
303
- onMouseEnter={handleDesktopMouseEnter}
304
- onMouseLeave={handleDesktopMouseLeave}
305
- >
306
- {/* Sidebar Header */}
307
- <div style={{
308
- flexShrink: 0,
309
- ...pcStyles.flex,
310
- minHeight: '56px',
311
- height: '56px',
312
- ...pcStyles.itemsCenter,
313
- ...(isMobileOpen || !isCollapsed ? { ...pcStyles.justifyBetween } : { ...pcStyles.justifyCenter }),
314
- backgroundColor: getVar('--pc-bg-shell'),
315
- paddingLeft: isMobileOpen || !isCollapsed ? '1rem' : '0.5rem',
316
- paddingRight: isMobileOpen || !isCollapsed ? '1rem' : '0.5rem',
317
- transition: 'padding-left 0.3s ease, padding-right 0.3s ease, justify-content 0.3s ease',
318
- willChange: 'padding-left, padding-right',
319
- }}>
320
- <div style={{
321
- ...pcStyles.flex,
322
- ...pcStyles.itemsCenter,
323
- ...(isMobileOpen || !isCollapsed ? { flex: 1, minWidth: 0 } : { justifyContent: 'center', width: '100%' }),
324
- transition: 'flex 0.3s ease, min-width 0.3s ease, justify-content 0.3s ease, width 0.3s ease',
325
- willChange: 'flex, min-width, justify-content, width',
326
- }}>
327
- <div style={{
328
- flexShrink: 0,
329
- transition: 'transform 0.3s ease',
330
- willChange: 'transform',
331
- }}>
332
- {isExpanded && branding?.logo_url ? (
333
- <img
334
- src={branding.logo_url}
335
- alt={displayName}
336
- style={{ height: '32px', width: 'auto', maxWidth: '160px', objectFit: 'contain', display: 'block' }}
337
- />
338
- ) : branding?.icon_url ? (
339
- <PCAvatar src={branding.icon_url} name={displayName} color="info" size="md" />
340
- ) : (
341
- <PCAvatar name={displayName} color="info" size="md" />
342
- )}
343
- </div>
344
- <div style={headerTextStyle}>
345
- <PCText style={{
346
- whiteSpace: 'nowrap',
347
- fontSize: pcStyles.textBase.fontSize,
348
- fontWeight: pcStyles.fontWeightSemibold.fontWeight,
349
- letterSpacing: '-0.025em',
350
- color: getVar('--pc-text-primary'),
351
- transition: 'opacity 0.3s ease, transform 0.3s ease',
352
- willChange: 'opacity, transform',
353
- }}>
354
- {displayName}
355
- </PCText>
356
- </div>
357
- </div>
358
- <div style={{
359
- ...pcStyles.flex,
360
- ...pcStyles.itemsCenter,
361
- flexShrink: 0,
362
- opacity: (isMobileOpen || !isCollapsed) ? 1 : 0,
363
- width: (isMobileOpen || !isCollapsed) ? 'auto' : 0,
364
- overflow: 'hidden',
365
- transition: 'opacity 0.3s ease, width 0.3s ease',
366
- willChange: 'opacity, width',
367
- pointerEvents: (isMobileOpen || !isCollapsed) ? 'auto' : 'none',
368
- }}>
369
- <PCActionIcon
370
- variant="default"
371
- style={{ color: getVar('--pc-text-primary') }}
372
- className="pc-hidden-md"
373
- onClick={handleMobileClose}
374
- aria-label={t('closeSidebarMenu')}
375
- >
376
- <IoCloseOutline size={20} />
377
- </PCActionIcon>
378
- </div>
379
- </div>
380
-
381
- <div style={{
382
- flex: 1,
383
- minHeight: 0,
384
- display: 'flex',
385
- flexDirection: 'column',
386
- overflow: 'hidden',
387
- position: 'relative',
388
- }}>
389
- <SimpleBar style={{
390
- flex: 1,
391
- minHeight: 0,
392
- height: '100%',
393
- overflowY: 'auto',
394
- overflowX: 'hidden',
395
- }}>
396
- <SideMenuPC
397
- menuItems={menuItems}
398
- isCollapsed={isMobileOpen ? false : isCollapsed}
399
- onItemSelect={handleMenuItemSelect}
400
- />
401
- </SimpleBar>
402
- </div>
403
-
404
- <div style={{
405
- flexShrink: 0,
406
- backgroundColor: getVar('--pc-bg-shell'),
407
- paddingTop: '0.75rem',
408
- paddingBottom: '0.75rem',
409
- paddingLeft: isMobileOpen || !isCollapsed ? '1rem' : '0.5rem',
410
- paddingRight: isMobileOpen || !isCollapsed ? '1rem' : '0.5rem',
411
- minHeight: 'fit-content',
412
- transition: 'padding-left 0.3s ease, padding-right 0.3s ease',
413
- willChange: 'padding-left, padding-right',
414
- }}>
415
- <div style={emailContainerStyle}>
416
- <PCText size="sm" color="secondary" style={{
417
- maxWidth: '100%',
418
- overflow: 'hidden',
419
- textOverflow: 'ellipsis',
420
- whiteSpace: 'nowrap',
421
- fontSize: pcStyles.textXs.fontSize,
422
- color: getVar('--pc-text-secondary'),
423
- lineHeight: 1.5,
424
- }}>
425
- {email}
426
- </PCText>
427
- </div>
428
- {onBack && (
429
- !isMobileOpen && isCollapsed ? (
430
- <div style={{ display: 'flex', justifyContent: 'center' }}>
431
- <PCTooltip content={footerActionLabel} placement="right" color="invert">
432
- <div style={{ width: 'fit-content', margin: '0 auto' }}>
433
- <PCButton
434
- variant="text"
435
- onClick={handleBack}
436
- aria-label={footerActionLabel}
437
- style={{
438
- ...buttonStyle,
439
- width: '2.25rem',
440
- height: '2.25rem',
441
- minHeight: '2.25rem',
442
- padding: 0,
443
- marginTop: email ? '0.5rem' : 0,
444
- justifyContent: 'center',
445
- alignItems: 'center',
446
- fontSize: pcStyles.textSm.fontSize,
447
- }}
448
- >
449
- {footerActionIcon}
450
- </PCButton>
451
- </div>
452
- </PCTooltip>
453
- </div>
454
- ) : (
455
- <PCButton
456
- variant="text"
457
- onClick={handleBack}
458
- aria-label={footerActionLabel}
459
- style={{
460
- ...buttonStyle,
461
- width: '100%',
462
- padding: '0.4375rem 0',
463
- marginTop: email && (isMobileOpen || !isCollapsed) ? '0.5rem' : 0,
464
- justifyContent: (isMobileOpen || !isCollapsed) ? 'flex-start' : 'center',
465
- fontSize: pcStyles.textSm.fontSize,
466
- minHeight: 'auto',
467
- }}
468
- >
469
- <span
470
- style={{
471
- marginRight: (isMobileOpen || !isCollapsed) ? '0.625rem' : 0,
472
- display: 'inline-flex',
473
- alignItems: 'center',
474
- justifyContent: 'center',
475
- }}
476
- >
477
- {footerActionIcon}
478
- </span>
479
- {(isMobileOpen || !isCollapsed) && (
480
- <span style={{ whiteSpace: 'nowrap', lineHeight: 1.5 }}>{footerActionLabel}</span>
481
- )}
482
- </PCButton>
483
- )
484
- )}
485
- </div>
486
- </aside>
487
- </>
488
- );
489
- }