Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ var orejimeConfig = {
// [optional]
// You can provide a custom function to unserialize the cookie contents.
parse: (cookie) => JSON.parse(cookie)
},

// [optional]
contextual: {
// [optional]
// The default level of the consent notice title.
defaultTitleLevel: 6
}
};
```
Expand Down Expand Up @@ -424,6 +431,24 @@ offering a way to consent in place.
</template>
```

#### Customization

The heading level of the notice's title can be configured via an attribute on
the `template`:

```diff
<template
data-purpose="youtube"
data-contextual
+ data-title-level="6"
>
...
</template>
```

When not specified, it will default to `6`, or the level set within
`orejimeConfig.contextual.defaultTitleLevel`.

<details>
<summary>Integration tips</summary>

Expand Down
4 changes: 4 additions & 0 deletions e2e/OrejimePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export class OrejimePage {
return this.page.getByTestId('orejime-contextual-notice');
}

get contextualNoticeTitle() {
return this.page.getByTestId('orejime-contextual-notice-title');
}

get contextualNoticePlaceholder() {
return this.page.getByTestId('orejime-contextual-notice-placeholder');
}
Expand Down
9 changes: 8 additions & 1 deletion e2e/orejime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test.describe('Orejime', () => {
]
},
`
<template data-purpose="contextual" data-contextual>
<template data-purpose="contextual" data-contextual data-title-level="2">
<iframe id="contextual" src=""></iframe>
</template>

Expand Down Expand Up @@ -205,6 +205,13 @@ test.describe('Orejime', () => {
await expect(orejimePage.contextualNotice).toBeAttached();
});

test('should use a custom heading level in the contextual consent notice', async () => {
await expect(orejimePage.contextualNoticeTitle).toHaveJSProperty(
'nodeName',
'H2'
);
});

test('should accept contextual consent from the notice', async () => {
await orejimePage.acceptContextualNotice();
await expect(orejimePage.locator('#contextual')).toBeVisible();
Expand Down
19 changes: 16 additions & 3 deletions src/ui/ContextualConsentsEffect.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {render} from 'preact';
import {ConsentsMap} from '../core/types';
import Manager from '../core/Manager';
import {Config} from './types';
import {Config, TitleLevel} from './types';
import Context from './components/Context';
import ContextualNoticeContainer from './components/ContextualNoticeContainer';
import ConsentsEffect from '../core/ConsentsEffect';
import {clamp} from './utils/numbers';

export default class ContextualConsentsEffect implements ConsentsEffect {
readonly #config: Config;
Expand Down Expand Up @@ -36,15 +37,27 @@ export default class ContextualConsentsEffect implements ConsentsEffect {
}}
>
<ContextualNoticeContainer
purposeId={template.dataset.purpose}
data={{...template.dataset}}
{...this.#parseNoticeData(template.dataset)}
isEnabled={isEnabled}
/>
</Context.Provider>,
this.#getNoticeContainer(template)
);
}

#parseNoticeData(data: DOMStringMap) {
return {
purposeId: data.purpose,
titleLevel: clamp(
parseInt(data.titleLevel, 10)
|| this.#config?.contextual?.defaultTitleLevel
|| 6,
1,
6
) as TitleLevel
};
}

#getNoticeContainer(template: HTMLTemplateElement) {
if (!this.#containers.has(template)) {
const container = document.createElement('div');
Expand Down
8 changes: 4 additions & 4 deletions src/ui/components/ContextualNoticeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import {useState} from 'preact/hooks';
import {purposesOnly} from '../utils/config';
import {useConfig, useManager, useTheme, useTranslations} from '../utils/hooks';
import {template} from '../utils/template';
import {Purpose} from '../types';
import {Purpose, TitleLevel} from '../types';

interface ContextualNoticeContainerProps {
purposeId: Purpose['id'];
data: Record<string, string>;
titleLevel: TitleLevel;
isEnabled: boolean;
}

const ContextualNoticeContainer = ({
purposeId,
data,
titleLevel,
isEnabled
}: ContextualNoticeContainerProps) => {
const config = useConfig();
Expand Down Expand Up @@ -45,7 +45,7 @@ const ContextualNoticeContainer = ({
{isEnabled ? (
<ContextualNotice
purpose={purpose}
data={data}
titleLevel={titleLevel}
privacyPolicyUrl={config.privacyPolicyUrl}
onAccept={() => {
manager.setConsent(purpose.id, true);
Expand Down
15 changes: 5 additions & 10 deletions src/ui/components/types/ContextualNotice.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import {FunctionComponent} from 'preact';
import {Purpose} from '../../types';
import {Purpose, TitleLevel} from '../../types';

export interface ContextualNoticeOptions extends Record<string, string> {
titleLevel?: '1' | '2' | '3' | '4' | '5' | '6';
}

export interface ContextualNoticeProps<Data extends ContextualNoticeOptions> {
export interface ContextualNoticeProps {
purpose: Purpose;
data: Data;
titleLevel: TitleLevel;
privacyPolicyUrl: string;
onAccept: () => void;
}

export type ContextualNoticeComponent<
Data extends ContextualNoticeOptions = ContextualNoticeOptions
> = FunctionComponent<ContextualNoticeProps<Data>>;
export type ContextualNoticeComponent =
FunctionComponent<ContextualNoticeProps>;
13 changes: 3 additions & 10 deletions src/ui/themes/dsfr/ContextualNotice.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import {useTranslations} from '../../utils/hooks';
import type {
ContextualNoticeComponent,
ContextualNoticeOptions
} from '../../components/types/ContextualNotice';
import type {ContextualNoticeComponent} from '../../components/types/ContextualNotice';
import {template} from '../../utils/template';

const ContextualNotice: ContextualNoticeComponent = ({
purpose,
data,
titleLevel,
onAccept
}) => {
const t = useTranslations();
const {titleLevel} = data;
const TitleTag: `h${ContextualNoticeOptions['titleLevel']}` = titleLevel
? `h${titleLevel}`
: 'h4';

const TitleTag: `h${typeof titleLevel}` = `h${titleLevel}`;
const templateProps = {
purpose: purpose.title
};
Expand Down
16 changes: 7 additions & 9 deletions src/ui/themes/standard/ContextualNotice.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import {useTranslations} from '../../utils/hooks';
import type {
ContextualNoticeComponent,
ContextualNoticeOptions
} from '../../components/types/ContextualNotice';
import type {ContextualNoticeComponent} from '../../components/types/ContextualNotice';
import {template} from '../../utils/template';

const ContextualNotice: ContextualNoticeComponent = ({
purpose,
data,
titleLevel,
onAccept,
privacyPolicyUrl
}) => {
const t = useTranslations();
const {titleLevel} = data;
const TitleTag: `h${ContextualNoticeOptions['titleLevel']}` | 'strong' =
titleLevel ? `h${titleLevel}` : 'strong';
const TitleTag: `h${typeof titleLevel}` = `h${titleLevel}`;
const templateProps = {
purpose: purpose.title,
privacyPolicy: (
Expand All @@ -29,7 +24,10 @@ const ContextualNotice: ContextualNoticeComponent = ({
className="orejime-ContextualNotice"
data-testid="orejime-contextual-notice"
>
<TitleTag className="orejime-ContextualNotice-title">
<TitleTag
className="orejime-ContextualNotice-title"
data-testid="orejime-contextual-notice-title"
>
{template(t.contextual.title, templateProps)}
</TitleTag>

Expand Down
7 changes: 7 additions & 0 deletions src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export type ImageAttributes = {

export type ImageDescriptor = string | ImageAttributes;

export type TitleLevel = 1 | 2 | 3 | 4 | 5 | 6;

export interface ContextualConfig {
defaultTitleLevel?: TitleLevel;
}

export interface Config {
theme: Theme;
orejimeElement?: ElementReference;
Expand All @@ -96,5 +102,6 @@ export interface Config {
forceBanner: boolean;
forceModal: boolean;
privacyPolicyUrl: string;
contextual?: ContextualConfig;
translations: Translations;
}
2 changes: 2 additions & 0 deletions src/ui/utils/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
Loading