SiteHeader
This commit is contained in:
parent
e0b9da1b29
commit
6fd7b88696
11 changed files with 112 additions and 96 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
||||||
33
src/components/SiteHeader/SiteHeader.mdx
Normal file
33
src/components/SiteHeader/SiteHeader.mdx
Normal 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>
|
||||||
|
```
|
||||||
|
|
@ -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 />
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
|
||||||
```
|
|
||||||
|
|
@ -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>
|
|
||||||
```
|
|
||||||
Loading…
Reference in a new issue