Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JumpTo Component (Experiment - Hindi Service) #12123

Open
wants to merge 41 commits into
base: latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ee3edb4
start setup for jumpto and readme
pvaliani Oct 31, 2024
98d451e
Merge branch 'latest' into WSTEAMA-1421-create-jumpto-skeleton-component
pvaliani Oct 31, 2024
8f4fe7a
Add component w/ tracking, props, semantic structure, heading and blo…
pvaliani Nov 1, 2024
6db4b0c
note for service config added
pvaliani Nov 1, 2024
8ab8fe5
add WIP storybook story, code comms for areas to pair, metadata obj f…
pvaliani Nov 1, 2024
ebe87c7
update service config with placeholder, update translations type for …
pvaliani Nov 1, 2024
8bf5813
Merge branch 'latest' into WSTEAMA-1421-create-jumpto-skeleton-component
pvaliani Nov 1, 2024
26b3130
update code comments w/ aarons feedback
pvaliani Nov 1, 2024
d84e1d3
update headings to subheadlines
pvaliani Nov 1, 2024
9d6c41d
change heading to text component with as strong element
pvaliani Nov 1, 2024
e29df45
replace anchor with inline link
pvaliani Nov 1, 2024
1470032
smol updates to jumpTo translation const and code comments WIP
pvaliani Nov 1, 2024
5b0729a
update component to reflect simpler bff structure, remove tabindex fo…
pvaliani Nov 1, 2024
9ae8f54
Merge branch 'latest' into WSTEAMA-1421-create-jumpto-skeleton-component
pvaliani Nov 1, 2024
8a1156e
update code comments
pvaliani Nov 1, 2024
ffc9c49
update event tracking to use componentName for identifier label in Piano
pvaliani Nov 2, 2024
e261b04
updates to jumptodata and mapping the heading title in the component …
pvaliani Nov 2, 2024
e02c38f
utilise idSanitiser to clean up id and add the -
pvaliani Nov 2, 2024
efee216
add sanitisedId usage and update storybook story
pvaliani Nov 2, 2024
97d7ff8
simplify unordered list and update story to reflect change in props
pvaliani Nov 3, 2024
5a0719d
consistent prop naming in story
pvaliani Nov 4, 2024
31ad36b
Merge branch 'WSTEAMA-1421-create-jumpto-skeleton-component' of https…
holchris Nov 4, 2024
ac5f25e
change ul to ol
holchris Nov 4, 2024
36227b8
rename prop name and code clean up
holchris Nov 4, 2024
5acd4b4
Merge branch 'latest' into WSTEAMA-1421-create-jumpto-skeleton-component
pvaliani Nov 4, 2024
012603c
export JumpToProps
holchris Nov 4, 2024
f06586e
Merge branch 'WSTEAMA-1421-create-jumpto-skeleton-component' of https…
holchris Nov 4, 2024
1c5cf64
updates readme
holchris Nov 4, 2024
4a53290
updates elements for screen reader ux
holchris Nov 4, 2024
3d5e235
Merge branch 'latest' into WSTEAMA-1421-create-jumpto-skeleton-component
pvaliani Nov 4, 2024
5995ca1
fix shadowing issue and update story to remove unused argument
pvaliani Nov 4, 2024
df5883b
Merge branch 'WSTEAMA-1421-create-jumpto-skeleton-component' of https…
holchris Nov 5, 2024
b2ee855
checks for hostname
holchris Nov 5, 2024
2090f49
Adds `decodeURIComponent` to handle non-latin hash characters (#12133)
amoore108 Nov 5, 2024
2ff60ab
Merge branch 'WSTEAMA-1421-create-jumpto-skeleton-component' of https…
holchris Nov 5, 2024
4f869e1
remove tabindex
holchris Nov 5, 2024
8aaba38
Update index.test.jsx
amoore108 Nov 5, 2024
3f75196
update fixture data
holchris Nov 5, 2024
727b58d
rename variable to titleId and add role to orderedList
holchris Nov 5, 2024
724b38a
Merge branch 'WSTEAMA-1421-create-jumpto-skeleton-component' of https…
holchris Nov 5, 2024
750dd7f
WSTEAMA-1428 - Update README docs for JumpTo component (#12137)
louisearchibald Nov 6, 2024
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
3 changes: 2 additions & 1 deletion src/app/components/InlineLink/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const InlineLink: FC<Props> = ({
}: Props) => {
const { externalLinkText } = useContext(ServiceContext);
const { hostname } = new Url(to);
const isExternalLink = !bbcDomains.some(bbcDomain => hostname === bbcDomain);
const isExternalLink =
hostname && !bbcDomains.some(bbcDomain => hostname === bbcDomain);
const linkProps = {
...(isExternalLink &&
typeof text === 'string' && {
Expand Down
12 changes: 12 additions & 0 deletions src/app/components/JumpTo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Description

The JumpTo component serves as an in-page navigation menu, similar to a table of contents, allowing users to quickly jump to specific headings within an article. This component renders text as a `strong` element, a list of `anchor` links, and groups them within a `navigation` landmark. When a link is actioned, the page scrolls down to the relevant heading, identified by matching anchor `ids`.

This component is typically used in articles with multiple headings, enhancing content findability by providing quick navigation options.

## Props

| Name | type | Description |
| ----------------- | --------------------- | ----------------------------------------------- |
| jumpToData | object | Contains article headings with titles and IDs |
| eventTrackingData | eventTrackingMetadata | Contains click and view tracking data for Piano |
38 changes: 38 additions & 0 deletions src/app/components/JumpTo/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import JumpTo, { JumpToProps } from '.';
import metadata from './metadata.json';
import readme from './README.md';

const Component = ({ jumpToHeadings = [] }: JumpToProps) => {
return <JumpTo jumpToHeadings={jumpToHeadings} />;
};

export default {
title: 'Components/JumpTo',
Component,
parameters: {
docs: { readme },
metadata,
},
};

// wip sample story - should use a fixture here instead probably for the titles
export const Example = () => {
const jumpToHeadings = [
{
heading: 'This is a subheading - a',
},
{
heading: 'This is a subheading - b',
},
{
heading: 'This is a subheading - c',
},
{
heading: 'This is a subheading - d',
},
];

return <Component jumpToHeadings={jumpToHeadings} />;
};
58 changes: 58 additions & 0 deletions src/app/components/JumpTo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @jsx jsx */
import { useContext } from 'react';
import { jsx } from '@emotion/react';
import { ServiceContext } from '#contexts/ServiceContext';
import useViewTracker from '#app/hooks/useViewTracker';
import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
import { EventTrackingMetadata } from '#app/models/types/eventTracking';
import Text from '#app/components/Text';
import InlineLink from '#app/components/InlineLink';
import idSanitiser from '../../lib/utilities/idSanitiser';

export interface JumpToProps {
jumpToHeadings?: Array<{ heading: string }>;
eventTrackingData?: EventTrackingMetadata;
}

const JumpTo = ({ jumpToHeadings, eventTrackingData }: JumpToProps) => {
const { translations } = useContext(ServiceContext);
const { jumpTo = 'Jump to' } = translations?.articlePage || {};

const viewRef = useViewTracker(eventTrackingData);
const clickTrackerHandler = useClickTrackerHandler({
...eventTrackingData,
componentName: 'jumpto',
});

const titleId = 'jump-to-heading';

return (
<nav
amoore108 marked this conversation as resolved.
Show resolved Hide resolved
ref={viewRef}
role="navigation"
aria-labelledby={titleId}
data-testid="jump-to"
>
<Text as="strong" id={titleId}>
{jumpTo}
</Text>
<ol role="list">
{jumpToHeadings?.map(({ heading }) => {
const sanitisedId = idSanitiser(heading);
return (
<li key={sanitisedId}>
<InlineLink
to={`#${sanitisedId}`}
onClick={clickTrackerHandler}
data-testid={`jump-to-link-${sanitisedId}`}
text={heading}
/>
</li>
);
})}
</ol>
</nav>
);
};

export default JumpTo;
29 changes: 29 additions & 0 deletions src/app/components/JumpTo/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"alpha": false,
"lastUpdated": {
"day": 1,
"month": "November",
"year": 2024
},
"uxAccessibilityDoc": {
"done": true,
"reference": {
"url": "https://www.figma.com/design/axYpw2KkNQAMjzdOYa677D/WS-OJ-experiment-handover?node-id=3506-3763&node-type=frame&t=djsGA5Y4x4ey3ar4-0",
"label": "Screen Reader UX"
}
},
"acceptanceCriteria": {
"done": true,
"reference": {
"url": "https://paper.dropbox.com/doc/Jump-to-menu-Accessibility-acceptance-criteria--CZuZGgB0eyva~6M50q_UCCQUAg-b5wF4xr9YMgITE0Ui9sWc",
"label": "Accessibility Acceptance Criteria"
}
},
"swarm": {
"done": false,
"reference": {
"url": "",
"label": "Accessibility Swarm Notes"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const useHashChangeHandler = hash => {
const getHashProp = path(['location', 'hash']);

const withHashChangeHandler = Component => props => {
const hash = getHashProp(props);
const hash = decodeURIComponent(getHashProp(props));

useHashChangeHandler(hash);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,74 @@ import withHashChangeHandler from '.';
const Fixture = withHashChangeHandler(() => (
<>
<a href="#section-1">Go to section 1</a>
<section id="section-1">Section 1</section>
<h2 id="section-1">Section 1</h2>
</>
));

const CyrillicFixture = withHashChangeHandler(() => (
<>
<a href="#Мирнија-сам-неко-брине-о-њој-док-је-у-школи">Go to section 1</a>
<h2 id="Мирнија-сам-неко-брине-о-њој-док-је-у-школи">Section 1</h2>
</>
));

const HindiFixture = withHashChangeHandler(() => (
<>
<a href="#आंखों-के-सामने-छा-गया-था-अंधेरा">Go to section 1</a>
<h2 id="आंखों-के-सामने-छा-गया-था-अंधेरा">Section 1</h2>
</>
));

window.HTMLElement.prototype.scrollIntoView = jest.fn();
window.HTMLElement.prototype.focus = jest.fn();

it('should scroll into view and focus on element when hash location changes', async () => {
const { rerender } = render(<Fixture location={{ hash: '' }} />);
describe('withHashChangeHandler', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should scroll into view and focus on element when hash location changes', async () => {
const { rerender } = render(<Fixture location={{ hash: '' }} />);

rerender(<Fixture location={{ hash: '#section-1' }} />);

expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(
1,
);
expect(window.HTMLElement.prototype.focus).toHaveBeenCalledTimes(1);
});

it('should scroll into view and focus on element when hash location contains Cyrillic characters', async () => {
render(
<CyrillicFixture
location={{
hash:
// This is the encoded version of Мирнија-сам-неко-брине-о-њој-док-је-у-школи
'#%D0%9C%D0%B8%D1%80%D0%BD%D0%B8%D1%98%D0%B0-%D1%81%D0%B0%D0%BC-%D0%BD%D0%B5%D0%BA%D0%BE-%D0%B1%D1%80%D0%B8%D0%BD%D0%B5-%D0%BE-%D1%9A%D0%BE%D1%98-%D0%B4%D0%BE%D0%BA-%D1%98%D0%B5-%D1%83-%D1%88%D0%BA%D0%BE%D0%BB%D0%B8',
}}
/>,
);

expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(
1,
);
expect(window.HTMLElement.prototype.focus).toHaveBeenCalledTimes(1);
});

rerender(<Fixture location={{ hash: '#section-1' }} />);
it('should scroll into view and focus on element when hash location contains Hindi characters', async () => {
render(
<HindiFixture
location={{
hash:
// This is the encoded version of आंखों-के-सामने-छा-गया-था-अंधेरा
'#%E0%A4%86%E0%A4%82%E0%A4%96%E0%A5%8B%E0%A4%82-%E0%A4%95%E0%A5%87-%E0%A4%B8%E0%A4%BE%E0%A4%AE%E0%A4%A8%E0%A5%87-%E0%A4%9B%E0%A4%BE-%E0%A4%97%E0%A4%AF%E0%A4%BE-%E0%A4%A5%E0%A4%BE-%E0%A4%85%E0%A4%82%E0%A4%A7%E0%A5%87%E0%A4%B0%E0%A4%BE',
}}
/>,
);

expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(1);
expect(window.HTMLElement.prototype.focus).toHaveBeenCalledTimes(1);
expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(
1,
);
expect(window.HTMLElement.prototype.focus).toHaveBeenCalledTimes(1);
});
});
3 changes: 3 additions & 0 deletions src/app/lib/config/services/hindi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export const service: DefaultServiceConfig = {
audioPlayer: 'ऑडिया प्लेयर',
videoPlayer: 'वीडियो प्लेयर',
},
articlePage: {
jumpTo: 'Jump to', // replace with Hindi translation later
},
liveExperiencePage: {
liveLabel: 'लाइव',
liveCoverage: 'लाइव कवरेज',
Expand Down
3 changes: 3 additions & 0 deletions src/app/models/types/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export interface Translations {
audioPlayer: string;
videoPlayer: string;
};
articlePage?: {
jumpTo: string;
};
liveExperiencePage: {
liveLabel: string;
liveCoverage: string;
Expand Down
Loading