@redacto.io/consent-sdk-react 5.3.0 → 7.0.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/.turbo/turbo-build.log +24 -0
- package/CHANGELOG.md +23 -0
- package/dist/privacy-center.d.mts +100 -42
- package/dist/privacy-center.d.ts +100 -42
- package/dist/privacy-center.js +4232 -2247
- package/dist/privacy-center.mjs +4266 -2284
- package/package.json +1 -1
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -10
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +18 -17
- package/src/RedactoPrivacyCenter/api/actions.ts +52 -8
- package/src/RedactoPrivacyCenter/api/client.ts +58 -15
- package/src/RedactoPrivacyCenter/api/orgBranding.ts +9 -0
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +729 -278
- package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +187 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +65 -165
- package/src/RedactoPrivacyCenter/components/ConsentManagerExpandedRow.tsx +179 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerGroupedColumns.tsx +318 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerProductCards.tsx +333 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerPurposeCards.tsx +102 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +6 -2
- package/src/RedactoPrivacyCenter/components/Form.tsx +3 -3
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +9 -6
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +1 -1
- package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +101 -56
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +16 -2
- package/src/RedactoPrivacyCenter/components/types.ts +2 -5
- package/src/RedactoPrivacyCenter/context/BrandingContext.tsx +44 -0
- package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +21 -1
- package/src/RedactoPrivacyCenter/lib/api.ts +70 -28
- package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +4 -2
- package/src/RedactoPrivacyCenter/lib/types.ts +79 -36
- package/src/RedactoPrivacyCenter/locales/as/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/en/translation.json +16 -1
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +15 -1
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/or/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/te/translation.json +12 -1
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +12 -1
- package/src/RedactoPrivacyCenter/styles/applyBrandingTheme.ts +66 -0
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +528 -114
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +3 -2
- package/src/RedactoPrivacyCenter/styles/variables.css +5 -42
- package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +36 -16
- package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +4 -12
- package/src/RedactoPrivacyCenter/ui/Footer.tsx +4 -10
- package/src/RedactoPrivacyCenter/ui/PCInput.tsx +1 -1
- package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +51 -5
- package/src/privacy-center.ts +7 -3
- package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +0 -85
- package/src/RedactoPrivacyCenter/context/theme.tsx +0 -127
- package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +0 -72
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* CSS Custom Properties for Privacy Center SDK */
|
|
2
2
|
/* These variables are scoped and won't conflict with host app styles */
|
|
3
3
|
|
|
4
|
-
:root
|
|
5
|
-
[data-theme="light"] {
|
|
4
|
+
:root {
|
|
6
5
|
/* Colors */
|
|
7
6
|
--pc-primary: #3b82f6;
|
|
8
7
|
--pc-primary-hover: #2563eb;
|
|
@@ -89,45 +88,9 @@
|
|
|
89
88
|
|
|
90
89
|
/* Icon filters for dark theme */
|
|
91
90
|
--pc-icon-filter: 0;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
[data-theme="dark"] {
|
|
95
|
-
/* Colors */
|
|
96
|
-
--pc-primary: #60a5fa;
|
|
97
|
-
--pc-primary-hover: #3b82f6;
|
|
98
|
-
--pc-primary-active: #2563eb;
|
|
99
|
-
--pc-secondary: #9ca3af;
|
|
100
|
-
--pc-secondary-hover: #6b7280;
|
|
101
|
-
--pc-success: #34d399;
|
|
102
|
-
--pc-error: #f87171;
|
|
103
|
-
--pc-warning: #fbbf24;
|
|
104
|
-
--pc-warning-bg: #78350f;
|
|
105
|
-
--pc-warning-border: #fbbf24;
|
|
106
|
-
--pc-warning-text: #fef3c7;
|
|
107
|
-
--pc-info: #60a5fa;
|
|
108
91
|
|
|
109
|
-
/*
|
|
110
|
-
--pc-
|
|
111
|
-
|
|
112
|
-
--pc-
|
|
113
|
-
--pc-bg-tertiary: #31405a;
|
|
114
|
-
--pc-bg-hover: #243044;
|
|
115
|
-
--pc-bg-active: #31405a;
|
|
116
|
-
--pc-bg-overlay: rgba(0, 0, 0, 0.7);
|
|
117
|
-
|
|
118
|
-
/* Text */
|
|
119
|
-
--pc-text-primary: #f9fafb;
|
|
120
|
-
--pc-text-secondary: #d1d5db;
|
|
121
|
-
--pc-text-tertiary: #9ca3af;
|
|
122
|
-
--pc-text-inverse: #111827;
|
|
123
|
-
--pc-text-disabled: #6b7280;
|
|
124
|
-
|
|
125
|
-
/* Borders */
|
|
126
|
-
--pc-border-color: rgba(148, 163, 184, 0.18);
|
|
127
|
-
--pc-border-color-hover: rgba(148, 163, 184, 0.28);
|
|
128
|
-
--pc-border-color-focus: #60a5fa;
|
|
129
|
-
--pc-sidebar-shadow: 14px 0 36px rgba(2, 6, 23, 0.42), 1px 0 0 rgba(148, 163, 184, 0.08);
|
|
130
|
-
|
|
131
|
-
/* Icon filters for dark theme */
|
|
132
|
-
--pc-icon-filter: 1;
|
|
92
|
+
/* Manage Consent toolbar: search, filters, export */
|
|
93
|
+
--pc-consent-toolbar-control-height: 2rem;
|
|
94
|
+
/* Header: language, notifications, theme (same pixel row as compact controls) */
|
|
95
|
+
--pc-navbar-actions-control-height: var(--pc-consent-toolbar-control-height);
|
|
133
96
|
}
|
|
@@ -67,6 +67,7 @@ export default function AdvancedTable<TData extends Record<string, any>>({
|
|
|
67
67
|
expandedComponent,
|
|
68
68
|
}: AdvancedTableProps<TData>) {
|
|
69
69
|
const { t } = useTranslation();
|
|
70
|
+
const [hoveredRowId, setHoveredRowId] = React.useState<string | null>(null);
|
|
70
71
|
if (!table) return null;
|
|
71
72
|
|
|
72
73
|
return (
|
|
@@ -111,8 +112,7 @@ export default function AdvancedTable<TData extends Record<string, any>>({
|
|
|
111
112
|
const onRowClick = meta.onRowClick;
|
|
112
113
|
const handleRowClick = onRowClick
|
|
113
114
|
? (e: React.MouseEvent) => {
|
|
114
|
-
|
|
115
|
-
onRowClick(row.original);
|
|
115
|
+
onRowClick(row.original, row);
|
|
116
116
|
}
|
|
117
117
|
: undefined;
|
|
118
118
|
|
|
@@ -124,8 +124,23 @@ export default function AdvancedTable<TData extends Record<string, any>>({
|
|
|
124
124
|
<React.Fragment key={row.id}>
|
|
125
125
|
<PCTable.Row
|
|
126
126
|
className={classNames?.rowClassName}
|
|
127
|
-
style={
|
|
127
|
+
style={{
|
|
128
|
+
...(onRowClick ? { cursor: 'pointer' } : {}),
|
|
129
|
+
...(row.getIsExpanded() ? { borderBottom: 'none' } : {}),
|
|
130
|
+
...(hoveredRowId === row.id
|
|
131
|
+
? { backgroundColor: getVar('--pc-bg-hover') }
|
|
132
|
+
: {}),
|
|
133
|
+
}}
|
|
128
134
|
onClick={handleRowClick}
|
|
135
|
+
onMouseEnter={() => setHoveredRowId(row.id)}
|
|
136
|
+
onMouseLeave={(e) => {
|
|
137
|
+
const nextSibling = e.currentTarget.nextElementSibling;
|
|
138
|
+
const relatedTarget = e.relatedTarget as Node | null;
|
|
139
|
+
if (nextSibling && relatedTarget && nextSibling.contains(relatedTarget)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
setHoveredRowId((prev) => (prev === row.id ? null : prev));
|
|
143
|
+
}}
|
|
129
144
|
>
|
|
130
145
|
{row.getVisibleCells().map((cell) => (
|
|
131
146
|
<TableCellBasic
|
|
@@ -141,12 +156,16 @@ export default function AdvancedTable<TData extends Record<string, any>>({
|
|
|
141
156
|
<PCTable.Row
|
|
142
157
|
className={classNames?.expandedRowClassName}
|
|
143
158
|
style={{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
pointerEvents: row.getIsExpanded() ? 'auto' : 'none',
|
|
160
|
+
}}
|
|
161
|
+
onMouseEnter={() => setHoveredRowId(row.id)}
|
|
162
|
+
onMouseLeave={(e) => {
|
|
163
|
+
const previousSibling = e.currentTarget.previousElementSibling;
|
|
164
|
+
const relatedTarget = e.relatedTarget as Node | null;
|
|
165
|
+
if (previousSibling && relatedTarget && previousSibling.contains(relatedTarget)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
setHoveredRowId((prev) => (prev === row.id ? null : prev));
|
|
150
169
|
}}
|
|
151
170
|
>
|
|
152
171
|
<PCTable.Cell
|
|
@@ -154,18 +173,19 @@ export default function AdvancedTable<TData extends Record<string, any>>({
|
|
|
154
173
|
className={classNames?.expandedCellClassName}
|
|
155
174
|
style={{
|
|
156
175
|
padding: row.getIsExpanded() ? undefined : '0',
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
paddingTop: row.getIsExpanded() ? undefined : '0',
|
|
160
|
-
paddingBottom: row.getIsExpanded() ? undefined : '0',
|
|
176
|
+
height: row.getIsExpanded() ? undefined : '0',
|
|
177
|
+
border: 'none',
|
|
161
178
|
}}
|
|
162
179
|
>
|
|
163
180
|
<div
|
|
164
181
|
style={{
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
overflow: 'hidden',
|
|
183
|
+
maxHeight: row.getIsExpanded() ? '1200px' : '0',
|
|
167
184
|
opacity: row.getIsExpanded() ? 1 : 0,
|
|
168
|
-
|
|
185
|
+
transform: row.getIsExpanded() ? 'translateY(0)' : 'translateY(-6px)',
|
|
186
|
+
transition:
|
|
187
|
+
'max-height 260ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease, transform 220ms ease',
|
|
188
|
+
willChange: 'max-height, opacity, transform',
|
|
169
189
|
}}
|
|
170
190
|
>
|
|
171
191
|
{rowExpandedComponent(row)}
|
|
@@ -58,10 +58,10 @@ export default function AdvancedTablePagination<
|
|
|
58
58
|
...pcStyles.flexCol,
|
|
59
59
|
...pcStyles.itemsCenter,
|
|
60
60
|
width: '100%',
|
|
61
|
-
paddingTop:
|
|
62
|
-
paddingBottom:
|
|
63
|
-
gap:
|
|
64
|
-
minHeight: '
|
|
61
|
+
paddingTop: '0.375rem',
|
|
62
|
+
paddingBottom: '0.125rem',
|
|
63
|
+
gap: '0.375rem',
|
|
64
|
+
minHeight: '44px',
|
|
65
65
|
opacity: shouldShowPagination ? 1 : 0.5,
|
|
66
66
|
pointerEvents: shouldShowPagination ? 'auto' : 'none',
|
|
67
67
|
}}
|
|
@@ -148,7 +148,6 @@ export default function AdvancedTablePagination<
|
|
|
148
148
|
disabled={!table.getCanPreviousPage()}
|
|
149
149
|
style={{
|
|
150
150
|
borderRadius: '0.5rem',
|
|
151
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
152
151
|
borderColor: getVar('--pc-border-color'),
|
|
153
152
|
color: getVar('--pc-text-primary'),
|
|
154
153
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -176,7 +175,6 @@ export default function AdvancedTablePagination<
|
|
|
176
175
|
disabled={!table.getCanPreviousPage()}
|
|
177
176
|
style={{
|
|
178
177
|
borderRadius: '0.5rem',
|
|
179
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
180
178
|
borderColor: getVar('--pc-border-color'),
|
|
181
179
|
color: getVar('--pc-text-primary'),
|
|
182
180
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -204,7 +202,6 @@ export default function AdvancedTablePagination<
|
|
|
204
202
|
disabled={!table.getCanNextPage()}
|
|
205
203
|
style={{
|
|
206
204
|
borderRadius: '0.5rem',
|
|
207
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
208
205
|
borderColor: getVar('--pc-border-color'),
|
|
209
206
|
color: getVar('--pc-text-primary'),
|
|
210
207
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -232,7 +229,6 @@ export default function AdvancedTablePagination<
|
|
|
232
229
|
disabled={!table.getCanNextPage()}
|
|
233
230
|
style={{
|
|
234
231
|
borderRadius: '0.5rem',
|
|
235
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
236
232
|
borderColor: getVar('--pc-border-color'),
|
|
237
233
|
color: getVar('--pc-text-primary'),
|
|
238
234
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -364,7 +360,6 @@ export default function AdvancedTablePagination<
|
|
|
364
360
|
disabled={!table.getCanPreviousPage()}
|
|
365
361
|
style={{
|
|
366
362
|
borderRadius: '0.5rem',
|
|
367
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
368
363
|
borderColor: getVar('--pc-border-color'),
|
|
369
364
|
color: getVar('--pc-text-primary'),
|
|
370
365
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -392,7 +387,6 @@ export default function AdvancedTablePagination<
|
|
|
392
387
|
disabled={!table.getCanPreviousPage()}
|
|
393
388
|
style={{
|
|
394
389
|
borderRadius: '0.5rem',
|
|
395
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
396
390
|
borderColor: getVar('--pc-border-color'),
|
|
397
391
|
color: getVar('--pc-text-primary'),
|
|
398
392
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -420,7 +414,6 @@ export default function AdvancedTablePagination<
|
|
|
420
414
|
disabled={!table.getCanNextPage()}
|
|
421
415
|
style={{
|
|
422
416
|
borderRadius: '0.5rem',
|
|
423
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
424
417
|
borderColor: getVar('--pc-border-color'),
|
|
425
418
|
color: getVar('--pc-text-primary'),
|
|
426
419
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -448,7 +441,6 @@ export default function AdvancedTablePagination<
|
|
|
448
441
|
disabled={!table.getCanNextPage()}
|
|
449
442
|
style={{
|
|
450
443
|
borderRadius: '0.5rem',
|
|
451
|
-
boxShadow: getVar('--pc-shadow-sm'),
|
|
452
444
|
borderColor: getVar('--pc-border-color'),
|
|
453
445
|
color: getVar('--pc-text-primary'),
|
|
454
446
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useTranslation } from 'react-i18next';
|
|
2
2
|
import { pcStyles } from '../styles/pcStyles';
|
|
3
3
|
import PoweredByRedacto from '../assets/poweredByRedacto.svg';
|
|
4
|
-
import {
|
|
4
|
+
import { useBranding } from '../context/BrandingContext';
|
|
5
5
|
|
|
6
6
|
const getVar = (varName: string): string => `var(${varName})`;
|
|
7
7
|
|
|
8
8
|
export default function Footer() {
|
|
9
9
|
const { t } = useTranslation();
|
|
10
|
-
const
|
|
10
|
+
const branding = useBranding();
|
|
11
|
+
|
|
12
|
+
if (branding?.hide_platform_branding) return null;
|
|
11
13
|
|
|
12
14
|
return (
|
|
13
15
|
<footer
|
|
@@ -46,15 +48,7 @@ export default function Footer() {
|
|
|
46
48
|
alignItems: 'center',
|
|
47
49
|
justifyContent: 'center',
|
|
48
50
|
minHeight: '1.75rem',
|
|
49
|
-
padding:
|
|
50
|
-
theme === 'dark' ? '0.25rem 0.5rem' : 0,
|
|
51
|
-
backgroundColor:
|
|
52
|
-
theme === 'dark' ? 'rgba(255, 255, 255, 0.92)' : 'transparent',
|
|
53
51
|
borderRadius: '9999px',
|
|
54
|
-
boxShadow:
|
|
55
|
-
theme === 'dark'
|
|
56
|
-
? '0 8px 18px rgba(2, 6, 23, 0.28)'
|
|
57
|
-
: 'none',
|
|
58
52
|
}}
|
|
59
53
|
>
|
|
60
54
|
<img
|
|
@@ -75,7 +75,7 @@ export function PCInput({
|
|
|
75
75
|
return (
|
|
76
76
|
<div className={className} style={wrapperStyle}>
|
|
77
77
|
{label && <label style={pcStyles.inputLabel}>{label}</label>}
|
|
78
|
-
<div style={pcStyles.inputWithPrefix}>
|
|
78
|
+
<div className="pc-input-affix" style={pcStyles.inputWithPrefix}>
|
|
79
79
|
{prefix && <span style={pcStyles.inputPrefix}>{prefix}</span>}
|
|
80
80
|
<input
|
|
81
81
|
ref={inputRef}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useLayoutEffect,
|
|
8
8
|
} from 'react';
|
|
9
9
|
import { pcStyles } from '../styles/pcStyles';
|
|
10
|
-
import { ChevronDown, X } from 'lucide-react';
|
|
10
|
+
import { ChevronDown, X, Check } from 'lucide-react';
|
|
11
11
|
|
|
12
12
|
const getVar = (varName: string): string => `var(${varName})`;
|
|
13
13
|
const useIsomorphicLayoutEffect =
|
|
@@ -32,6 +32,7 @@ export interface PCSelectProps {
|
|
|
32
32
|
disabled?: boolean;
|
|
33
33
|
size?: 'sm' | 'md' | 'lg';
|
|
34
34
|
label?: ReactNode;
|
|
35
|
+
prefix?: ReactNode;
|
|
35
36
|
/**
|
|
36
37
|
* If true, the dropdown list width will match the trigger width.
|
|
37
38
|
* Useful in tight filter rows where you want trigger + dropdown aligned.
|
|
@@ -40,6 +41,7 @@ export interface PCSelectProps {
|
|
|
40
41
|
searchPlaceholder?: string;
|
|
41
42
|
noOptionsMessage?: string;
|
|
42
43
|
clearAriaLabel?: string;
|
|
44
|
+
showSelectedCheck?: boolean;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export function PCSelect({
|
|
@@ -55,10 +57,12 @@ export function PCSelect({
|
|
|
55
57
|
disabled = false,
|
|
56
58
|
size = 'md',
|
|
57
59
|
label,
|
|
60
|
+
prefix,
|
|
58
61
|
dropdownMatchTriggerWidth = false,
|
|
59
62
|
searchPlaceholder = 'Search...',
|
|
60
63
|
noOptionsMessage = 'No options found',
|
|
61
64
|
clearAriaLabel = 'Clear selection',
|
|
65
|
+
showSelectedCheck = false,
|
|
62
66
|
}: PCSelectProps) {
|
|
63
67
|
const [isOpen, setIsOpen] = useState(false);
|
|
64
68
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -112,7 +116,7 @@ export function PCSelect({
|
|
|
112
116
|
const paddingLeft = parseFloat(computed.paddingLeft || '0') || 0;
|
|
113
117
|
const paddingRight = parseFloat(computed.paddingRight || '0') || 0;
|
|
114
118
|
// Chevron + spacing (and optional clear button) lives on the right
|
|
115
|
-
const iconAreaPx = clearable ? 52 : 32;
|
|
119
|
+
const iconAreaPx = clearable ? 52 : showSelectedCheck ? 46 : 32;
|
|
116
120
|
|
|
117
121
|
const maxLabelWidth = options.reduce((max, opt) => {
|
|
118
122
|
const w = measureTextWidth(opt.label ?? '', font);
|
|
@@ -267,6 +271,17 @@ export function PCSelect({
|
|
|
267
271
|
setIsOpen((prev) => !prev);
|
|
268
272
|
}}
|
|
269
273
|
>
|
|
274
|
+
{prefix && (
|
|
275
|
+
<span style={{
|
|
276
|
+
display: 'inline-flex',
|
|
277
|
+
alignItems: 'center',
|
|
278
|
+
flexShrink: 0,
|
|
279
|
+
marginRight: '0.375rem',
|
|
280
|
+
color: pcStyles.textSecondary.color,
|
|
281
|
+
}}>
|
|
282
|
+
{prefix}
|
|
283
|
+
</span>
|
|
284
|
+
)}
|
|
270
285
|
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
271
286
|
{value ? value.label : <span style={{ color: pcStyles.textSecondary.color }}>{placeholder}</span>}
|
|
272
287
|
</span>
|
|
@@ -349,10 +364,14 @@ export function PCSelect({
|
|
|
349
364
|
<div
|
|
350
365
|
key={String(option.value)}
|
|
351
366
|
className="pc-select-option"
|
|
367
|
+
data-selected={value?.value === option.value ? 'true' : 'false'}
|
|
352
368
|
style={{
|
|
353
369
|
...optionStyle,
|
|
354
370
|
backgroundColor: getVar('--pc-bg-primary'),
|
|
355
|
-
color:
|
|
371
|
+
color:
|
|
372
|
+
value?.value === option.value && showSelectedCheck
|
|
373
|
+
? getVar('--pc-primary')
|
|
374
|
+
: getVar('--pc-text-primary'),
|
|
356
375
|
transition: 'background-color 0.2s ease, color 0.2s ease',
|
|
357
376
|
whiteSpace: 'nowrap',
|
|
358
377
|
overflow: 'hidden',
|
|
@@ -360,7 +379,9 @@ export function PCSelect({
|
|
|
360
379
|
maxWidth: '100%',
|
|
361
380
|
...(option.disabled && { opacity: 0.5, cursor: 'not-allowed' }),
|
|
362
381
|
...(value?.value === option.value && {
|
|
363
|
-
backgroundColor:
|
|
382
|
+
backgroundColor: showSelectedCheck
|
|
383
|
+
? 'rgba(59, 130, 246, 0.12)'
|
|
384
|
+
: getVar('--pc-bg-hover'),
|
|
364
385
|
}),
|
|
365
386
|
}}
|
|
366
387
|
onClick={() => handleSelect(option)}
|
|
@@ -375,7 +396,32 @@ export function PCSelect({
|
|
|
375
396
|
: getVar('--pc-bg-primary');
|
|
376
397
|
}}
|
|
377
398
|
>
|
|
378
|
-
|
|
399
|
+
<span
|
|
400
|
+
style={{
|
|
401
|
+
display: 'flex',
|
|
402
|
+
alignItems: 'center',
|
|
403
|
+
justifyContent: 'space-between',
|
|
404
|
+
gap: '0.5rem',
|
|
405
|
+
width: '100%',
|
|
406
|
+
minWidth: 0,
|
|
407
|
+
}}
|
|
408
|
+
>
|
|
409
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
410
|
+
{option.label}
|
|
411
|
+
</span>
|
|
412
|
+
{showSelectedCheck && (
|
|
413
|
+
<span
|
|
414
|
+
style={{
|
|
415
|
+
display: 'inline-flex',
|
|
416
|
+
alignItems: 'center',
|
|
417
|
+
opacity: value?.value === option.value ? 1 : 0,
|
|
418
|
+
flexShrink: 0,
|
|
419
|
+
}}
|
|
420
|
+
>
|
|
421
|
+
<Check size={14} strokeWidth={2.5} />
|
|
422
|
+
</span>
|
|
423
|
+
)}
|
|
424
|
+
</span>
|
|
379
425
|
</div>
|
|
380
426
|
))
|
|
381
427
|
)}
|
package/src/privacy-center.ts
CHANGED
|
@@ -37,11 +37,14 @@ export type {
|
|
|
37
37
|
TokenExchangeResponse,
|
|
38
38
|
FetchOrgParams,
|
|
39
39
|
ConsentDataElement,
|
|
40
|
-
|
|
40
|
+
PurposeItem,
|
|
41
|
+
ConsentGroup,
|
|
42
|
+
DisplayPurposeRow,
|
|
43
|
+
ConsentModalRecord,
|
|
44
|
+
GroupPurposesOut,
|
|
45
|
+
PaginationMeta,
|
|
41
46
|
NominatorInfo,
|
|
42
47
|
ConsentHistoryStatusSummary,
|
|
43
|
-
ProductConsentHistoryGroup,
|
|
44
|
-
NominatedPurposesGroup,
|
|
45
48
|
Pagination,
|
|
46
49
|
UserConsentDetail,
|
|
47
50
|
UserConsentResponse,
|
|
@@ -70,6 +73,7 @@ export {
|
|
|
70
73
|
createCaseRequest,
|
|
71
74
|
fetchProducts,
|
|
72
75
|
fetchUserConsents,
|
|
76
|
+
fetchGroupPurposes,
|
|
73
77
|
manageConsent,
|
|
74
78
|
fetchActivities,
|
|
75
79
|
fetchCaseHistory,
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { IoSunnyOutline, IoMoonOutline } from 'react-icons/io5';
|
|
3
|
-
import { useTheme } from '../context/theme';
|
|
4
|
-
|
|
5
|
-
export default function ThemeTransitionOverlay() {
|
|
6
|
-
const { isTransitioning, targetTheme } = useTheme();
|
|
7
|
-
const overlayRef = useRef<HTMLDivElement>(null);
|
|
8
|
-
const iconRef = useRef<HTMLDivElement>(null);
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
if (isTransitioning && overlayRef.current && iconRef.current) {
|
|
12
|
-
const overlay = overlayRef.current;
|
|
13
|
-
const icon = iconRef.current;
|
|
14
|
-
|
|
15
|
-
// Trigger animation
|
|
16
|
-
overlay.style.display = 'flex';
|
|
17
|
-
// Force reflow to ensure display change is applied
|
|
18
|
-
overlay.offsetHeight;
|
|
19
|
-
overlay.style.opacity = '1';
|
|
20
|
-
icon.style.animation = 'none';
|
|
21
|
-
// Force reflow
|
|
22
|
-
icon.offsetHeight;
|
|
23
|
-
icon.style.animation = '';
|
|
24
|
-
|
|
25
|
-
const timeout = setTimeout(() => {
|
|
26
|
-
if (overlay) {
|
|
27
|
-
overlay.style.opacity = '0';
|
|
28
|
-
setTimeout(() => {
|
|
29
|
-
if (overlay) {
|
|
30
|
-
overlay.style.display = 'none';
|
|
31
|
-
}
|
|
32
|
-
}, 300);
|
|
33
|
-
}
|
|
34
|
-
}, 1000);
|
|
35
|
-
|
|
36
|
-
return () => clearTimeout(timeout);
|
|
37
|
-
}
|
|
38
|
-
}, [isTransitioning, targetTheme]);
|
|
39
|
-
|
|
40
|
-
if (!targetTheme) return null;
|
|
41
|
-
|
|
42
|
-
const Icon = targetTheme === 'light' ? IoSunnyOutline : IoMoonOutline;
|
|
43
|
-
// Sun: golden yellow, Moon: soft blue/white
|
|
44
|
-
const iconColor = targetTheme === 'light' ? '#fbbf24' : '#c7d2fe';
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div
|
|
48
|
-
ref={overlayRef}
|
|
49
|
-
className="pc-theme-transition-overlay"
|
|
50
|
-
style={{
|
|
51
|
-
display: 'none',
|
|
52
|
-
opacity: 0,
|
|
53
|
-
position: 'fixed',
|
|
54
|
-
top: 0,
|
|
55
|
-
left: 0,
|
|
56
|
-
right: 0,
|
|
57
|
-
bottom: 0,
|
|
58
|
-
pointerEvents: 'none',
|
|
59
|
-
zIndex: 9999,
|
|
60
|
-
alignItems: 'center',
|
|
61
|
-
justifyContent: 'center',
|
|
62
|
-
transition: 'opacity 0.3s ease',
|
|
63
|
-
}}
|
|
64
|
-
aria-hidden="true"
|
|
65
|
-
>
|
|
66
|
-
<div
|
|
67
|
-
ref={iconRef}
|
|
68
|
-
className={`pc-theme-transition-icon pc-theme-transition-${targetTheme}`}
|
|
69
|
-
style={{
|
|
70
|
-
position: 'absolute',
|
|
71
|
-
bottom: '-150px',
|
|
72
|
-
left: '50%',
|
|
73
|
-
transform: 'translateX(-50%)',
|
|
74
|
-
fontSize: '120px',
|
|
75
|
-
color: iconColor,
|
|
76
|
-
display: 'flex',
|
|
77
|
-
alignItems: 'center',
|
|
78
|
-
justifyContent: 'center',
|
|
79
|
-
}}
|
|
80
|
-
>
|
|
81
|
-
<Icon size={120} />
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useState,
|
|
5
|
-
useEffect,
|
|
6
|
-
useCallback,
|
|
7
|
-
type ReactNode,
|
|
8
|
-
} from 'react';
|
|
9
|
-
|
|
10
|
-
export type Theme = 'light' | 'dark';
|
|
11
|
-
|
|
12
|
-
type ThemeContextType = {
|
|
13
|
-
theme: Theme;
|
|
14
|
-
toggleTheme: () => void;
|
|
15
|
-
setTheme: (theme: Theme) => void;
|
|
16
|
-
isTransitioning: boolean;
|
|
17
|
-
targetTheme: Theme | null;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const THEME_STORAGE_KEY = 'privacy-center-theme';
|
|
21
|
-
|
|
22
|
-
const ThemeContext = createContext<ThemeContextType | null>(null);
|
|
23
|
-
|
|
24
|
-
function getInitialTheme(defaultTheme?: Theme): Theme {
|
|
25
|
-
// If defaultTheme is provided, always use it (takes precedence over sessionStorage)
|
|
26
|
-
// This ensures that when a parent component explicitly sets a theme, it's respected
|
|
27
|
-
if (defaultTheme !== undefined) {
|
|
28
|
-
return defaultTheme;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Otherwise, check sessionStorage first
|
|
32
|
-
if (typeof window !== 'undefined') {
|
|
33
|
-
const stored = sessionStorage.getItem(THEME_STORAGE_KEY);
|
|
34
|
-
if (stored === 'light' || stored === 'dark') {
|
|
35
|
-
return stored;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Fallback to system preference
|
|
39
|
-
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
40
|
-
return 'dark';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return 'light';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type ThemeProviderProps = {
|
|
48
|
-
children: ReactNode;
|
|
49
|
-
defaultTheme?: Theme;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const ThemeProvider = ({ children, defaultTheme }: ThemeProviderProps) => {
|
|
53
|
-
// Initialize with defaultTheme if provided, otherwise use stored/system preference
|
|
54
|
-
// When defaultTheme is provided, it takes precedence over sessionStorage
|
|
55
|
-
const [theme, setThemeState] = useState<Theme>(() => getInitialTheme(defaultTheme));
|
|
56
|
-
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
57
|
-
const [targetTheme, setTargetTheme] = useState<Theme | null>(null);
|
|
58
|
-
|
|
59
|
-
// Ensure defaultTheme is applied on mount if provided
|
|
60
|
-
// This handles the case where sessionStorage had a different value
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (defaultTheme !== undefined) {
|
|
63
|
-
setThemeState(defaultTheme);
|
|
64
|
-
}
|
|
65
|
-
}, []); // Only run on mount
|
|
66
|
-
|
|
67
|
-
const setTheme = useCallback((newTheme: Theme) => {
|
|
68
|
-
if (newTheme !== theme) {
|
|
69
|
-
setIsTransitioning(true);
|
|
70
|
-
setTargetTheme(newTheme);
|
|
71
|
-
setThemeState(newTheme);
|
|
72
|
-
// Reset transition state after animation completes
|
|
73
|
-
setTimeout(() => {
|
|
74
|
-
setIsTransitioning(false);
|
|
75
|
-
setTargetTheme(null);
|
|
76
|
-
}, 1000);
|
|
77
|
-
}
|
|
78
|
-
}, [theme]);
|
|
79
|
-
|
|
80
|
-
const toggleTheme = useCallback(() => {
|
|
81
|
-
setIsTransitioning(true);
|
|
82
|
-
const newTheme = theme === 'light' ? 'dark' : 'light';
|
|
83
|
-
setTargetTheme(newTheme);
|
|
84
|
-
setThemeState(newTheme);
|
|
85
|
-
// Reset transition state after animation completes
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
setIsTransitioning(false);
|
|
88
|
-
setTargetTheme(null);
|
|
89
|
-
}, 1000);
|
|
90
|
-
}, [theme]);
|
|
91
|
-
|
|
92
|
-
// Apply theme to document and persist to sessionStorage
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
document.documentElement.setAttribute('data-theme', theme);
|
|
95
|
-
sessionStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
96
|
-
}, [theme]);
|
|
97
|
-
|
|
98
|
-
// Listen for system theme changes
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
101
|
-
|
|
102
|
-
const handleChange = (e: MediaQueryListEvent) => {
|
|
103
|
-
// Only auto-switch if user hasn't explicitly set a preference
|
|
104
|
-
const stored = sessionStorage.getItem(THEME_STORAGE_KEY);
|
|
105
|
-
if (!stored) {
|
|
106
|
-
setThemeState(e.matches ? 'dark' : 'light');
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
mediaQuery.addEventListener('change', handleChange);
|
|
111
|
-
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
112
|
-
}, []);
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme, isTransitioning, targetTheme }}>
|
|
116
|
-
{children}
|
|
117
|
-
</ThemeContext.Provider>
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const useTheme = (): ThemeContextType => {
|
|
122
|
-
const context = useContext(ThemeContext);
|
|
123
|
-
if (!context) {
|
|
124
|
-
throw new Error('useTheme must be used within a ThemeProvider');
|
|
125
|
-
}
|
|
126
|
-
return context;
|
|
127
|
-
};
|