refactor(consent-sdk): split ConsentManager + framework adapters under 500 LOC

Phase 4: extract config defaults, Google Consent Mode helper, and framework
adapter internals into sibling files so every source file is under the
hard cap. Public API surface preserved; all 135 tests green, tsup build +
tsc typecheck clean.

- core/ConsentManager 525 -> 467 LOC (extract config + google helpers)
- react/index 511 LOC -> 199 LOC barrel + components/hooks/context
- vue/index 511 LOC -> 32 LOC barrel + components/composables/context/plugin
- angular/index 509 LOC -> 45 LOC barrel + interface/service/module/templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-11 22:25:44 +02:00
parent ef8284dff5
commit 4ed39d2616
17 changed files with 1341 additions and 1375 deletions
+190
View File
@@ -0,0 +1,190 @@
/**
* React UI components for the consent SDK.
*
* Phase 4: extracted from index.tsx to keep the main file under 500 LOC.
* Exports ConsentGate, ConsentPlaceholder, and ConsentBanner (all headless).
*/
import type { FC, ReactNode } from 'react';
import type {
ConsentCategories,
ConsentCategory,
ConsentState,
} from '../types';
import { useConsent } from './hooks';
// =============================================================================
// ConsentGate
// =============================================================================
interface ConsentGateProps {
/** Erforderliche Kategorie */
category: ConsentCategory;
/** Inhalt bei Consent */
children: ReactNode;
/** Inhalt ohne Consent */
placeholder?: ReactNode;
/** Fallback waehrend Laden */
fallback?: ReactNode;
}
/**
* ConsentGate - zeigt Inhalt nur bei Consent.
*/
export const ConsentGate: FC<ConsentGateProps> = ({
category,
children,
placeholder = null,
fallback = null,
}) => {
const { hasConsent, isLoading } = useConsent();
if (isLoading) {
return <>{fallback}</>;
}
if (!hasConsent(category)) {
return <>{placeholder}</>;
}
return <>{children}</>;
};
// =============================================================================
// ConsentPlaceholder
// =============================================================================
interface ConsentPlaceholderProps {
category: ConsentCategory;
message?: string;
buttonText?: string;
className?: string;
}
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt.
*/
export const ConsentPlaceholder: FC<ConsentPlaceholderProps> = ({
category,
message,
buttonText,
className = '',
}) => {
const { showSettings } = useConsent();
const categoryNames: Record<ConsentCategory, string> = {
essential: 'Essentielle Cookies',
functional: 'Funktionale Cookies',
analytics: 'Statistik-Cookies',
marketing: 'Marketing-Cookies',
social: 'Social Media-Cookies',
};
const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`;
return (
<div className={`bp-consent-placeholder ${className}`}>
<p>{message || defaultMessage}</p>
<button type="button" onClick={showSettings}>
{buttonText || 'Cookie-Einstellungen oeffnen'}
</button>
</div>
);
};
// =============================================================================
// ConsentBanner (headless)
// =============================================================================
export interface ConsentBannerRenderProps {
isVisible: boolean;
consent: ConsentState | null;
needsConsent: boolean;
onAcceptAll: () => void;
onRejectAll: () => void;
onSaveSelection: (categories: Partial<ConsentCategories>) => void;
onShowSettings: () => void;
onClose: () => void;
}
interface ConsentBannerProps {
render?: (props: ConsentBannerRenderProps) => ReactNode;
className?: string;
}
/**
* ConsentBanner - Headless Banner-Komponente.
* Kann mit eigener UI gerendert werden oder nutzt Default-UI.
*/
export const ConsentBanner: FC<ConsentBannerProps> = ({ render, className }) => {
const {
consent,
isBannerVisible,
needsConsent,
acceptAll,
rejectAll,
saveSelection,
showSettings,
hideBanner,
} = useConsent();
const renderProps: ConsentBannerRenderProps = {
isVisible: isBannerVisible,
consent,
needsConsent,
onAcceptAll: acceptAll,
onRejectAll: rejectAll,
onSaveSelection: saveSelection,
onShowSettings: showSettings,
onClose: hideBanner,
};
if (render) {
return <>{render(renderProps)}</>;
}
if (!isBannerVisible) {
return null;
}
return (
<div
className={`bp-consent-banner ${className || ''}`}
role="dialog"
aria-modal="true"
aria-label="Cookie-Einstellungen"
>
<div className="bp-consent-banner-content">
<h2>Datenschutzeinstellungen</h2>
<p>
Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales
Nutzererlebnis zu bieten.
</p>
<div className="bp-consent-banner-actions">
<button
type="button"
className="bp-consent-btn bp-consent-btn-reject"
onClick={rejectAll}
>
Alle ablehnen
</button>
<button
type="button"
className="bp-consent-btn bp-consent-btn-settings"
onClick={showSettings}
>
Einstellungen
</button>
<button
type="button"
className="bp-consent-btn bp-consent-btn-accept"
onClick={acceptAll}
>
Alle akzeptieren
</button>
</div>
</div>
</div>
);
};