Skip to content

Commit

Permalink
(hds-2521) Added options.focusTargetSelector
Browse files Browse the repository at this point in the history
The focus is moved to given element when banner closes on user action.
  • Loading branch information
NikoHelle committed Nov 20, 2024
1 parent 461d873 commit cf1631a
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const Actions = () => {
};
const openBanner = async () => {
// eslint-disable-next-line no-console
console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat']));
console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'], '#banner-opener'));
};
return (
<div>
Expand All @@ -71,7 +71,7 @@ const Actions = () => {
<Button data-testid="remove-cookie-button" onClick={removeConsentCookie}>
Remove all consents
</Button>
<Button data-testid="open-banner-button" onClick={openBanner}>
<Button id="banner-opener" data-testid="open-banner-button" onClick={openBanner}>
Open banner with highlighted groups &quot;chat&quot; and &quot;statistics&quot;
</Button>
</div>
Expand Down Expand Up @@ -120,7 +120,8 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {})
return (
<CookieConsentContextProvider
onChange={onChange}
options={{ language, theme }}
// focusing the logo link, because the tab component loses focus on re-render.
options={{ language, focusTargetSelector: '#actionbar > a', theme }}
siteSettings={{ ...siteSettings, remove: false, monitorInterval: 0 }}
>
<Header languages={languages} onDidChangeLanguage={onLangChange} defaultLanguage={language}>
Expand All @@ -131,6 +132,7 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {})
titleHref="https://hel.fi"
logo={<Logo src={logoFi} alt="City of Helsinki" />}
logoAriaLabel="Service logo"
id="actionbar"
>
<Header.LanguageSelector aria-label="aria" languageHeading="other" />
</Header.ActionBar>
Expand All @@ -157,13 +159,13 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {})
<span data-testid="page-tab" />
</Tabs.TabPanel>
<Tabs.TabPanel>
<h1>Banner ( {language} )</h1>
<h1 tabIndex={-1}>Banner ( {language} )</h1>
<p>Banner is shown if required consents are not consented.</p>
<CookieBanner />
<span data-testid="banner-tab" />
</Tabs.TabPanel>
<Tabs.TabPanel>
<h1>Consents ( {language} )</h1>
<h1 tabIndex={-1}>Consents ( {language} )</h1>
<p>Banner is also shown here when needed.</p>
<ConsentOutput />
<CookieBanner />
Expand Down Expand Up @@ -205,9 +207,10 @@ export const Banner = () => {
<CookieConsentContextProvider
onChange={onChange}
siteSettings={{ ...siteSettings, remove: false, monitorInterval: 0 }}
options={{ focusTargetSelector: 'main h1' }}
>
<main>
<h1>Cookie consent banner</h1>
<h1 tabIndex={-1}>Cookie consent banner</h1>
<p>The banner is shown only if necessary.</p>
<CookieBanner />
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const Actions = () => {
};
const openBanner = async () => {
// eslint-disable-next-line no-console
console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat']));
console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'], '#banner-opener'));
};
return (
<>
Expand All @@ -45,7 +45,9 @@ const Actions = () => {
<Button onClick={addChatCookie}>Add chat group</Button>
<Button onClick={addUnallowedCookie}>Add unallowed group</Button>
<Button onClick={removeConsentCookie}>Remove consent cookie</Button>
<Button onClick={openBanner}>Open banner</Button>
<Button id="banner-opener" onClick={openBanner}>
Open banner
</Button>
</div>
</>
);
Expand All @@ -71,20 +73,22 @@ const DummyContent = () => (
);

export const Banner = (options: Options = {}) => {
const focusTargetSelector = 'main h1';
const combinedOptions: Options = { ...options, focusTargetSelector, submitEvent: true };
return (
<main>
<Info />
<h1>Cookie consent banner</h1>
<h1 tabIndex={-1}>Cookie consent banner</h1>
<p>The banner is shown only if necessary.</p>
<Actions />
<DummyContent />
<BannerComponent siteSettings={siteSettingsJsonUrl} options={options} />
<BannerComponent siteSettings={siteSettingsJsonUrl} options={combinedOptions} />
</main>
);
};

export const SettingsPage = (options: Options = {}) => {
const combinedOptions = { ...options, submitEvent: true };
const combinedOptions: Options = { ...options, submitEvent: true };
return (
<main>
<Info />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class CookieConsentCore {
#pageContentSelector;
#submitEvent = false;
#settingsPageSelector;
#focusTargetSelector;
#disableAutoRender;
#monitor;
#cookieHandler;
Expand Down Expand Up @@ -74,6 +75,7 @@ export class CookieConsentCore {
* @param {string} [options.pageContentSelector='body'] - The selector for where to add scroll-margin-bottom.
* @param {boolean} [options.submitEvent=false] - If set to true, do not reload the page, but submit the string as an event after consent.
* @param {string} [options.settingsPageSelector=null] - If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element.
* @param {string} [options.focusTargetSelector=null] - Selector for the element that will receive focus once the banner is closed.
* @param {boolean} [options.disableAutoRender=false] - If true, neither banner or page are rendered automatically
* @param {boolean} [calledFromCreate=false] - Indicates if the constructor was called from the create method.
* @throws {Error} Throws an error if called from outside the create method.
Expand All @@ -89,6 +91,7 @@ export class CookieConsentCore {
pageContentSelector = 'body', // Where to add scroll-margin-bottom
submitEvent = false, // if set, do not reload page, but submit 'hds-cookie-consent-changed' as event after consent
settingsPageSelector = null, // If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element.
focusTargetSelector = null,
disableAutoRender = false,
},
calledFromCreate = false,
Expand All @@ -106,6 +109,7 @@ export class CookieConsentCore {
this.#pageContentSelector = pageContentSelector;
this.#submitEvent = submitEvent;
this.#settingsPageSelector = settingsPageSelector;
this.#focusTargetSelector = focusTargetSelector;
this.#disableAutoRender = disableAutoRender;

CookieConsentCore.addToHdsScope('cookieConsent', this);
Expand Down Expand Up @@ -161,6 +165,7 @@ export class CookieConsentCore {
* @param {string} [options.pageContentSelector='body'] - The selector for where to add scroll-margin-bottom.
* @param {boolean} [options.submitEvent=false] - If set, do not reload the page, but submit 'hds-cookie-consent-changed' event after consent.
* @param {string} [options.settingsPageSelector=null] - If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element.
* @param {string} [options.focusTargetSelector=null] - Selector for the element that will receive focus once the banner is closed.
* @param {boolean} [options.disableAutoRender=false] - If...
* @return {Promise<CookieConsentCore>} A promise that resolves to a new instance of the CookieConsent class.
* @throws {Error} Throws an error if the siteSettingsParam is not a string or an object.
Expand Down Expand Up @@ -246,13 +251,19 @@ export class CookieConsentCore {

/**
* Opens banner when not on cookie settings page.
* * @param {Array} highlightedGroups - Groups to highlight when opened
* * @param {string} focusTargetSelector - Selector for the element that will receive focus once the banner is closed. Overrides the options.focusTargetSelector
*/
openBanner(highlightedGroups = []) {
openBanner(highlightedGroups = [], focusTargetSelector = '') {
if (this.#settingsPageSelector && document.querySelector(this.#settingsPageSelector)) {
// eslint-disable-next-line no-console
console.error(`Cookie consent: The user is already on settings page`);
return;
}

if (focusTargetSelector) {
this.#focusTargetSelector = focusTargetSelector;
}
this.removeBanner();
this.#render(this.#language, this.#siteSettings, true, null, highlightedGroups);
}
Expand Down Expand Up @@ -342,7 +353,7 @@ export class CookieConsentCore {
* Removes the banner and related elements.
* @returns {void}
*/
removeBanner() {
removeBanner(setFocus = false) {
this.killTimeout();
// Remove banner size observer
if (this.#resizeReference.resizeObserver && this.#resizeReference.bannerHeightElement) {
Expand All @@ -360,6 +371,13 @@ export class CookieConsentCore {

// Remove scroll-margin-bottom variable from all elements inside the contentSelector
document.documentElement.style.removeProperty('--hds-cookie-consent-height');

if (setFocus && this.#focusTargetSelector) {
const element = document.querySelector(this.#focusTargetSelector);
if (element) {
element.focus();
}
}
}

// MARK: Private methods
Expand Down Expand Up @@ -417,7 +435,7 @@ export class CookieConsentCore {
} else {
window.dispatchEvent(new CustomEvent(cookieEventType.CHANGE, { detail: { acceptedGroups } }));
if (!this.#settingsPageElement) {
this.removeBanner();
this.removeBanner(true);
// removeBanner() removes the setTimeout that shows notification
// announceSettingsSaved() must be called after the removeBanner()
this.#announceSettingsSaved();
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/cookieConsentCore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Options = {
pageContentSelector?: string | undefined;
submitEvent?: boolean | undefined;
settingsPageSelector?: string | undefined;
focusTargetSelector?: string | undefined;
disableAutoRender?: boolean | undefined;
};

Expand Down

0 comments on commit cf1631a

Please sign in to comment.