From a846be930c6559c22500f10904fb423abfe2fd1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Girault?= Date: Tue, 2 Jun 2026 11:05:50 +0200 Subject: [PATCH] Made privacy policy link attributes configurable --- .../components/ContextualNoticeContainer.tsx | 1 + src/ui/components/Main.tsx | 6 ++++ src/ui/components/PrivacyPolicyLink.tsx | 33 +++++++++++++++++++ src/ui/components/types/Banner.ts | 3 +- src/ui/components/types/ContextualNotice.ts | 3 +- src/ui/components/types/Modal.ts | 7 +++- src/ui/themes/dsfr/Banner.tsx | 11 +++++-- src/ui/themes/dsfr/Modal.tsx | 12 ++++--- src/ui/themes/standard/Banner.tsx | 10 +++--- src/ui/themes/standard/ContextualNotice.tsx | 13 +++++--- src/ui/themes/standard/Modal.tsx | 16 ++++----- src/ui/types.ts | 2 ++ 12 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 src/ui/components/PrivacyPolicyLink.tsx diff --git a/src/ui/components/ContextualNoticeContainer.tsx b/src/ui/components/ContextualNoticeContainer.tsx index b04a231f..1578e7f3 100644 --- a/src/ui/components/ContextualNoticeContainer.tsx +++ b/src/ui/components/ContextualNoticeContainer.tsx @@ -47,6 +47,7 @@ const ContextualNoticeContainer = ({ purpose={purpose} data={data} privacyPolicyUrl={config.privacyPolicyUrl} + privacyPolicyLinkAttributes={config.privacyPolicyLinkAttributes} onAccept={() => { manager.setConsent(purpose.id, true); setIsBeingDisabled(true); diff --git a/src/ui/components/Main.tsx b/src/ui/components/Main.tsx index 92f67a70..bb7bce4b 100644 --- a/src/ui/components/Main.tsx +++ b/src/ui/components/Main.tsx @@ -52,6 +52,9 @@ const Main = ({apiRef}: MainProps) => { needsUpdate={manager.needsUpdate()} purposeTitles={config.purposes.map(({title}) => title)} privacyPolicyUrl={config.privacyPolicyUrl} + privacyPolicyLinkAttributes={ + config.privacyPolicyLinkAttributes + } logo={config.logo} onConfigure={openModal} onAccept={() => { @@ -74,6 +77,9 @@ const Main = ({apiRef}: MainProps) => { isForced={config.forceModal && manager.isDirty()} needsUpdate={manager.needsUpdate()} privacyPolicyUrl={config.privacyPolicyUrl} + privacyPolicyLinkAttributes={ + config.privacyPolicyLinkAttributes + } onClose={closeModal} onSave={commit} > diff --git a/src/ui/components/PrivacyPolicyLink.tsx b/src/ui/components/PrivacyPolicyLink.tsx new file mode 100644 index 00000000..8a74af5a --- /dev/null +++ b/src/ui/components/PrivacyPolicyLink.tsx @@ -0,0 +1,33 @@ +import {AnchorHTMLAttributes} from 'preact'; +import {useTranslations} from '../utils/hooks'; + +interface PrivacyPolicyLinkProps extends AnchorHTMLAttributes { + label: string; + onExit?: () => void; +} + +const PrivacyPolicyLink = ({ + label, + onExit, + ...props +}: PrivacyPolicyLinkProps) => { + const t = useTranslations(); + const isBlank = props?.target === '_blank'; + const title = isBlank ? `${label} (${t.misc.newWindowTitle})` : null; + + return ( + { + if (onExit && !isBlank) { + onExit(); + } + }} + > + {label} + + ); +}; + +export default PrivacyPolicyLink; diff --git a/src/ui/components/types/Banner.ts b/src/ui/components/types/Banner.ts index 831b2ef3..ced91eae 100644 --- a/src/ui/components/types/Banner.ts +++ b/src/ui/components/types/Banner.ts @@ -1,4 +1,4 @@ -import {FunctionComponent} from 'preact'; +import {AnchorHTMLAttributes, FunctionComponent} from 'preact'; import {ImageDescriptor} from '../../types'; export interface BannerProps { @@ -6,6 +6,7 @@ export interface BannerProps { needsUpdate: boolean; purposeTitles: string[]; privacyPolicyUrl: string; + privacyPolicyLinkAttributes?: AnchorHTMLAttributes; logo?: ImageDescriptor; onAccept: () => void; onDecline: () => void; diff --git a/src/ui/components/types/ContextualNotice.ts b/src/ui/components/types/ContextualNotice.ts index 8cdfe0fe..6f06a72a 100644 --- a/src/ui/components/types/ContextualNotice.ts +++ b/src/ui/components/types/ContextualNotice.ts @@ -1,4 +1,4 @@ -import {FunctionComponent} from 'preact'; +import {AnchorHTMLAttributes, FunctionComponent} from 'preact'; import {Purpose} from '../../types'; export interface ContextualNoticeOptions extends Record { @@ -9,6 +9,7 @@ export interface ContextualNoticeProps { purpose: Purpose; data: Data; privacyPolicyUrl: string; + privacyPolicyLinkAttributes?: AnchorHTMLAttributes; onAccept: () => void; } diff --git a/src/ui/components/types/Modal.ts b/src/ui/components/types/Modal.ts index 5f1e72e5..20f83b0d 100644 --- a/src/ui/components/types/Modal.ts +++ b/src/ui/components/types/Modal.ts @@ -1,9 +1,14 @@ -import {ComponentChildren, FunctionComponent} from 'preact'; +import { + AnchorHTMLAttributes, + ComponentChildren, + FunctionComponent +} from 'preact'; export interface ModalProps { isForced: boolean; needsUpdate: boolean; privacyPolicyUrl: string; + privacyPolicyLinkAttributes?: AnchorHTMLAttributes; onClose: () => void; onSave: () => void; children: ComponentChildren; diff --git a/src/ui/themes/dsfr/Banner.tsx b/src/ui/themes/dsfr/Banner.tsx index 3eb22fc4..2e0dc159 100644 --- a/src/ui/themes/dsfr/Banner.tsx +++ b/src/ui/themes/dsfr/Banner.tsx @@ -2,12 +2,14 @@ import {useRef} from 'preact/hooks'; import type {BannerComponent} from '../../components/types/Banner'; import {useNonObscuringElement, useTranslations} from '../../utils/hooks'; import {template} from '../../utils/template'; +import PrivacyPolicyLink from '../../components/PrivacyPolicyLink'; const Banner: BannerComponent = ({ needsUpdate, isHidden, purposeTitles, privacyPolicyUrl, + privacyPolicyLinkAttributes, onAccept, onDecline, onConfigure @@ -28,9 +30,12 @@ const Banner: BannerComponent = ({ {purposeTitles.join(', ')} ), privacyPolicy: ( - - {t.banner.privacyPolicyLabel} - + ) })}

diff --git a/src/ui/themes/dsfr/Modal.tsx b/src/ui/themes/dsfr/Modal.tsx index a323cd24..a64e776e 100644 --- a/src/ui/themes/dsfr/Modal.tsx +++ b/src/ui/themes/dsfr/Modal.tsx @@ -3,11 +3,13 @@ import {template} from '../../utils/template'; import Dialog from '../../components/Dialog'; import PoweredByLink from '../../components/PoweredByLink'; import type {ModalComponent} from '../../components/types/Modal'; +import PrivacyPolicyLink from '../../components/PrivacyPolicyLink'; const Modal: ModalComponent = ({ isForced, needsUpdate, privacyPolicyUrl, + privacyPolicyLinkAttributes, onClose, onSave, children @@ -57,13 +59,13 @@ const Modal: ModalComponent = ({

{template(t.modal.description, { privacyPolicy: ( - - {t.modal.privacyPolicyLabel} - + label={t.modal.privacyPolicyLabel} + onExit={onClose} + /> ) })}

diff --git a/src/ui/themes/standard/Banner.tsx b/src/ui/themes/standard/Banner.tsx index 20979662..79224015 100644 --- a/src/ui/themes/standard/Banner.tsx +++ b/src/ui/themes/standard/Banner.tsx @@ -3,12 +3,14 @@ import {BannerComponent} from '../../components/types/Banner'; import {imageAttributes} from '../../utils/config'; import {useNonObscuringElement, useTranslations} from '../../utils/hooks'; import {template} from '../../utils/template'; +import PrivacyPolicyLink from '../../components/PrivacyPolicyLink'; const Banner: BannerComponent = ({ isHidden, needsUpdate, purposeTitles, privacyPolicyUrl, + privacyPolicyLinkAttributes, logo, onAccept: onSaveRequest, onDecline: onDeclineRequest, @@ -57,13 +59,13 @@ const Banner: BannerComponent = ({ ), privacyPolicy: ( - - {t.banner.privacyPolicyLabel} - + label={t.banner.privacyPolicyLabel} + /> ) })}

diff --git a/src/ui/themes/standard/ContextualNotice.tsx b/src/ui/themes/standard/ContextualNotice.tsx index 0a4ed336..e323be1b 100644 --- a/src/ui/themes/standard/ContextualNotice.tsx +++ b/src/ui/themes/standard/ContextualNotice.tsx @@ -4,12 +4,14 @@ import type { ContextualNoticeOptions } from '../../components/types/ContextualNotice'; import {template} from '../../utils/template'; +import PrivacyPolicyLink from '../../components/PrivacyPolicyLink'; const ContextualNotice: ContextualNoticeComponent = ({ purpose, data, onAccept, - privacyPolicyUrl + privacyPolicyUrl, + privacyPolicyLinkAttributes }) => { const t = useTranslations(); const {titleLevel} = data; @@ -18,9 +20,12 @@ const ContextualNotice: ContextualNoticeComponent = ({ const templateProps = { purpose: purpose.title, privacyPolicy: ( - - {t.contextual.privacyPolicyLabel} - + ) }; diff --git a/src/ui/themes/standard/Modal.tsx b/src/ui/themes/standard/Modal.tsx index 2bdadb31..b9925c7c 100644 --- a/src/ui/themes/standard/Modal.tsx +++ b/src/ui/themes/standard/Modal.tsx @@ -4,17 +4,19 @@ import {template} from '../../utils/template'; import {useTranslations} from '../../utils/hooks'; import {ModalComponent} from '../../components/types/Modal'; import PoweredByLink from '../../components/PoweredByLink'; +import PrivacyPolicyLink from '../../components/PrivacyPolicyLink'; const Modal: ModalComponent = ({ isForced, needsUpdate, privacyPolicyUrl, + privacyPolicyLinkAttributes, onClose, onSave, children }) => { const t = useTranslations(); - + console.log(privacyPolicyLinkAttributes); return ( { - onClose(); - }} href={privacyPolicyUrl} - > - {t.modal.privacyPolicyLabel} - + label={t.modal.privacyPolicyLabel} + onExit={onClose} + /> ) })}

diff --git a/src/ui/types.ts b/src/ui/types.ts index a999c109..de0a7057 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -1,3 +1,4 @@ +import {AnchorHTMLAttributes} from 'preact'; import {CookieOptions, Purpose as CorePurpose} from '../core/types'; import {Theme} from './components/types/Theme'; @@ -96,5 +97,6 @@ export interface Config { forceBanner: boolean; forceModal: boolean; privacyPolicyUrl: string; + privacyPolicyLinkAttributes?: AnchorHTMLAttributes; translations: Translations; }