@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.
@@ -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,2 @@
1
+ export { CookieConsent, default as CookieConsentComponent } from '../CookieConsent'
2
+ export { CookieDrawer, default as CookieDrawerComponent } from '../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, /&quot;hasConsented&quot;:false/)
48
+ assert.match(html, /&quot;necessary&quot;:true/)
49
+ assert.match(html, /&quot;analytics&quot;: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
+ }