@el7ven/cookie-kit 0.2.15
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/README.md +201 -0
- package/dist/styles/index.css +744 -0
- package/package.json +64 -0
- package/src/core/index.js +232 -0
- package/src/core/index.test.js +90 -0
- package/src/core/version.js +29 -0
- package/src/index.js +67 -0
- package/src/js/CookieConsent.js +618 -0
- package/src/js/components/index.js +1 -0
- package/src/js/index.js +53 -0
- package/src/js/index.test.js +42 -0
- package/src/js/version.js +29 -0
- package/src/react/CookieConsent.tsx +152 -0
- package/src/react/CookieDrawer.tsx +233 -0
- package/src/react/components/index.js +2 -0
- package/src/react/index.js +89 -0
- package/src/react/index.test.js +62 -0
- package/src/react/types.ts +47 -0
- package/src/react/version.js +29 -0
- package/src/vue/CookieConsent.vue +180 -0
- package/src/vue/CookieDrawer.vue +203 -0
- package/src/vue/components/index.js +2 -0
- package/src/vue/composables/useCookieConsent.js +182 -0
- package/src/vue/index.js +58 -0
- package/src/vue/index.test.js +40 -0
- package/src/vue/version.js +29 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version metadata for package runtime.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const VERSION = '1.0.0-beta.0'
|
|
6
|
+
export const PACKAGE_NAME = '@el7ven/cookie-consent/js'
|
|
7
|
+
|
|
8
|
+
// JS adapter version metadata.
|
|
9
|
+
export const VERSION_INFO = {
|
|
10
|
+
version: VERSION,
|
|
11
|
+
name: PACKAGE_NAME,
|
|
12
|
+
buildDate: new Date().toISOString(),
|
|
13
|
+
environment: 'production'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Return package version.
|
|
17
|
+
export function getVersion() {
|
|
18
|
+
return VERSION
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Return full version metadata.
|
|
22
|
+
export function getVersionInfo() {
|
|
23
|
+
return VERSION_INFO
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Print runtime version in debug-friendly format.
|
|
27
|
+
export function logVersion() {
|
|
28
|
+
console.log(`🍪 ${PACKAGE_NAME} v${VERSION}`)
|
|
29
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
|
2
|
+
import { useCookieKit } from './index.js'
|
|
3
|
+
import { CookieDrawer } from './CookieDrawer'
|
|
4
|
+
import { CookieConfig } from './types'
|
|
5
|
+
import '@el7ven/cookie-consent/dist/styles'
|
|
6
|
+
|
|
7
|
+
interface CookieConsentProps {
|
|
8
|
+
config?: CookieConfig
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CookieConsent: React.FC<CookieConsentProps> = ({
|
|
12
|
+
config = {
|
|
13
|
+
categories: {
|
|
14
|
+
necessary: { required: true, enabled: true, name: 'Essential Cookies' },
|
|
15
|
+
analytics: { required: false, enabled: true, name: 'Analytics Cookies' },
|
|
16
|
+
marketing: { required: false, enabled: false, name: 'Marketing Cookies' }
|
|
17
|
+
},
|
|
18
|
+
consentExpireDays: 365,
|
|
19
|
+
version: '0.2.0'
|
|
20
|
+
}
|
|
21
|
+
}) => {
|
|
22
|
+
const {
|
|
23
|
+
consent,
|
|
24
|
+
acceptAll,
|
|
25
|
+
rejectAll,
|
|
26
|
+
acceptCategory,
|
|
27
|
+
hasConsent,
|
|
28
|
+
hasCategoryConsent
|
|
29
|
+
} = useCookieKit(config)
|
|
30
|
+
|
|
31
|
+
// Local state
|
|
32
|
+
const [isSettingsMode, setIsSettingsMode] = useState(false)
|
|
33
|
+
const [currentTab, setCurrentTab] = useState('privacy')
|
|
34
|
+
const [categories, setCategories] = useState(config.categories)
|
|
35
|
+
const drawerRef = useRef<HTMLDivElement>(null)
|
|
36
|
+
|
|
37
|
+
const isVisible = !consent?.hasConsented
|
|
38
|
+
const consentVersion = config.version || '0.2.0'
|
|
39
|
+
const isV2 = true
|
|
40
|
+
const themeVarsStyle = Object.fromEntries(
|
|
41
|
+
Object.entries(config.themeVars || {}).map(([key, value]) => [key.startsWith('--') ? key : `--${key}`, value])
|
|
42
|
+
) as React.CSSProperties
|
|
43
|
+
|
|
44
|
+
const enabledSettingsTabs = Object.keys(config.categories || {}).filter(categoryId => {
|
|
45
|
+
return config.categories[categoryId]?.enabled
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const openSettings = useCallback(() => {
|
|
49
|
+
setIsSettingsMode(true)
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
const closeSettings = useCallback(() => {
|
|
53
|
+
if (consent?.hasConsented) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
setIsSettingsMode(false)
|
|
57
|
+
}, [consent])
|
|
58
|
+
|
|
59
|
+
const acceptSelection = useCallback(() => {
|
|
60
|
+
// Save current category states
|
|
61
|
+
Object.keys(categories).forEach(id => {
|
|
62
|
+
const isEnabled = categories[id]?.enabled
|
|
63
|
+
if (isEnabled !== undefined) {
|
|
64
|
+
acceptCategory(id, isEnabled)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}, [categories, acceptCategory])
|
|
68
|
+
|
|
69
|
+
const selectTab = useCallback((tabId: string) => {
|
|
70
|
+
setCurrentTab(tabId)
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
// Event handlers
|
|
74
|
+
const handleAcceptAll = useCallback(() => {
|
|
75
|
+
acceptAll()
|
|
76
|
+
}, [acceptAll])
|
|
77
|
+
|
|
78
|
+
const handleRejectAll = useCallback(() => {
|
|
79
|
+
rejectAll()
|
|
80
|
+
}, [rejectAll])
|
|
81
|
+
|
|
82
|
+
const handleAcceptSelection = useCallback(() => {
|
|
83
|
+
acceptSelection()
|
|
84
|
+
}, [acceptSelection])
|
|
85
|
+
|
|
86
|
+
const handleOpenSettings = useCallback(() => {
|
|
87
|
+
openSettings()
|
|
88
|
+
}, [openSettings])
|
|
89
|
+
|
|
90
|
+
const handleSelectTab = useCallback((tabId: string) => {
|
|
91
|
+
selectTab(tabId)
|
|
92
|
+
}, [selectTab])
|
|
93
|
+
|
|
94
|
+
const handleToggleCategory = useCallback((categoryId: string) => {
|
|
95
|
+
const current = categories[categoryId]
|
|
96
|
+
if (current) {
|
|
97
|
+
setCategories(prev => ({
|
|
98
|
+
...prev,
|
|
99
|
+
[categoryId]: { ...current, enabled: !current.enabled }
|
|
100
|
+
}))
|
|
101
|
+
}
|
|
102
|
+
}, [categories])
|
|
103
|
+
|
|
104
|
+
const handleClose = useCallback(() => {
|
|
105
|
+
if (isSettingsMode) {
|
|
106
|
+
closeSettings()
|
|
107
|
+
} else {
|
|
108
|
+
// Banner will auto-hide when consent is given
|
|
109
|
+
}
|
|
110
|
+
}, [isSettingsMode, closeSettings])
|
|
111
|
+
|
|
112
|
+
// Focus trap logic (simplified)
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (isVisible && drawerRef.current && config.debug) {
|
|
115
|
+
console.log('[CookieConsent] Focus trap activated')
|
|
116
|
+
}
|
|
117
|
+
}, [isVisible, config.debug])
|
|
118
|
+
|
|
119
|
+
// Expose methods for external use
|
|
120
|
+
React.useImperativeHandle(React.createRef(), () => ({
|
|
121
|
+
acceptAll: handleAcceptAll,
|
|
122
|
+
rejectAll: handleRejectAll,
|
|
123
|
+
resetConsent: () => {
|
|
124
|
+
// Reset logic if needed
|
|
125
|
+
}
|
|
126
|
+
}))
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="cookie-consent" data-cookie-kit-theme={config.theme || 'light'} style={themeVarsStyle}>
|
|
130
|
+
<CookieDrawer
|
|
131
|
+
ref={drawerRef}
|
|
132
|
+
isVisible={isVisible}
|
|
133
|
+
isSettingsMode={isSettingsMode}
|
|
134
|
+
currentTab={currentTab}
|
|
135
|
+
categories={categories}
|
|
136
|
+
config={config}
|
|
137
|
+
consentVersion={consentVersion}
|
|
138
|
+
capabilities={{}}
|
|
139
|
+
isV2={isV2}
|
|
140
|
+
onClose={handleClose}
|
|
141
|
+
onOpenSettings={handleOpenSettings}
|
|
142
|
+
onSelectTab={handleSelectTab}
|
|
143
|
+
onToggleCategory={handleToggleCategory}
|
|
144
|
+
onAcceptSelection={handleAcceptSelection}
|
|
145
|
+
onAcceptAll={handleAcceptAll}
|
|
146
|
+
onRejectAll={handleRejectAll}
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default CookieConsent
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import React, { forwardRef, useRef, useEffect } from 'react'
|
|
2
|
+
import { CookieConfig } from './types'
|
|
3
|
+
|
|
4
|
+
interface CookieDrawerProps {
|
|
5
|
+
isVisible: boolean
|
|
6
|
+
isSettingsMode: boolean
|
|
7
|
+
currentTab: string
|
|
8
|
+
categories: Record<string, any>
|
|
9
|
+
config: CookieConfig
|
|
10
|
+
consentVersion: string
|
|
11
|
+
capabilities: Record<string, any>
|
|
12
|
+
isV2: boolean
|
|
13
|
+
onClose: () => void
|
|
14
|
+
onOpenSettings: () => void
|
|
15
|
+
onSelectTab: (tabId: string) => void
|
|
16
|
+
onToggleCategory: (categoryId: string) => void
|
|
17
|
+
onAcceptSelection: () => void
|
|
18
|
+
onAcceptAll: () => void
|
|
19
|
+
onRejectAll: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const CookieDrawer = forwardRef<HTMLDivElement, CookieDrawerProps>(({
|
|
23
|
+
isVisible,
|
|
24
|
+
isSettingsMode,
|
|
25
|
+
currentTab,
|
|
26
|
+
categories,
|
|
27
|
+
config,
|
|
28
|
+
consentVersion,
|
|
29
|
+
capabilities,
|
|
30
|
+
isV2,
|
|
31
|
+
onClose,
|
|
32
|
+
onOpenSettings,
|
|
33
|
+
onSelectTab,
|
|
34
|
+
onToggleCategory,
|
|
35
|
+
onAcceptSelection,
|
|
36
|
+
onAcceptAll,
|
|
37
|
+
onRejectAll
|
|
38
|
+
}, ref) => {
|
|
39
|
+
const drawerRef = useRef<HTMLDivElement>(null)
|
|
40
|
+
|
|
41
|
+
const isEssentialBanner = config.mode === 'essential'
|
|
42
|
+
|
|
43
|
+
const drawerClasses = {
|
|
44
|
+
'cookie-drawer__content-wrapper--settings': isSettingsMode,
|
|
45
|
+
'cookie-drawer__content-wrapper--banner': !isSettingsMode
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootClasses = {
|
|
49
|
+
'cookie-drawer__content-wrapper--bottom': true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const drawerAnimation = isSettingsMode ? 'drawer-slide-up' : 'banner-slide-up'
|
|
53
|
+
|
|
54
|
+
const enabledSettingsTabs = ['privacy', ...Object.keys(categories || {}).filter(id =>
|
|
55
|
+
categories[id]?.enabled
|
|
56
|
+
)]
|
|
57
|
+
|
|
58
|
+
const getCategoryTitle = (categoryId: string) => {
|
|
59
|
+
return categories[categoryId]?.name || categoryId
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const getCategoryDescription = (categoryId: string) => {
|
|
63
|
+
return categories[categoryId]?.description || `Description for ${categoryId} cookies.`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleBackdropClick = () => {
|
|
67
|
+
if (!isSettingsMode) {
|
|
68
|
+
onClose()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Expose dialog element for focus trap
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (ref) {
|
|
75
|
+
if (typeof ref === 'function') {
|
|
76
|
+
ref(drawerRef.current)
|
|
77
|
+
} else {
|
|
78
|
+
ref.current = drawerRef.current
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}, [ref])
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
{/* Backdrop with opacity animation */}
|
|
86
|
+
{isVisible && !isEssentialBanner && (
|
|
87
|
+
<div
|
|
88
|
+
className="cookie-drawer__backdrop"
|
|
89
|
+
onClick={handleBackdropClick}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{/* Drawer with slide animation */}
|
|
94
|
+
{isVisible && (
|
|
95
|
+
<div
|
|
96
|
+
className={`cookie-drawer__content-wrapper ${drawerAnimation} ${Object.entries(drawerClasses).filter(([_, value]) => value).map(([key]) => key).join(' ')} ${Object.entries(rootClasses).filter(([_, value]) => value).map(([key]) => key).join(' ')}`}
|
|
97
|
+
>
|
|
98
|
+
<div ref={drawerRef} className="cookie-drawer__content" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
|
|
99
|
+
{/* Header */}
|
|
100
|
+
<div className="cookie-drawer__header">
|
|
101
|
+
<h3 className="cookie-drawer__title">
|
|
102
|
+
{isSettingsMode
|
|
103
|
+
? (config.texts?.settings?.title || 'Cookie Settings')
|
|
104
|
+
: (config.mode === 'essential'
|
|
105
|
+
? (config.texts?.essential?.title || 'Essential Cookies')
|
|
106
|
+
: (config.texts?.gdpr?.title || 'Cookie Consent'))
|
|
107
|
+
}
|
|
108
|
+
</h3>
|
|
109
|
+
{/* Message */}
|
|
110
|
+
{!isSettingsMode && !isEssentialBanner && (
|
|
111
|
+
<div className="cookie-drawer__message">
|
|
112
|
+
{config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.'}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{isSettingsMode && (
|
|
117
|
+
<button
|
|
118
|
+
onClick={onClose}
|
|
119
|
+
className="cookie-drawer__close"
|
|
120
|
+
aria-label="Close"
|
|
121
|
+
>
|
|
122
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
123
|
+
<path d="M6.40013 18.3079L5.69238 17.6001L11.2924 12.0001L5.69238 6.40013L6.40013 5.69238L12.0001 11.2924L17.6001 5.69238L18.3079 6.40013L12.7079 12.0001L18.3079 17.6001L17.6001 18.3079L12.0001 12.7079L6.40013 18.3079Z" fill="#0D0B3D"/>
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Essential Banner */}
|
|
130
|
+
{!isSettingsMode && isEssentialBanner && (
|
|
131
|
+
<div className="cookie-drawer__banner">
|
|
132
|
+
<div className="cookie-drawer__essential">
|
|
133
|
+
<div className="cookie-drawer__message cookie-drawer__message--essential">
|
|
134
|
+
{config.texts?.essential?.message || 'We use essential cookies to make our site work. By using our site, you accept our use of essential cookies.'}
|
|
135
|
+
</div>
|
|
136
|
+
<div className="cookie-drawer__actions">
|
|
137
|
+
<button onClick={onAcceptAll} className="cookie-drawer__button cookie-drawer__button--primary">
|
|
138
|
+
{config.texts?.essential?.accept || 'Accept'}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{/* GDPR Banner/Settings */}
|
|
146
|
+
{(!isSettingsMode || !isEssentialBanner) && (
|
|
147
|
+
<div className="cookie-drawer__body">
|
|
148
|
+
{/* Banner Mode */}
|
|
149
|
+
{!isSettingsMode && (
|
|
150
|
+
<div className="cookie-drawer__banner">
|
|
151
|
+
<div className="cookie-drawer__message">
|
|
152
|
+
{config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.'}
|
|
153
|
+
</div>
|
|
154
|
+
<div className="cookie-drawer__actions">
|
|
155
|
+
<button onClick={onRejectAll} className="cookie-drawer__button cookie-drawer__button--secondary">
|
|
156
|
+
{config.texts?.gdpr?.reject || 'Reject All'}
|
|
157
|
+
</button>
|
|
158
|
+
<button onClick={onAcceptAll} className="cookie-drawer__button cookie-drawer__button--primary">
|
|
159
|
+
{config.texts?.gdpr?.accept || 'Accept All'}
|
|
160
|
+
</button>
|
|
161
|
+
<button onClick={onOpenSettings} className="cookie-drawer__button cookie-drawer__button--link">
|
|
162
|
+
{config.texts?.gdpr?.settings || 'Customize'}
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{/* Settings Mode */}
|
|
169
|
+
{isSettingsMode && (
|
|
170
|
+
<div className="cookie-drawer__settings">
|
|
171
|
+
{/* Tabs */}
|
|
172
|
+
{enabledSettingsTabs.length > 1 && (
|
|
173
|
+
<div className="cookie-drawer__tabs">
|
|
174
|
+
{enabledSettingsTabs.map(tab => (
|
|
175
|
+
<button
|
|
176
|
+
key={tab}
|
|
177
|
+
className={`cookie-drawer__tab ${currentTab === tab ? 'cookie-drawer__tab--active' : ''}`}
|
|
178
|
+
onClick={() => onSelectTab(tab)}
|
|
179
|
+
>
|
|
180
|
+
{getCategoryTitle(tab)}
|
|
181
|
+
</button>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{/* Tab Content */}
|
|
187
|
+
<div className="cookie-drawer__tab-content">
|
|
188
|
+
{currentTab === 'privacy' ? (
|
|
189
|
+
<div className="cookie-drawer__privacy">
|
|
190
|
+
<h4>{config.texts?.settings?.privacy?.title || 'Privacy Policy'}</h4>
|
|
191
|
+
<p>{config.texts?.settings?.privacy?.description || 'Learn about how we use cookies and protect your privacy.'}</p>
|
|
192
|
+
</div>
|
|
193
|
+
) : (
|
|
194
|
+
<div className="cookie-drawer__category">
|
|
195
|
+
<div className="cookie-drawer__category-header">
|
|
196
|
+
<h4>{getCategoryTitle(currentTab)}</h4>
|
|
197
|
+
<label className="cookie-drawer__toggle">
|
|
198
|
+
<input
|
|
199
|
+
type="checkbox"
|
|
200
|
+
checked={categories[currentTab]?.enabled || false}
|
|
201
|
+
disabled={categories[currentTab]?.required}
|
|
202
|
+
onChange={() => onToggleCategory(currentTab)}
|
|
203
|
+
/>
|
|
204
|
+
<span className="cookie-drawer__toggle-slider"></span>
|
|
205
|
+
</label>
|
|
206
|
+
</div>
|
|
207
|
+
<div className="cookie-drawer__category-description">
|
|
208
|
+
{getCategoryDescription(currentTab)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Settings Actions */}
|
|
215
|
+
<div className="cookie-drawer__settings-actions">
|
|
216
|
+
<button onClick={onAcceptSelection} className="cookie-drawer__button cookie-drawer__button--primary">
|
|
217
|
+
{config.texts?.settings?.save || 'Save Preferences'}
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</>
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
CookieDrawer.displayName = 'CookieDrawer'
|
|
232
|
+
|
|
233
|
+
export default CookieDrawer
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createElement,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { createCookieKit, DEFAULT_CONFIG } from '../core/index.js'
|
|
11
|
+
import { VERSION, getVersion, getVersionInfo, logVersion } from './version.js'
|
|
12
|
+
|
|
13
|
+
const CookieKitContext = createContext(null)
|
|
14
|
+
|
|
15
|
+
export { createCookieKit, DEFAULT_CONFIG, VERSION, getVersion, getVersionInfo, logVersion }
|
|
16
|
+
|
|
17
|
+
export function useCookieKitReact(userConfig = {}) {
|
|
18
|
+
const coreRef = useRef(null)
|
|
19
|
+
if (!coreRef.current) {
|
|
20
|
+
coreRef.current = createCookieKit(userConfig)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const core = coreRef.current
|
|
24
|
+
const [consent, setConsent] = useState(null)
|
|
25
|
+
const [categories, setCategories] = useState(() => core.getDefaultCategoriesState())
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let active = true
|
|
29
|
+
|
|
30
|
+
core.getConsentAsync().then(current => {
|
|
31
|
+
if (!active) return
|
|
32
|
+
setConsent(current)
|
|
33
|
+
if (current?.categories) {
|
|
34
|
+
setCategories({ ...current.categories })
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const offChanged = core.on('consentChanged', ({ consent: nextConsent, categories: nextCategories }) => {
|
|
39
|
+
if (!active) return
|
|
40
|
+
setConsent(nextConsent)
|
|
41
|
+
setCategories({ ...nextCategories })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const offCleared = core.on('consentCleared', () => {
|
|
45
|
+
if (!active) return
|
|
46
|
+
setConsent(null)
|
|
47
|
+
setCategories(core.getDefaultCategoriesState())
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
active = false
|
|
52
|
+
offChanged()
|
|
53
|
+
offCleared()
|
|
54
|
+
}
|
|
55
|
+
}, [core])
|
|
56
|
+
|
|
57
|
+
const hasConsented = useMemo(() => consent?.hasConsented === true, [consent])
|
|
58
|
+
|
|
59
|
+
const acceptAll = async (source = 'banner') => core.acceptAll(source)
|
|
60
|
+
const rejectAll = async (source = 'banner') => core.rejectAll(source)
|
|
61
|
+
const acceptSelected = async (selectedIds, source = 'settings') => core.acceptSelected(selectedIds, source)
|
|
62
|
+
const resetConsent = async () => core.clearConsentAsync()
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
core,
|
|
66
|
+
consent,
|
|
67
|
+
categories,
|
|
68
|
+
hasConsented,
|
|
69
|
+
acceptAll,
|
|
70
|
+
rejectAll,
|
|
71
|
+
acceptSelected,
|
|
72
|
+
resetConsent,
|
|
73
|
+
hasCategoryConsent: category => core.hasCategoryConsent(category),
|
|
74
|
+
hasCategoryConsentAsync: category => core.hasCategoryConsentAsync(category)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function CookieKitProvider({ config = {}, children }) {
|
|
79
|
+
const value = useCookieKitReact(config)
|
|
80
|
+
return createElement(CookieKitContext.Provider, { value }, children)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useCookieKitContext() {
|
|
84
|
+
const value = useContext(CookieKitContext)
|
|
85
|
+
if (!value) {
|
|
86
|
+
throw new Error('useCookieKitContext must be used within CookieKitProvider')
|
|
87
|
+
}
|
|
88
|
+
return value
|
|
89
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { createElement } from 'react'
|
|
4
|
+
import { renderToString } from 'react-dom/server'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createCookieKit,
|
|
8
|
+
DEFAULT_CONFIG,
|
|
9
|
+
useCookieKitReact,
|
|
10
|
+
CookieKitProvider,
|
|
11
|
+
useCookieKitContext
|
|
12
|
+
} from './index.js'
|
|
13
|
+
|
|
14
|
+
test('react package: exports expected public API', () => {
|
|
15
|
+
assert.equal(typeof createCookieKit, 'function')
|
|
16
|
+
assert.equal(typeof useCookieKitReact, 'function')
|
|
17
|
+
assert.equal(typeof CookieKitProvider, 'function')
|
|
18
|
+
assert.equal(typeof useCookieKitContext, 'function')
|
|
19
|
+
assert.equal(typeof DEFAULT_CONFIG, 'object')
|
|
20
|
+
assert.equal(typeof DEFAULT_CONFIG.storageKey, 'string')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('react package: provider/context renders with initial categories via SSR', () => {
|
|
24
|
+
function Probe() {
|
|
25
|
+
const kit = useCookieKitContext()
|
|
26
|
+
return createElement('pre', null, JSON.stringify({
|
|
27
|
+
hasConsented: kit.hasConsented,
|
|
28
|
+
categories: kit.categories
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const html = renderToString(
|
|
33
|
+
createElement(
|
|
34
|
+
CookieKitProvider,
|
|
35
|
+
{
|
|
36
|
+
config: {
|
|
37
|
+
categories: {
|
|
38
|
+
necessary: { required: true, enabled: true },
|
|
39
|
+
analytics: { required: false, enabled: true }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
createElement(Probe)
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert.match(html, /"hasConsented":false/)
|
|
48
|
+
assert.match(html, /"necessary":true/)
|
|
49
|
+
assert.match(html, /"analytics":false/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('react package: useCookieKitContext throws outside provider', () => {
|
|
53
|
+
function BrokenProbe() {
|
|
54
|
+
useCookieKitContext()
|
|
55
|
+
return createElement('div', null, 'ok')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
assert.throws(
|
|
59
|
+
() => renderToString(createElement(BrokenProbe)),
|
|
60
|
+
/useCookieKitContext must be used within CookieKitProvider/
|
|
61
|
+
)
|
|
62
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface CookieConfig {
|
|
2
|
+
theme?: 'light' | 'dark'
|
|
3
|
+
themeVars?: Record<string, string>
|
|
4
|
+
categories: Record<string, {
|
|
5
|
+
required?: boolean
|
|
6
|
+
enabled?: boolean
|
|
7
|
+
name?: string
|
|
8
|
+
description?: string
|
|
9
|
+
}>
|
|
10
|
+
consentExpireDays?: number
|
|
11
|
+
version?: string
|
|
12
|
+
mode?: 'gdpr' | 'essential'
|
|
13
|
+
debug?: boolean
|
|
14
|
+
texts?: {
|
|
15
|
+
gdpr?: {
|
|
16
|
+
title?: string
|
|
17
|
+
message?: string
|
|
18
|
+
accept?: string
|
|
19
|
+
reject?: string
|
|
20
|
+
settings?: string
|
|
21
|
+
}
|
|
22
|
+
essential?: {
|
|
23
|
+
title?: string
|
|
24
|
+
message?: string
|
|
25
|
+
accept?: string
|
|
26
|
+
}
|
|
27
|
+
settings?: {
|
|
28
|
+
title?: string
|
|
29
|
+
save?: string
|
|
30
|
+
privacy?: {
|
|
31
|
+
title?: string
|
|
32
|
+
description?: string
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ConsentState {
|
|
39
|
+
consentId: string
|
|
40
|
+
timestamp: string
|
|
41
|
+
expiresAt: string
|
|
42
|
+
status: string
|
|
43
|
+
version: string
|
|
44
|
+
source: string
|
|
45
|
+
hasConsented: boolean
|
|
46
|
+
categories: Record<string, boolean>
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version metadata for package runtime.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const VERSION = '1.0.0-beta.0'
|
|
6
|
+
export const PACKAGE_NAME = '@el7ven/cookie-consent/react'
|
|
7
|
+
|
|
8
|
+
// React adapter version metadata.
|
|
9
|
+
export const VERSION_INFO = {
|
|
10
|
+
version: VERSION,
|
|
11
|
+
name: PACKAGE_NAME,
|
|
12
|
+
buildDate: new Date().toISOString(),
|
|
13
|
+
environment: 'production'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Return package version.
|
|
17
|
+
export function getVersion() {
|
|
18
|
+
return VERSION
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Return full version metadata.
|
|
22
|
+
export function getVersionInfo() {
|
|
23
|
+
return VERSION_INFO
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Print runtime version in debug-friendly format.
|
|
27
|
+
export function logVersion() {
|
|
28
|
+
console.log(`⚛️ ${PACKAGE_NAME} v${VERSION}`)
|
|
29
|
+
}
|