SiteHeader

This commit is contained in:
hobbes7878 2025-04-18 14:38:37 +01:00
parent e0b9da1b29
commit 6fd7b88696
Failed to extract signature
11 changed files with 112 additions and 96 deletions

View file

@ -4,13 +4,15 @@
import { normalizeUrl } from '../NavBar/utils/index.js'; import { normalizeUrl } from '../NavBar/utils/index.js';
interface Props { interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: any; data?: any;
isMobileMenuOpen?: boolean; isMobileMenuOpen?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
releaseMobileMenu?: any; releaseMobileMenu?: any;
} }
let { let {
data = [], data = {},
isMobileMenuOpen = false, isMobileMenuOpen = false,
releaseMobileMenu = () => {}, releaseMobileMenu = () => {},
}: Props = $props(); }: Props = $props();

View file

@ -13,12 +13,19 @@
<span>{story.title}</span> <span>{story.title}</span>
<time datetime={story.display_time}>{getTime(story.display_time)}</time> <time datetime={story.display_time}>{getTime(story.display_time)}</time>
</div> </div>
{#if thumbnail} {#if thumbnail && (thumbnail.resizer_url || thumbnail?.renditions?.square?.['120w'])}
<div class="thumbnail"> <div class="thumbnail">
<img {#if thumbnail.resizer_url}
src={thumbnail.renditions.square['120w']} <img
alt={thumbnail.alt_text} src="{thumbnail.resizer_url}&width=120&quality=80"
/> alt={thumbnail.alt_text}
/>
{:else}
<img
src={thumbnail.renditions.square['120w']}
alt={thumbnail.alt_text}
/>
{/if}
</div> </div>
{/if} {/if}
</a> </a>
@ -39,9 +46,6 @@
text-decoration: none; text-decoration: none;
.story-text span { .story-text span {
text-decoration: underline; text-decoration: underline;
&.label {
text-decoration: none;
}
} }
} }
@ -62,14 +66,6 @@
} }
} }
span.label {
font-size: 14px;
line-height: 1.1;
margin-bottom: 8px;
display: block;
font-weight: 200;
}
time { time {
@include font-sans; @include font-sans;
margin-top: 8px; margin-top: 8px;

View file

@ -31,7 +31,7 @@ dayjs.updateLocale('en', {
}, },
}); });
const getTimeZone = (local) => { const getTimeZone = (local: boolean) => {
if (local) { if (local) {
return dayjs.tz.guess(); return dayjs.tz.guess();
} }
@ -39,15 +39,19 @@ const getTimeZone = (local) => {
return 'UTC'; return 'UTC';
}; };
const diff = (dateFrom, dateTo, measurement = 'day') => { const diff = (
dateFrom: Date,
dateTo: number,
measurement: 'day' | 'hour' = 'day'
) => {
return dayjs(dateFrom).diff(dayjs(dateTo), measurement, true); return dayjs(dateFrom).diff(dayjs(dateTo), measurement, true);
}; };
const olderThanHour = (dateFrom, dateTo, hours = 1) => { const olderThanHour = (dateFrom: Date, dateTo: number, hours = 1) => {
return diff(dateFrom, dateTo, 'hour') < -hours; return diff(dateFrom, dateTo, 'hour') < -hours;
}; };
const isSameDay = (dateFrom, dateTo) => { const isSameDay = (dateFrom: Date, dateTo: number) => {
const first = new Date(dateFrom); const first = new Date(dateFrom);
const second = new Date(dateTo); const second = new Date(dateTo);
return ( return (
@ -57,10 +61,10 @@ const isSameDay = (dateFrom, dateTo) => {
); );
}; };
export const getTime = (datetime) => { export const getTime = (datetime: dayjs.ConfigType) => {
const publishTime = dayjs(datetime, { utc: true }); const publishTime = dayjs(datetime, { utc: true });
const showRelativeTime = !olderThanHour(publishTime, Date.now()); const showRelativeTime = !olderThanHour(publishTime.toDate(), Date.now());
const showTime = isSameDay(publishTime, Date.now()); const showTime = isSameDay(publishTime.toDate(), Date.now());
const timezone = getTimeZone(false); const timezone = getTimeZone(false);
if (showRelativeTime) { if (showRelativeTime) {
return dayjs().to(publishTime); return dayjs().to(publishTime);

View file

@ -1,20 +1,34 @@
<!-- @migration-task Error while migrating Svelte code: Can't migrate code with afterUpdate. Please migrate by hand. --> <script lang="ts">
<script>
import { afterUpdate } from 'svelte';
import StoryCard from './StoryCard/index.svelte'; import StoryCard from './StoryCard/index.svelte';
import Spinner from './Spinner/index.svelte'; import Spinner from './Spinner/index.svelte';
import { getContext } from 'svelte'; import { getContext, type Snippet } from 'svelte';
import type { Writable } from 'svelte/store';
const activeSection = getContext('nav-active-section'); interface Props {
headingText?: string;
children: Snippet;
}
export let headingText = 'Trending Stories'; let { headingText = 'Trending Stories', children }: Props = $props();
let stories = []; const activeSection =
let lastFetched = null; getContext<Writable<null | string>>('nav-active-section');
afterUpdate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
if (lastFetched === $activeSection) return; let stories = $state<any[]>([]);
if ($activeSection === 'more') { let lastFetched = $state<null | string>(null);
$effect(() => {
try {
fetchSection($activeSection);
} catch {
console.log('Error fetching articles');
}
});
const fetchSection = async (activeSection: null | string) => {
if (lastFetched === activeSection) return;
if (activeSection === 'more') {
await fetch( await fetch(
'https://www.reuters.com/pf/api/v3/content/fetch/articles-by-trends-v1?' + 'https://www.reuters.com/pf/api/v3/content/fetch/articles-by-trends-v1?' +
new URLSearchParams({ new URLSearchParams({
@ -27,14 +41,17 @@
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
stories = data.result.articles; stories = data.result.articles;
lastFetched = $activeSection; lastFetched = activeSection;
})
.catch(() => {
console.log('Error fetching articles');
}); });
} else { } else {
await fetch( await fetch(
'https://www.reuters.com/pf/api/v3/content/fetch/recent-stories-by-sections-v1?' + 'https://www.reuters.com/pf/api/v3/content/fetch/recent-stories-by-sections-v1?' +
new URLSearchParams({ new URLSearchParams({
query: JSON.stringify({ query: JSON.stringify({
section_ids: $activeSection, section_ids: activeSection,
size: 4, size: 4,
website: 'reuters', website: 'reuters',
}), }),
@ -43,10 +60,13 @@
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
stories = data.result.articles; stories = data.result.articles;
lastFetched = $activeSection; lastFetched = activeSection;
})
.catch(() => {
console.log('Error fetching articles');
}); });
} }
}); };
</script> </script>
<div class="dropdown"> <div class="dropdown">
@ -54,7 +74,7 @@
<div class="inner"> <div class="inner">
<div class="submenu"> <div class="submenu">
<div class="inner"> <div class="inner">
<slot /> {@render children?.()}
</div> </div>
</div> </div>
<div class="stories-container"> <div class="stories-container">

View file

@ -4,10 +4,12 @@
import MoreDropdown from './NavDropdown/MoreDropdown.svelte'; import MoreDropdown from './NavDropdown/MoreDropdown.svelte';
import { normalizeUrl } from './utils/index'; import { normalizeUrl } from './utils/index';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
let { sections = [] } = $props(); let { sections = [] } = $props();
const activeSection = getContext('nav-active-section'); const activeSection =
getContext<Writable<null | string>>('nav-active-section');
let windowWidth = $state(1200); let windowWidth = $state(1200);
@ -16,7 +18,7 @@
return 5; return 5;
}); });
let navTimeout = $state(); let navTimeout = $state<ReturnType<typeof setTimeout>>();
const timeout = 250; const timeout = 250;
let displayCount = $derived(getDisplayCount()); let displayCount = $derived(getDisplayCount());

View file

@ -1,2 +1,2 @@
export const normalizeUrl = (url) => export const normalizeUrl = (url: string) =>
/^http/.test(url) ? url : `https://www.reuters.com${url}`; /^http/.test(url) ? url : `https://www.reuters.com${url}`;

View file

@ -0,0 +1,33 @@
import { Meta } from '@storybook/blocks';
import * as SiteHeaderStories from './SiteHeader.stories.svelte';
<Meta of={SiteHeaderStories} />
# SiteHeader
Reuters dotcom site header, ported from [Raptor UI components](https://github.com/tr/rcom-arc_raptor-ui/tree/develop/packages/rcom-raptor-ui_common/src/components/site-header).
> **Note:** In the Graphics Kit, you can find this component in `pages/+page.svelte`. Customise it there for the default page.
```svelte
<script>
import { SiteHeader } from '@reuters-graphics/graphics-components';
</script>
<SiteHeader />
```
## Dark theme
Colours are customised by the [`Theme`](?path=/docs/theming-theme--default) component. ([Demo](?path=/story/components-page-furniture-siteheader--customised-theme))
```svelte
<script>
import { SiteHeader, Theme } from '@reuters-graphics/graphics-components';
</script>
<Theme base="dark">
<SiteHeader />
</Theme>
```

View file

@ -1,43 +1,24 @@
<script module lang="ts"> <script module lang="ts">
// @ts-ignore raw import { defineMeta } from '@storybook/addon-svelte-csf';
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore raw
import darkThemeDocs from './stories/docs/darkTheme.md?raw';
import SiteHeader from './SiteHeader.svelte'; import SiteHeader from './SiteHeader.svelte';
import Theme from '../Theme/Theme.svelte';
import { const { Story } = defineMeta({
withComponentDocs,
withStoryDocs,
} from '$lib/docs/utils/withParams.js';
export const meta = {
title: 'Components/Page furniture/SiteHeader', title: 'Components/Page furniture/SiteHeader',
component: SiteHeader, component: SiteHeader,
...withComponentDocs(componentDocs), argTypes: {
argsTypes: {
themes: { control: { disable: true } }, themes: { control: { disable: true } },
}, },
}; });
</script> </script>
<script> <Story name="Demo">
import { Template, Story } from '@storybook/addon-svelte-csf'; <div>
<SiteHeader />
</div>
</Story>
import Theme from '../Theme/Theme.svelte'; <Story name="Customised theme">
</script>
<Template>
{#snippet children({ args })}
<div>
<SiteHeader {...args} />
</div>
{/snippet}
</Template>
<Story name="Default" />
<Story name="Customised theme" {...withStoryDocs(darkThemeDocs)}>
<div> <div>
<Theme base="dark"> <Theme base="dark">
<SiteHeader /> <SiteHeader />

View file

@ -8,7 +8,7 @@
import MenuIcon from './svgs/Menu.svelte'; import MenuIcon from './svgs/Menu.svelte';
import MobileMenu from './MobileMenu/index.svelte'; import MobileMenu from './MobileMenu/index.svelte';
setContext('nav-active-section', writable(null)); setContext('nav-active-section', writable<null | string>(null));
let data = $state(starterData); let data = $state(starterData);

View file

@ -1,11 +0,0 @@
Reuters dotcom site header, ported from [Raptor UI components](https://github.com/tr/rcom-arc_raptor-ui/tree/develop/packages/rcom-raptor-ui_common/src/components/site-header).
> **Note:** In the Graphics Kit, you can find this component in `pages/+page.svelte`. Customise it there for the default page.
```svelte
<script>
import { SiteHeader } from '@reuters-graphics/graphics-components';
</script>
<SiteHeader />
```

View file

@ -1,11 +0,0 @@
Colours are customised by the [`Theme`](?path=/docs/theming-theme--default) component.
```svelte
<script>
import { SiteHeader, Theme } from '@reuters-graphics/graphics-components';
</script>
<Theme base="dark">
<SiteHeader />
</Theme>
```