fix merge conflict
4
.vscode/settings.json
vendored
|
|
@ -12,7 +12,7 @@
|
|||
"editor.wordWrap": "on"
|
||||
},
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||
},
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
import SharkImg from './stories/shark.jpg';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<YourComponent {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -36,9 +36,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
src: SharkImg,
|
||||
altText: "Duh dum! It's a shark!!",
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@
|
|||
|
||||
<Block {width} {id} class="photo {cls}">
|
||||
<div
|
||||
style:background-image="{`url(${src})`}"
|
||||
style:height="{`${height}px`}"
|
||||
style:background-image={`url(${src})`}
|
||||
style:height={`${height}px`}
|
||||
></div>
|
||||
<p class="visually-hidden">{altText}</p>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@
|
|||
"dayjs": "^1.11.13",
|
||||
"journalize": "^2.6.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.3.0",
|
||||
"marked": "^15.0.7",
|
||||
"marked-smartypants": "^1.1.9",
|
||||
"proper-url-join": "^2.1.2",
|
||||
"pym.js": "^1.3.2",
|
||||
"slugify": "^1.6.6",
|
||||
|
|
|
|||
|
|
@ -33,8 +33,11 @@ importers:
|
|||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
marked:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
specifier: ^15.0.7
|
||||
version: 15.0.7
|
||||
marked-smartypants:
|
||||
specifier: ^1.1.9
|
||||
version: 1.1.9(marked@15.0.7)
|
||||
proper-url-join:
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2
|
||||
|
|
@ -2968,9 +2971,14 @@ packages:
|
|||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
marked@4.3.0:
|
||||
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
||||
engines: {node: '>= 12'}
|
||||
marked-smartypants@1.1.9:
|
||||
resolution: {integrity: sha512-VPeuaUr5IWptI7nJdgQ9ugrLWYGv13NdzEXTtKY3cmB4aRWOI2RzhLlf+xQp6Wnob9SAPO2sNVlfSJr+nflk/A==}
|
||||
peerDependencies:
|
||||
marked: '>=4 <16'
|
||||
|
||||
marked@15.0.7:
|
||||
resolution: {integrity: sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==}
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
|
|
@ -3909,6 +3917,10 @@ packages:
|
|||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
smartypants@0.2.2:
|
||||
resolution: {integrity: sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==}
|
||||
hasBin: true
|
||||
|
||||
smol-toml@1.3.1:
|
||||
resolution: {integrity: sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
|
@ -7598,7 +7610,12 @@ snapshots:
|
|||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
marked@4.3.0: {}
|
||||
marked-smartypants@1.1.9(marked@15.0.7):
|
||||
dependencies:
|
||||
marked: 15.0.7
|
||||
smartypants: 0.2.2
|
||||
|
||||
marked@15.0.7: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
|
|
@ -8979,6 +8996,8 @@ snapshots:
|
|||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
smartypants@0.2.2: {}
|
||||
|
||||
smol-toml@1.3.1: {}
|
||||
|
||||
snake-case@3.0.4:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ An action you can use to easily set [CSS variables](https://developer.mozilla.or
|
|||
</script>
|
||||
|
||||
<!-- Attach it to a parent element with the action -->
|
||||
<div use:cssVariables="{variables}">
|
||||
<div use:cssVariables={variables}>
|
||||
<p>My text...</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ An action you can use to easily to check when a DOM element's dimensions change
|
|||
let elementWidth = 0;
|
||||
</script>
|
||||
|
||||
<div use:resizeObserver="{(element) => (elementWidth = element.clientWidth)}">
|
||||
<div use:resizeObserver={(element) => (elementWidth = element.clientWidth)}>
|
||||
My width is: {elementWidth}
|
||||
</div>
|
||||
```
|
||||
|
|
|
|||
20
src/app.html
|
|
@ -1,11 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -48,3 +48,18 @@ export interface ScrollerStep {
|
|||
*/
|
||||
foregroundProps?: object;
|
||||
}
|
||||
|
||||
export interface PhotoCarouselImage {
|
||||
/** Image source */
|
||||
src: string;
|
||||
/** Image alt text */
|
||||
altText: string;
|
||||
/** Optional caption */
|
||||
caption?: string;
|
||||
/** Optional credit */
|
||||
credit?: string;
|
||||
/** Optional object-fit rule */
|
||||
objectFit?: string;
|
||||
/** Optional object-position rule */
|
||||
objectPosition?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import * as AnalyticsStories from './Analytics.stories.svelte';
|
|||
|
||||
# Analytics
|
||||
|
||||
Add Google and Chartbeat analytics to your page.
|
||||
The `Analytics` component adds Google and Chartbeat analytics to your page.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
|
|
@ -42,7 +42,7 @@ For example, the following excludes analytics from pages in development or hoste
|
|||
|
||||
If you're using analytics to measure a multipage newsapp that uses [client-side routing](https://kit.svelte.dev/docs/glossary#routing), then you may need to trigger analytics after virtual page navigation.
|
||||
|
||||
This component also exports a function you can call to register pageviews.
|
||||
This component exports a function you can call to register pageviews.
|
||||
|
||||
For example, here's how you can use SvelteKit's [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate) lifecycle to capture additional pageviews:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta, type Args } from '@storybook/addon-svelte-csf';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Analytics from './Analytics.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
|
|
@ -8,15 +8,10 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#snippet template(args: Args<typeof Story>)}
|
||||
<Analytics {...args} />
|
||||
<div>Nothing to see here</div>
|
||||
{/snippet}
|
||||
|
||||
<Story
|
||||
name="Demo"
|
||||
tags={['!autodocs', '!dev']}
|
||||
args={{
|
||||
authors: [{ name: 'Jane Doe' }, { name: 'John Doe' }],
|
||||
}}
|
||||
children={template}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
<!-- @component `Analytics` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-ads-analytics-analytics--docs) -->
|
||||
|
||||
<script module>
|
||||
import { registerPageview as registerChartbeatPageview } from './providers/chartbeat';
|
||||
import { registerPageview as registerGAPageview } from './providers/ga';
|
||||
|
|
@ -13,7 +15,6 @@
|
|||
export { registerPageview };
|
||||
</script>
|
||||
|
||||
<!-- @component `Analytics` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-ads-analytics-analytics--docs) -->
|
||||
<script lang="ts">
|
||||
interface Author {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
Add Google and Chartbeat analytics to your page.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Analytics } from '@reuters-graphics/graphics-components';
|
||||
|
||||
const authors = [{ name: 'Jane Doe' }, { name: 'John Doe' }];
|
||||
</script>
|
||||
|
||||
<Analytics authors="{authors}" />
|
||||
```
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
Generally, you only want to send page analytics in production environments.
|
||||
|
||||
In a SvelteKit context, you can use `$app` stores to restrict when you send analytics.
|
||||
|
||||
For example, the following excludes analytics from pages in development or hosted on our preview server:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Analytics } from '@reuters-graphics/graphics-components';
|
||||
import { dev } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
{#if !dev && $page.url?.hostname !== 'graphics.thomsonreuters.com'}
|
||||
<Analytics />
|
||||
{/if}
|
||||
```
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
If you're using analytics to measure a multipage newsapp that uses [client-side routing](https://kit.svelte.dev/docs/glossary#routing), then you may need to trigger analytics after virtual page navigation.
|
||||
|
||||
This component also exports a function you can call to register pageviews.
|
||||
|
||||
For example, here's how you can use SvelteKit's [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate) lifecycle to capture additional pageviews:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import {
|
||||
Analytics,
|
||||
registerPageview,
|
||||
} from '@reuters-graphics/graphics-components';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
|
||||
let isFirstPageview = true;
|
||||
|
||||
afterNavigate(() => {
|
||||
// We shouldn't fire on initial page load because the Analytics component
|
||||
// already registers a reader's first pageview. After this component
|
||||
// has initially mounted, we can be sure that further navigation is virtual
|
||||
// and register pageviews using this function.
|
||||
if (!isFirstPageview) {
|
||||
registerPageview();
|
||||
} else {
|
||||
isFirstPageview = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Analytics />
|
||||
```
|
||||
111
src/components/BeforeAfter/BeforeAfter.mdx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as BeforeAfterStories from './BeforeAfter.stories.svelte';
|
||||
|
||||
<Meta of={BeforeAfterStories} />
|
||||
|
||||
# BeforeAfter
|
||||
|
||||
The `BeforeAfter` component shows a before-and-after comparison of an image.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { BeforeAfter } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // 👈 If using in the Graphics Kit...
|
||||
</script>
|
||||
|
||||
<BeforeAfter
|
||||
beforeSrc={`${assets}/images/before-after/myrne-before.jpg`}
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc={`${assets}/images/before-after/myrne-after.jpg`}
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={BeforeAfterStories.Demo} />
|
||||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[blocks]
|
||||
|
||||
type: before-after
|
||||
beforeSrc: images/before-after/myrne-before.jpg
|
||||
beforeAlt: Satellite image of Russian base at Myrne taken on July 7, 2020.
|
||||
afterSrc: images/before-after/myrne-after.jpg
|
||||
afterAlt: Satellite image of Russian base at Myrne taken on Oct. 20, 2020.
|
||||
|
||||
[]
|
||||
```
|
||||
|
||||
... which you'll parse out of a ArchieML block object before passing to the `BeforeAfter` component.
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'before-after'}
|
||||
<BeforeAfter
|
||||
beforeSrc={`${assets}/${block.beforeSrc}`}
|
||||
beforeAlt={block.beforeAlt}
|
||||
afterSrc={`${assets}/${block.afterSrc}`}
|
||||
afterAlt={block.afterAlt}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
<Canvas of={BeforeAfterStories.Demo} />
|
||||
|
||||
## Adding text
|
||||
|
||||
To add text overlays and captions, use [snippets](https://svelte.dev/docs/svelte/snippet) for `beforeOverlay`, `afterOverlay` and `caption`. You can style the snippets to match your page design, like in [this demo](./?path=/story/components-multimedia-beforeafter--with-overlays).
|
||||
|
||||
> 💡**NOTE:** The text in the overlays are used as [ARIA descriptions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) for your before and after images. You must always use the `beforeAlt` / `afterAlt` props to label your image for visually impaired readers, but these ARIA descriptions provide additional information or context that the reader might need.
|
||||
|
||||
```svelte
|
||||
<BeforeAfter
|
||||
beforeSrc={beforeImg}
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc={afterImg}
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
>
|
||||
<!-- Optional custom text overlay for the before image -->
|
||||
{#snippet beforeOverlay()}
|
||||
<div class="overlay p-3 before text-left">
|
||||
<p class="h4 font-bold">July 7, 2020</p>
|
||||
<p class="body-note">Initially, this site was far smaller.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<!-- Optional custom text overlay for the after image -->
|
||||
{#snippet afterOverlay()}
|
||||
<div class="overlay p-3 after text-right">
|
||||
<p class="h4 font-bold">Oct. 20, 2020</p>
|
||||
<p class="body-note">But then forces built up.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<!-- Optional custom caption for both images -->
|
||||
{#snippet caption()}
|
||||
<p class="body-note">Photos by MAXAR Technologies, 2021.</p>
|
||||
{/snippet}
|
||||
</BeforeAfter>
|
||||
|
||||
<style lang="scss">
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
max-width: 350px;
|
||||
&.after {
|
||||
text-align: right;
|
||||
}
|
||||
p {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
<Canvas of={BeforeAfterStories.WithText} />
|
||||
|
|
@ -1,19 +1,10 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: end is out of bounds -->
|
||||
<script context="module" lang="ts">
|
||||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import BeforeAfter from './BeforeAfter.svelte';
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import withOverlaysDocs from './stories/docs/withOverlays.md?raw';
|
||||
// @ts-ignore raw
|
||||
import ariaDescriptionsDocs from './stories/docs/ariaDescriptions.md?raw';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
title: 'Components/Graphics/BeforeAfter',
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Multimedia/BeforeAfter',
|
||||
component: BeforeAfter,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
handleColour: { control: 'color' },
|
||||
width: {
|
||||
|
|
@ -21,96 +12,54 @@
|
|||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
// @ts-ignore raw
|
||||
import beforeImg from './stories/myrne-before.jpg';
|
||||
// @ts-ignore raw
|
||||
import afterImg from './stories/myrne-after.jpg';
|
||||
import beforeImg from './images/myrne-before.jpg';
|
||||
import afterImg from './images/myrne-after.jpg';
|
||||
</script>
|
||||
|
||||
<Template let:args>
|
||||
<BeforeAfter {...args} />
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
name="Demo"
|
||||
args={{
|
||||
beforeSrc: beforeImg,
|
||||
beforeAlt:
|
||||
'Satellite image of Russian base at Myrne taken on July 7, 2020.',
|
||||
afterSrc: afterImg,
|
||||
afterAlt:
|
||||
'Satellite image of Russian base at Myrne taken on Oct. 20, 2020.',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="With overlays" {...withStoryDocs(withOverlaysDocs)}>
|
||||
<Story name="With text" exportName="WithText">
|
||||
<BeforeAfter
|
||||
beforeSrc="{beforeImg}"
|
||||
beforeSrc={beforeImg}
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc="{afterImg}"
|
||||
afterSrc={afterImg}
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
>
|
||||
<div slot="beforeOverlay" class="overlay p-3 before">
|
||||
<p class="h4 font-bold">July 7, 2020</p>
|
||||
<p class="body-note">Initially, this site was far smaller.</p>
|
||||
</div>
|
||||
<div slot="afterOverlay" class="overlay p-3 after">
|
||||
<p class="h4 font-bold">Oct. 20, 2020</p>
|
||||
<p class="body-note">But then forces built up.</p>
|
||||
</div>
|
||||
<p slot="caption">Photos by MAXAR Technologies, 2021.</p>
|
||||
{#snippet beforeOverlay()}
|
||||
<div class="overlay p-3 before text-left">
|
||||
<p class="h4 font-bold">July 7, 2020</p>
|
||||
<p class="body-note">Initially, this site was far smaller.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet afterOverlay()}
|
||||
<div class="overlay p-3 after text-right">
|
||||
<p class="h4 font-bold">Oct. 20, 2020</p>
|
||||
<p class="body-note">But then forces built up.</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet caption()}
|
||||
<p class="body-note">Photos by MAXAR Technologies, 2021.</p>
|
||||
{/snippet}
|
||||
</BeforeAfter>
|
||||
|
||||
<style lang="scss">
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
max-width: 350px;
|
||||
&.after {
|
||||
text-align: right;
|
||||
}
|
||||
p {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</Story>
|
||||
|
||||
<Story name="ARIA descriptions" {...withStoryDocs(ariaDescriptionsDocs)}>
|
||||
<BeforeAfter
|
||||
beforeSrc="{beforeImg}"
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc="{afterImg}"
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
>
|
||||
<div let:description="{id}" slot="beforeOverlay" class="overlay p-3">
|
||||
<p class="body-caption" {id}>
|
||||
On July 7, 2020, the base contained only a few transport vehicles.
|
||||
</p>
|
||||
</div>
|
||||
<div let:description="{id}" slot="afterOverlay" class="overlay p-3">
|
||||
<!-- 👇 id can also be used on an element containing multiple text elements -->
|
||||
<div {id}>
|
||||
<p class="body-caption">
|
||||
But by October, tanks and artillery could be seen.
|
||||
</p>
|
||||
<p class="body-caption">
|
||||
In total, over 50 pieces of heavy machinery and 200 personnel are now
|
||||
based here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p slot="caption">Photos by MAXAR Technologies, 2021.</p>
|
||||
</BeforeAfter>
|
||||
<style lang="scss">
|
||||
div.overlay {
|
||||
max-width: 250px;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
p {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,119 +1,149 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `BeforeAfter` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-graphics-beforeafter--docs) -->
|
||||
<script lang="ts">
|
||||
import { type Snippet } from 'svelte';
|
||||
import { throttle } from 'lodash-es';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
import { random4 } from '../../utils/';
|
||||
|
||||
/** Width of the chart within the text well. */
|
||||
export let width: ContainerWidth = 'normal'; // options: wide, wider, widest, fluid
|
||||
/** Height of the component */
|
||||
export let height = 600;
|
||||
interface Props {
|
||||
/** Width of the chart within the text well. Options: wide, wider, widest, fluid */
|
||||
width?: ContainerWidth;
|
||||
/** Height of the component */
|
||||
height?: number;
|
||||
/**
|
||||
* If set, makes the height a ratio of the component's width.
|
||||
*/
|
||||
heightRatio?: number;
|
||||
/**
|
||||
* Before image source
|
||||
*/
|
||||
beforeSrc: string;
|
||||
/**
|
||||
* Before image altText
|
||||
*/
|
||||
beforeAlt: string;
|
||||
/**
|
||||
* After image source
|
||||
*/
|
||||
afterSrc: string;
|
||||
/**
|
||||
* After image altText
|
||||
*/
|
||||
afterAlt: string;
|
||||
/**
|
||||
* Class to target with SCSS.
|
||||
*/
|
||||
class?: string;
|
||||
/** Drag handle colour */
|
||||
handleColour?: string;
|
||||
/** Drag handle opacity */
|
||||
handleInactiveOpacity?: number;
|
||||
/** Margin at the edge of the image to stop dragging */
|
||||
handleMargin?: number;
|
||||
/** Percentage of the component width the handle will travel ona key press */
|
||||
keyPressStep?: number;
|
||||
/** Initial offset of the handle, between 0 and 1. */
|
||||
offset?: number;
|
||||
/** ID to target with SCSS. */
|
||||
id?: string;
|
||||
/**
|
||||
* Optional snippet for a custom overlay for the before image.
|
||||
*/
|
||||
beforeOverlay?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom overlay for the after image.
|
||||
*/
|
||||
afterOverlay?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom caption.
|
||||
*/
|
||||
caption?: Snippet;
|
||||
/** Custom ARIA label language to label the component. */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, makes the height a ratio of the component's width.
|
||||
* @type {number}
|
||||
let {
|
||||
width = 'normal',
|
||||
height = 600,
|
||||
heightRatio,
|
||||
beforeSrc,
|
||||
beforeAlt,
|
||||
afterSrc,
|
||||
afterAlt,
|
||||
class: cls = '',
|
||||
handleColour = 'white',
|
||||
handleInactiveOpacity = 0.9,
|
||||
handleMargin = 20,
|
||||
keyPressStep = 0.05,
|
||||
offset = 0.5,
|
||||
id = 'before-after-' + random4() + random4(),
|
||||
beforeOverlay,
|
||||
afterOverlay,
|
||||
caption,
|
||||
ariaLabel = 'Stacked before and after images with an adjustable slider',
|
||||
}: Props = $props();
|
||||
|
||||
/** DOM nodes are undefined until the component is mounted — in other words, you should read it inside an effect or an event handler, but not during component initialisation.
|
||||
*/
|
||||
export let heightRatio: number | null = null;
|
||||
let img: HTMLImageElement | undefined = $state(undefined);
|
||||
|
||||
/**
|
||||
* Before image src
|
||||
* @required
|
||||
*/
|
||||
export let beforeSrc: string | null = null;
|
||||
/**
|
||||
* Before image altText
|
||||
* @required
|
||||
*/
|
||||
export let beforeAlt: string | null = null;
|
||||
/**
|
||||
* After image src
|
||||
* @required
|
||||
*/
|
||||
export let afterSrc: string | null = null;
|
||||
/**
|
||||
* After image altText
|
||||
* @required
|
||||
*/
|
||||
export let afterAlt: string | null = null;
|
||||
|
||||
/**
|
||||
* Set a class to target with SCSS.
|
||||
* @type {string}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/** Drag handle colour */
|
||||
export let handleColour = 'white';
|
||||
/** Drag handle opacity */
|
||||
export let handleInactiveOpacity = 0.9;
|
||||
/** Margin at the edge of the image to stop dragging */
|
||||
export let handleMargin = 20;
|
||||
/** Percentage of the component width the handle will travel ona key press */
|
||||
export let keyPressStep = 0.05;
|
||||
|
||||
/** Initial offset of the handle, between 0 and 1. */
|
||||
export let offset = 0.5;
|
||||
|
||||
const random4 = () =>
|
||||
Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
|
||||
/**
|
||||
* Add an ID to target with SCSS.
|
||||
* @type {string}
|
||||
*/
|
||||
export let id: string = 'before-after-' + random4() + random4();
|
||||
|
||||
let img: HTMLImageElement;
|
||||
let imgOffset: DOMRect | null = null;
|
||||
/** Defaults with an empty DOMRect with all values set to 0 */
|
||||
let imgOffset: DOMRect = $state(new DOMRect());
|
||||
let sliding = false;
|
||||
let figure: HTMLElement;
|
||||
let beforeOverlayWidth = 0;
|
||||
let figure: HTMLElement | undefined = $state(undefined);
|
||||
let beforeOverlayWidth = $state(0);
|
||||
let isFocused = false;
|
||||
let containerWidth: number;
|
||||
let containerWidth: number = $state(0); // Defaults to 0
|
||||
|
||||
$: containerHeight =
|
||||
containerWidth && heightRatio ? containerWidth * heightRatio : height;
|
||||
let containerHeight = $derived(
|
||||
containerWidth && heightRatio ? containerWidth * heightRatio : height
|
||||
);
|
||||
|
||||
$: w = (imgOffset && imgOffset.width) || 0;
|
||||
$: x = w * offset;
|
||||
$: figStyle = `width:100%;height:${containerHeight}px;`;
|
||||
$: imgStyle = 'width:100%;height:100%;';
|
||||
$: beforeOverlayClip =
|
||||
x < beforeOverlayWidth ? Math.abs(x - beforeOverlayWidth) : 0;
|
||||
let w = $derived(imgOffset.width);
|
||||
let x = $derived(w * offset);
|
||||
let figStyle = $derived(`width:100%;height:${containerHeight}px;`);
|
||||
const imgStyle = 'width:100%;height:100%;';
|
||||
let beforeOverlayClip = $derived(
|
||||
x < beforeOverlayWidth ? Math.abs(x - beforeOverlayWidth) : 0
|
||||
);
|
||||
|
||||
const onFocus = () => (isFocused = true);
|
||||
const onBlur = () => (isFocused = false);
|
||||
/** Toggle `isFocused` */
|
||||
const onfocus = () => (isFocused = true);
|
||||
const onblur = () => (isFocused = false);
|
||||
|
||||
/** Handle left or right arrows being pressed */
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!isFocused) return;
|
||||
const { keyCode } = e;
|
||||
const { code, key } = e;
|
||||
const margin = handleMargin / w;
|
||||
if (keyCode === 39) {
|
||||
if (code === 'ArrowRight' || key === 'ArrowRight') {
|
||||
offset = Math.min(1 - margin, offset + keyPressStep);
|
||||
} else if (keyCode === 37) {
|
||||
} else if (code === 'ArrowLeft' || key === 'ArrowLeft') {
|
||||
offset = Math.max(0 + margin, offset - keyPressStep);
|
||||
}
|
||||
};
|
||||
|
||||
/** Measure image and set image offset */
|
||||
const measureImage = () => {
|
||||
if (img && img.complete) imgOffset = img.getBoundingClientRect();
|
||||
};
|
||||
|
||||
/** Reset image offset on resize */
|
||||
const resize = () => {
|
||||
measureImage();
|
||||
};
|
||||
|
||||
/** Measure image and set image offset on load */
|
||||
const measureLoadedImage = (e: Event) => {
|
||||
if (e.type === 'load') {
|
||||
imgOffset = (e.target as HTMLImageElement).getBoundingClientRect();
|
||||
}
|
||||
};
|
||||
|
||||
/** Move the slider */
|
||||
const move = (e: MouseEvent | TouchEvent) => {
|
||||
if (sliding && imgOffset) {
|
||||
const el =
|
||||
|
|
@ -130,115 +160,115 @@
|
|||
offset = x / w;
|
||||
}
|
||||
};
|
||||
|
||||
/** Starts the slider */
|
||||
const start = (e: MouseEvent | TouchEvent) => {
|
||||
sliding = true;
|
||||
move(e);
|
||||
};
|
||||
|
||||
/** Sets `sliding` to `false`*/
|
||||
const end = () => {
|
||||
sliding = false;
|
||||
};
|
||||
|
||||
/** Keep this warning since these values are often read from an ArchieML doc, which will not trigger typescript errors even if required values don't exist */
|
||||
if (!(beforeSrc && beforeAlt && afterSrc && afterAlt)) {
|
||||
console.warn('Missing required src or alt props for BeforeAfter component');
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// This is necessary b/c on:load doesn't reliably fire on the image...
|
||||
const interval = setInterval(() => {
|
||||
if (imgOffset) clearInterval(interval);
|
||||
if (img && img.complete && !imgOffset) measureImage();
|
||||
}, 50);
|
||||
});
|
||||
/** @TODO - Double check if this onMount is still necessary */
|
||||
// onMount(() => {
|
||||
// // This is necessary b/c on:load doesn't reliably fire on the image...
|
||||
// const interval = setInterval(() => {
|
||||
// if (imgOffset) clearInterval(interval);
|
||||
// if (img && img.complete && !imgOffset) measureImage();
|
||||
// }, 50);
|
||||
// });
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:touchmove="{move}"
|
||||
on:touchend="{end}"
|
||||
on:mousemove="{move}"
|
||||
on:mouseup="{end}"
|
||||
on:resize="{throttle(resize, 100)}"
|
||||
on:keydown="{handleKeyDown}"
|
||||
ontouchmove={move}
|
||||
ontouchend={end}
|
||||
onmousemove={move}
|
||||
onmouseup={end}
|
||||
onresize={throttle(resize, 100)}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
|
||||
<!-- Since we usually read these values from ArchieML, check that they exist -->
|
||||
{#if beforeSrc && beforeAlt && afterSrc && afterAlt}
|
||||
<Block {width} {id} class="photo before-after fmy-6 {cls}">
|
||||
<div
|
||||
style="height: {containerHeight}px;"
|
||||
bind:clientWidth="{containerWidth}"
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<figure
|
||||
style="{figStyle}"
|
||||
<div style="height: {containerHeight}px;" bind:clientWidth={containerWidth}>
|
||||
<button
|
||||
style={figStyle}
|
||||
class="before-after-container relative overflow-hidden my-0 mx-auto"
|
||||
on:touchstart="{start}"
|
||||
on:mousedown="{start}"
|
||||
bind:this="{figure}"
|
||||
aria-labelledby="{($$slots.caption && `${id}-caption`) || undefined}"
|
||||
ontouchstart={start}
|
||||
onmousedown={start}
|
||||
bind:this={figure}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<img
|
||||
bind:this="{img}"
|
||||
src="{afterSrc}"
|
||||
alt="{afterAlt}"
|
||||
on:load="{measureLoadedImage}"
|
||||
on:mousedown|preventDefault
|
||||
style="{imgStyle}"
|
||||
bind:this={img}
|
||||
src={afterSrc}
|
||||
alt={afterAlt}
|
||||
onload={measureLoadedImage}
|
||||
style={imgStyle}
|
||||
class="after absolute block m-0 max-w-full object-cover"
|
||||
aria-describedby="{($$slots.beforeOverlay && `${id}-before`) ||
|
||||
undefined}"
|
||||
aria-describedby={beforeOverlay ?
|
||||
`${id}-before-description`
|
||||
: undefined}
|
||||
/>
|
||||
|
||||
<img
|
||||
src="{beforeSrc}"
|
||||
alt="{beforeAlt}"
|
||||
on:mousedown|preventDefault
|
||||
src={beforeSrc}
|
||||
alt={beforeAlt}
|
||||
style="clip: rect(0 {x}px {containerHeight}px 0);{imgStyle}"
|
||||
class="before absolute block m-0 max-w-full object-cover"
|
||||
aria-describedby="{($$slots.afterOverlay && `${id}-after`) ||
|
||||
undefined}"
|
||||
aria-describedby={afterOverlay ?
|
||||
`${id}-after-description`
|
||||
: undefined}
|
||||
/>
|
||||
{#if $$slots.beforeOverlay}
|
||||
{#if beforeOverlay}
|
||||
<div
|
||||
id="image-before-label"
|
||||
id="{id}-before-description"
|
||||
class="overlay-container before absolute"
|
||||
bind:clientWidth="{beforeOverlayWidth}"
|
||||
bind:clientWidth={beforeOverlayWidth}
|
||||
style="clip-path: inset(0 {beforeOverlayClip}px 0 0);"
|
||||
>
|
||||
<!-- Overlay for before image -->
|
||||
<slot
|
||||
name="beforeOverlay"
|
||||
description="{`${id}-before-description`}"
|
||||
/>
|
||||
{@render beforeOverlay()}
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.afterOverlay}
|
||||
<div id="image-after-label" class="overlay-container after absolute">
|
||||
{#if afterOverlay}
|
||||
<div
|
||||
id="{id}-after-description"
|
||||
class="overlay-container after absolute"
|
||||
>
|
||||
<!-- Overlay for after image -->
|
||||
<slot
|
||||
name="afterOverlay"
|
||||
description="{`${id}-after-description`}"
|
||||
/>
|
||||
{@render afterOverlay()}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
tabindex="0"
|
||||
role="slider"
|
||||
aria-valuenow="{Math.round(offset * 100)}"
|
||||
aria-valuenow={Math.round(offset * 100)}
|
||||
class="handle"
|
||||
style="left: calc({offset *
|
||||
100}% - 20px); --before-after-handle-colour: {handleColour}; --before-after-handle-inactive-opacity: {handleInactiveOpacity};"
|
||||
on:focus="{onFocus}"
|
||||
on:blur="{onBlur}"
|
||||
{onfocus}
|
||||
{onblur}
|
||||
>
|
||||
<div class="arrow-left"></div>
|
||||
<div class="arrow-right"></div>
|
||||
</div>
|
||||
</figure>
|
||||
</button>
|
||||
</div>
|
||||
{#if $$slots.caption}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<aside class="before-after-caption mx-auto" id="{`${id}-caption`}">
|
||||
{#if caption}
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<aside class="before-after-caption mx-auto" id={`${id}-caption`}>
|
||||
<!-- Caption for image credits -->
|
||||
<slot name="caption" />
|
||||
{@render caption()}
|
||||
</aside>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
|
|
@ -246,9 +276,9 @@
|
|||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '../../scss/mixins' as *;
|
||||
@use '../../scss/mixins' as mixins;
|
||||
|
||||
figure.before-after-container {
|
||||
button.before-after-container {
|
||||
box-sizing: content-box;
|
||||
|
||||
img {
|
||||
|
|
@ -264,7 +294,7 @@
|
|||
user-select: none;
|
||||
}
|
||||
.overlay-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
:global(:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
@ -337,9 +367,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
aside.before-after-caption {
|
||||
.before-after-caption {
|
||||
:global(p) {
|
||||
@include body-caption;
|
||||
@include mixins.body-caption;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 715 KiB After Width: | Height: | Size: 715 KiB |
|
Before Width: | Height: | Size: 472 KiB After Width: | Height: | Size: 472 KiB |
|
|
@ -1,35 +0,0 @@
|
|||
Use text elements in your overlays as [ARIA descriptions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) for your images by setting an ID on text elements within each overlay with the `description` [slot prop](https://svelte.dev/tutorial/slot-props).
|
||||
|
||||
> **💡 TIP:** You must always use the `beforeAlt` / `afterAlt` props to label your image for visually impaired readers, but you can use these descriptions to provide more information or context that the reader might also need.
|
||||
|
||||
```svelte
|
||||
<BeforeAfter
|
||||
beforeSrc="{beforeImg}"
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc="{afterImg}"
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
>
|
||||
<div slot="beforeOverlay" class="overlay p-3 before">
|
||||
<p class="h4 font-bold">July 7, 2020</p>
|
||||
<p class="body-note">Initially, this site was far smaller.</p>
|
||||
</div>
|
||||
<div slot="afterOverlay" class="overlay p-3 after">
|
||||
<p class="h4 font-bold">Oct. 20, 2020</p>
|
||||
<p class="body-note">But then forces built up.</p>
|
||||
</div>
|
||||
<p slot="caption">Photos by MAXAR Technologies, 2021.</p>
|
||||
</BeforeAfter>
|
||||
|
||||
<style lang="scss">
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
max-width: 350px;
|
||||
&.after {
|
||||
text-align: right;
|
||||
}
|
||||
p {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
A before and after image comparison component.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { BeforeAfter } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // If using in the Graphics Kit
|
||||
</script>
|
||||
|
||||
<BeforeAfter
|
||||
beforeSrc="{`${assets}/images/before-after/myrne-before.jpg`}"
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc="{`${assets}/images/before-after/myrne-after.jpg`}"
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
/>
|
||||
```
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
Add overlays with the `beforeOverlay` and `afterOverlay` slots and a caption to the `caption` slot, then style these elements to match your page design as below.
|
||||
|
||||
```svelte
|
||||
<BeforeAfter
|
||||
beforeSrc="{beforeImg}"
|
||||
beforeAlt="Satellite image of Russian base at Myrne taken on July 7, 2020."
|
||||
afterSrc="{afterImg}"
|
||||
afterAlt="Satellite image of Russian base at Myrne taken on Oct. 20, 2020."
|
||||
>
|
||||
<div slot="beforeOverlay" class="overlay p-3 before">
|
||||
<p class="h4 font-bold">July 7, 2020</p>
|
||||
<p class="body-note">Initially, this site was far smaller.</p>
|
||||
</div>
|
||||
<div slot="afterOverlay" class="overlay p-3 after">
|
||||
<p class="h4 font-bold">Oct. 20, 2020</p>
|
||||
<p class="body-note">But then forces built up.</p>
|
||||
</div>
|
||||
<p slot="caption">Photos by MAXAR Technologies, 2021.</p>
|
||||
</BeforeAfter>
|
||||
|
||||
<style lang="scss">
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
max-width: 350px;
|
||||
&.after {
|
||||
text-align: right;
|
||||
}
|
||||
p {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
99
src/components/Block/Block.mdx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as BlockStories from './Block.stories.svelte';
|
||||
|
||||
<Meta of={BlockStories} />
|
||||
|
||||
# Block
|
||||
|
||||
The `Block` component is the basic building block of pages, a responsive container that wraps around each section of your piece.
|
||||
|
||||
Blocks are stacked vertically within the well created by the [Article](./?path=/docs/components-page-layout-article--docs) component. They can have different widths on larger screens depending on the `width` prop.
|
||||
|
||||
> 📌 Many of our other components already use the `Block` component internally. You'll usually only need to use it yourself if you're making something custom.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Block } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
<!-- Contents for this block goes here -->
|
||||
</Block>
|
||||
```
|
||||
|
||||
<Canvas of={BlockStories.Demo} />
|
||||
|
||||
## Custom layouts
|
||||
|
||||
Our article well is designed to provide a basic responsive layout for you, but it also lets you customise.
|
||||
|
||||
The radical but easiest way to do this is to create a `Block` with a `fluid` width -- which basically cancels out the article well dimensions -- and then code whatever you need from scratch or with another framework.
|
||||
|
||||
The demo below does exactly that to create an edge-to-edge grid with [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/).
|
||||
|
||||
```svelte
|
||||
<Block width="fluid">
|
||||
<div class="my-radical-container">
|
||||
<!-- Now, you have full control over layout! -->
|
||||
</div>
|
||||
</Block>
|
||||
```
|
||||
|
||||
<Canvas of={BlockStories.CustomLayout} />
|
||||
|
||||
## Snap widths
|
||||
|
||||
Normally, `Block` containers resize fluidly below the original `width`. Sometimes, though, you may want the container to snap to the next breakpoint -- for example, if you have a static graphic that looks fine at the set block breakpoints, but isn't so great at widths inbetween.
|
||||
|
||||
You can use the `snap` prop to force the container to snap to each block width successively as the window sizes down.
|
||||
|
||||
```svelte
|
||||
<Block width="wider" snap={true}>
|
||||
<!-- Contents for this block -->
|
||||
</Block>
|
||||
```
|
||||
|
||||
<Canvas of={BlockStories.SnapWidthsBasic} />
|
||||
|
||||
If you want to skip certain block widths entirely, you can add one or more class of `skip-{block width class}` to the `Block`.
|
||||
|
||||
> **NOTE:** The snap width breakpoints only work on `Block` components with widths `wider` and below. `widest` and `fluid` are both **always** fluid, since they go edge-to-edge.
|
||||
|
||||
```svelte
|
||||
<!-- Will skip wide and go straight to normal column width on resize. -->
|
||||
<Block width="wider" snap={true} class="skip-wide">
|
||||
<!-- Contents for this block -->
|
||||
</Block>
|
||||
```
|
||||
|
||||
This is probably easier to see in action than explain in words, so [resize the demo](./?path=/docs/components-page-layout-block--snap-skip-widths) to get a better picture of how it all works.
|
||||
|
||||
## Using with custom column widths
|
||||
|
||||
Snap width breakpoints are hard-coded to the default article well column widths, so if you set custom `columnWidths` on the [Article](./?path=/docs/components-page-layout-article--docs) component (**rare!**), you need to do a littie work to use this functionality.
|
||||
|
||||
Luckily, it's still pretty easy. Just add a `cls` or `id` to your `Block` so you can target it with some custom SCSS. Then define a few SCSS variables corresponding to your custom column widths, and use the `block-snap-widths` SCSS mixin to get the same functionality at your custom breakpoints.
|
||||
|
||||
```svelte
|
||||
<Block width="wider" snap={true} class="custom-blocks">
|
||||
<!-- Contents for this block -->
|
||||
</Block>
|
||||
|
||||
<style lang="scss">
|
||||
// Define custom column widths
|
||||
$column-width-narrower: 310px;
|
||||
$column-width-narrow: 450px;
|
||||
$column-width-normal: 600px;
|
||||
$column-width-wide: 860px;
|
||||
$column-width-wider: 1400px;
|
||||
|
||||
@use '@reuters-graphics/graphics-components/scss/mixins' as mixins;
|
||||
|
||||
:global {
|
||||
div.custom-blocks {
|
||||
@include mixins.block-snap-widths; // Use the `block-snap-widths` mixin
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
|
@ -1,19 +1,11 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import customLayoutsDocs from './stories/docs/customLayouts.md?raw';
|
||||
// @ts-ignore raw
|
||||
import snapWidthsDocs from './stories/docs/snapWidths.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Block from './Block.svelte';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Page layout/Block',
|
||||
component: Block,
|
||||
...withComponentDocs(componentDocs),
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
|
|
@ -28,35 +20,27 @@
|
|||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
import Article from '../Article/Article.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
<div class="label">Article</div>
|
||||
<Block {...args}>
|
||||
<div class="label">Block</div>
|
||||
</Block>
|
||||
</div>
|
||||
</Article>
|
||||
{/snippet}
|
||||
</Template>
|
||||
{#snippet template()}
|
||||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
<div class="label">Article</div>
|
||||
<Block>
|
||||
<div class="label">Block</div>
|
||||
</Block>
|
||||
</div>
|
||||
</Article>
|
||||
{/snippet}
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
width: 'normal',
|
||||
}}"
|
||||
/>
|
||||
<Story name="Demo" children={template} />
|
||||
|
||||
<Story name="Custom layouts" {...withStoryDocs(customLayoutsDocs)}>
|
||||
<Story name="Custom layout" exportName="CustomLayout">
|
||||
<Block width="fluid">
|
||||
<!-- Enter bootstrap grid! -->
|
||||
<div id="block-flex-example">
|
||||
|
|
@ -73,53 +57,76 @@
|
|||
</Block>
|
||||
</Story>
|
||||
|
||||
<Story name="Snap widths" {...withStoryDocs(snapWidthsDocs)}>
|
||||
<Story name="Snap widths" exportName="SnapWidthsBasic">
|
||||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
<div class="label">Article</div>
|
||||
<Block width="narrower" snap="{true}" class="block-snap-widths-demo"
|
||||
>narrower</Block
|
||||
>
|
||||
<Block width="narrow" snap="{true}" class="block-snap-widths-demo"
|
||||
>narrow</Block
|
||||
>
|
||||
<Block width="normal" snap="{true}" class="block-snap-widths-demo"
|
||||
>normal</Block
|
||||
>
|
||||
<Block width="wide" snap="{true}" class="block-snap-widths-demo"
|
||||
>wide</Block
|
||||
>
|
||||
<Block width="wider" snap="{true}" class="block-snap-widths-demo"
|
||||
>wider</Block
|
||||
>
|
||||
<Block width="narrower" snap="{true}" class="block-snap-widths-demo even"
|
||||
>narrower</Block
|
||||
>
|
||||
<Block width="narrow" snap="{true}" class="block-snap-widths-demo even"
|
||||
>narrow</Block
|
||||
>
|
||||
<h4>snap widths</h4>
|
||||
<Block snap={true}>
|
||||
<div class="label">Block</div>
|
||||
</Block>
|
||||
</div>
|
||||
</Article>
|
||||
</Story>
|
||||
|
||||
<Story name="Snap and skip widths" exportName="SnapSkipWidths">
|
||||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
<div class="label">Article</div>
|
||||
<h4>Regular layout</h4>
|
||||
|
||||
<Block width="narrower" snap={true} class="block-snap-widths-demo">
|
||||
narrower
|
||||
</Block>
|
||||
<Block width="narrow" snap={true} class="block-snap-widths-demo">
|
||||
narrow
|
||||
</Block>
|
||||
<Block width="normal" snap={true} class="block-snap-widths-demo">
|
||||
normal
|
||||
</Block>
|
||||
<Block width="wide" snap={true} class="block-snap-widths-demo">
|
||||
wide
|
||||
</Block>
|
||||
<Block width="wider" snap={true} class="block-snap-widths-demo">
|
||||
wider
|
||||
</Block>
|
||||
|
||||
<h4>with snap and skip</h4>
|
||||
<Block width="narrower" snap={true} class="block-snap-widths-demo even">
|
||||
narrower
|
||||
</Block>
|
||||
<Block width="narrow" snap={true} class="block-snap-widths-demo even">
|
||||
narrow
|
||||
</Block>
|
||||
<Block
|
||||
width="normal"
|
||||
snap="{true}"
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-narrow"
|
||||
>normal.skip-narrow</Block
|
||||
>
|
||||
normal.skip-narrow
|
||||
</Block>
|
||||
<Block
|
||||
width="wide"
|
||||
snap="{true}"
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-normal skip-narrow"
|
||||
>wide.skip-normal.skip-narrow</Block
|
||||
>
|
||||
wide.skip-normal.skip-narrow
|
||||
</Block>
|
||||
<Block
|
||||
width="wider"
|
||||
snap="{true}"
|
||||
class="block-snap-widths-demo even skip-wide">wider.skip-wide</Block
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-wide"
|
||||
>
|
||||
wider.skip-wide
|
||||
</Block>
|
||||
</div>
|
||||
</Article>
|
||||
</Story>
|
||||
|
||||
<style lang="scss">
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
:global(#block-demo-article) {
|
||||
background-color: #ddd;
|
||||
position: relative;
|
||||
|
|
@ -145,11 +152,12 @@
|
|||
background: rgb(211, 132, 123);
|
||||
}
|
||||
:global(
|
||||
#block-demo-article .label,
|
||||
#block-demo-article div.article-block.block-snap-widths-demo
|
||||
) {
|
||||
#block-demo-article .label,
|
||||
#block-demo-article div.article-block.block-snap-widths-demo
|
||||
) {
|
||||
padding-left: 3px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
div#block-flex-example {
|
||||
|
|
|
|||
|
|
@ -1,37 +1,44 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `Block` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-page-layout-block--docs) -->
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
|
||||
/** Width of the block within the article well. */
|
||||
export let width: ContainerWidth = 'normal';
|
||||
interface Props {
|
||||
/** Content that goes inside `<Block>`*/
|
||||
children: Snippet;
|
||||
/** Width of the block within the article well. */
|
||||
width?: ContainerWidth;
|
||||
/** Add an id to the block tag to target it with custom CSS. */
|
||||
id?: string;
|
||||
/** Add custom classes to the block tag to target it with custom CSS. */
|
||||
class?: string;
|
||||
/** Snap block to column widths, rather than fluidly resizing them. */
|
||||
snap?: boolean;
|
||||
/** ARIA [role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) for the block */
|
||||
role?: string;
|
||||
/** ARIA [label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) for the block */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
/** Add an id to the block tag to target it with custom CSS. */
|
||||
export let id: string = '';
|
||||
|
||||
/** Add extra classes to the block tag to target it with custom CSS. */
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/** Snap block to column widths, rather than fluidly resizing them. */
|
||||
export let snap: boolean = false;
|
||||
|
||||
/** ARIA [role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) for the block */
|
||||
export let role: string | null = null;
|
||||
|
||||
/** ARIA [label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) for the block */
|
||||
export let ariaLabel: string | null = null;
|
||||
let {
|
||||
children,
|
||||
width = 'normal',
|
||||
id = '',
|
||||
class: cls = '',
|
||||
snap = false,
|
||||
role,
|
||||
ariaLabel,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
{id}
|
||||
class="article-block fmx-auto {width} {cls}"
|
||||
class:snap="{snap && width !== 'fluid' && width !== 'widest'}"
|
||||
class:snap={snap && width !== 'fluid' && width !== 'widest'}
|
||||
{role}
|
||||
aria-label="{ariaLabel}"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<!-- block content -->
|
||||
<slot />
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -66,7 +73,7 @@
|
|||
max-width: none;
|
||||
}
|
||||
|
||||
// Only setup for the default column widths, b/c can't use
|
||||
// Only setup for the default column widths, b/c we can't use
|
||||
// CSS vars in media queries.
|
||||
&.snap {
|
||||
@include mixins.block-snap-widths;
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
The `Block` component is the basic building block of stories, a responsive container that wraps each section of your piece.
|
||||
|
||||
Blocks are stacked vertically within the well created by the [`Article`](./?path=/docs/layout-article) component. They can have different widths on larger screens depending on the `width` prop.
|
||||
|
||||
> 📌 Many of our other components already use the `Block` component, internally. You'll usually only need to use it yourself if you're making something custom.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Block } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Block width="normal">
|
||||
<!-- Your stuff for this block -->
|
||||
</Block>
|
||||
```
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
Our article well is designed to provide a basic responsive layout for you, but it's also made to get out of the way quickly when you need to do something custom.
|
||||
|
||||
If you need to get really radical, the easiest way is to create a `Block` with a `fluid` width -- which basically cancels out the article well dimensions -- and then code whatever you need from scratch or with another framework.
|
||||
|
||||
```svelte
|
||||
<Block width="fluid">
|
||||
<div class="my-radical-container">
|
||||
<!-- Now, you have full control! -->
|
||||
</div>
|
||||
</Block>
|
||||
```
|
||||
|
||||
The demo below does exactly that to create an edge-to-edge grid with [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/).
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
Normally, `Block` containers resize fluidly below the original `width`. Sometimes, though, you may want the container to snap to the next breakpoint -- for example, if you have a static graphic that looks fine at the set block breakpoints, but isn't so great at widths inbetween.
|
||||
|
||||
You can use the `snap` prop to force the container to snap to each block width successively as the window sizes down.
|
||||
|
||||
```svelte
|
||||
<Block width="wider" snap="{true}">
|
||||
<!-- Your stuff for this block -->
|
||||
</Block>
|
||||
```
|
||||
|
||||
If you want to skip certain block widths entirely, you can add one or more class of `skip-{block width class}` to the `Block`.
|
||||
|
||||
```svelte
|
||||
<!-- Will skip wide and go straight to normal column width on resize. -->
|
||||
<Block width="wider" snap="{true}" class="skip-wide">
|
||||
<!-- Your stuff for this block -->
|
||||
</Block>
|
||||
```
|
||||
|
||||
This is probably easier to see in action than explain in words, so resize the window to get a better picture of how it all works.
|
||||
|
||||
> **NOTE:** The snap width breakpoints only work on `Block` components with widths `wider` and below. `widest` and `fluid` are both **always** fluid, since they go edge-to-edge.
|
||||
|
||||
#### Using with custom column widths
|
||||
|
||||
Snap width breakpoints are hard-coded to the default article well column widths, so if you set custom `columnWidths` on the `Article` tag (**rare!**), you can't use this functionality without a little extra work.
|
||||
|
||||
Luckily, it's still pretty easy. Just add a `cls` or `id` to your `Block` so you can target it with some custom SCSS. Now, defined a few SCSS variables corresponding to your custom column widths and use the `block-snap-widths` SCSS mixin to get the same functionality at your custom breakpoints.
|
||||
|
||||
```svelte
|
||||
<Block width="wider" snap="{true}" class="custom-blocks">
|
||||
<!-- Your stuff for this block -->
|
||||
</Block>
|
||||
|
||||
<style lang="scss">
|
||||
$column-width-narrower: 310px;
|
||||
$column-width-narrow: 450px;
|
||||
$column-width-normal: 600px;
|
||||
$column-width-wide: 860px;
|
||||
$column-width-wider: 1400px;
|
||||
|
||||
@use '@reuters-graphics/graphics-components/scss/mixins' as mixins;
|
||||
|
||||
:global {
|
||||
div.custom-blocks {
|
||||
@include mixins.block-snap-widths;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
|
@ -30,10 +30,10 @@ Venison shoulder *ham hock ham leberkas*. Flank beef ribs fatback, jerky meatbal
|
|||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the graphics kit, you'll likely get your text value from an ArchieML doc.
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# Archie ML doc
|
||||
# ArchieML doc
|
||||
[blocks]
|
||||
|
||||
type: text
|
||||
|
|
@ -49,6 +49,11 @@ text: Bacon ipsum ...
|
|||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
import { BodyText } from '@reuters-graphics/graphics-components';
|
||||
import content from '$locales/en/content.json';
|
||||
</script>
|
||||
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'text'}
|
||||
<BodyText text={block.text} />
|
||||
|
|
|
|||
106
src/components/Byline/Byline.mdx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as BylineStories from './Byline.stories.svelte';
|
||||
|
||||
<Meta of={BylineStories} />
|
||||
|
||||
# Byline
|
||||
|
||||
The `Byline` component adds a byline, published and updated datelines to your page.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Byline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Byline
|
||||
authors={[
|
||||
'Dea Bankova',
|
||||
'Prasanta Kumar Dutta',
|
||||
'Anurag Rao',
|
||||
'Mariano Zafra',
|
||||
]}
|
||||
publishTime="2021-09-12T00:00:00.000Z"
|
||||
updateTime="2021-09-12T12:57:00.000Z"
|
||||
/>
|
||||
```
|
||||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[authors]
|
||||
* Dea Bankova
|
||||
* Prasanta Kumar Dutta
|
||||
* Anurag Rao
|
||||
* Mariano Zafra
|
||||
[]
|
||||
publishTime: 2021-09-12T00:00:00.000Z
|
||||
updateTime: 2021-09-12T12:57:00.000Z
|
||||
```
|
||||
|
||||
... which you'll pass to the `Byline` component.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Byline } from '@reuters-graphics/graphics-components';
|
||||
import content from '$locales/en/content.json';
|
||||
</script>
|
||||
|
||||
<Byline
|
||||
authors={content.authors}
|
||||
publishTime={content.publishTime}
|
||||
updateTime={content.updateTime}
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={BylineStories.Demo} />
|
||||
|
||||
## Custom byline, published and updated datelines
|
||||
|
||||
Use [snippets](https://svelte.dev/docs/svelte/snippet) to customise the byline, published and updated datelines.
|
||||
|
||||
```svelte
|
||||
<Byline publishTime="2021-09-12T00:00:00Z" updateTime="2021-09-12T13:57:00Z">
|
||||
<!-- Optional custom byline -->
|
||||
{#snippet byline()}
|
||||
<strong>BY REUTERS GRAPHICS</strong>
|
||||
{/snippet}
|
||||
|
||||
<!-- Optional custom published dateline -->
|
||||
{#snippet published()}
|
||||
PUBLISHED on some custom date and time
|
||||
{/snippet}
|
||||
|
||||
<!-- Optional custom updated dateline -->
|
||||
{#snippet updated()}
|
||||
<em>Updated every 5 minutes</em>
|
||||
{/snippet}
|
||||
</Byline>
|
||||
```
|
||||
|
||||
<Canvas of={BylineStories.Customised} />
|
||||
|
||||
## Custom author page
|
||||
|
||||
By default, the `Byline` component will hyperlink each author's byline to their Reuters.com page, formatted `https://www.reuters.com/authors/{author-slug}/`.
|
||||
|
||||
To hyperlink to different pages or email addresses, pass a custom function to the `getAuthorPage` prop.
|
||||
|
||||
```svelte
|
||||
<!-- Pass a custom function as `getAuthorPage` -->
|
||||
<Byline
|
||||
authors={['Dea Bankova', 'Prasanta Kumar Dutta', 'Anurag Rao', 'Mariano Zafra']}
|
||||
publishTime="2021-09-12T00:00:00Z"
|
||||
updateTime="2021-09-12T13:57:00Z"
|
||||
getAuthorPage={(author: string) => {
|
||||
return `mailto:${author.replace(' ', '')}@example.com`;
|
||||
}}
|
||||
/>
|
||||
|
||||
```
|
||||
|
||||
<Canvas of={BylineStories.CustomAuthorPage} />
|
||||
````
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Byline from './Byline.svelte';
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
||||
import { withComponentDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/Byline',
|
||||
component: Byline,
|
||||
...withComponentDocs(componentDocs),
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
align: {
|
||||
control: 'select',
|
||||
|
|
@ -21,17 +15,11 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#snippet template(args: ComponentProps<Byline>)}
|
||||
<Byline {...args} />
|
||||
{/snippet}
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
name="Demo"
|
||||
args={{
|
||||
align: 'left',
|
||||
authors: [
|
||||
'Dea Bankova',
|
||||
'Aditi Bhandari',
|
||||
'Prasanta Kumar Dutta',
|
||||
'Anurag Rao',
|
||||
'Mariano Zafra',
|
||||
|
|
@ -39,5 +27,37 @@
|
|||
publishTime: new Date('2021-09-12').toISOString(),
|
||||
updateTime: new Date('2021-09-12T13:57:00').toISOString(),
|
||||
}}
|
||||
children={template}
|
||||
/>
|
||||
|
||||
<Story name="Customised" tags={['!autodocs', '!dev']}>
|
||||
<Byline publishTime="2021-09-12T00:00:00Z" updateTime="2021-09-12T13:57:00Z">
|
||||
{#snippet byline()}
|
||||
<strong>BY REUTERS GRAPHICS</strong>
|
||||
{/snippet}
|
||||
{#snippet published()}
|
||||
PUBLISHED on some custom date and time
|
||||
{/snippet}
|
||||
{#snippet updated()}
|
||||
<em>Updated every 5 minutes</em>
|
||||
{/snippet}
|
||||
</Byline>
|
||||
</Story>
|
||||
|
||||
<Story
|
||||
name="Custom author page"
|
||||
exportName="CustomAuthorPage"
|
||||
tags={['!autodocs', '!dev']}
|
||||
args={{
|
||||
authors: [
|
||||
'Dea Bankova',
|
||||
'Prasanta Kumar Dutta',
|
||||
'Anurag Rao',
|
||||
'Mariano Zafra',
|
||||
],
|
||||
publishTime: '2021-09-12T00:00:00Z',
|
||||
updateTime: '2021-09-12T13:57:00Z',
|
||||
getAuthorPage: (author: string) => {
|
||||
return `mailto:${author.replace(' ', '')}@example.com`;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,66 +1,88 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `Byline` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-byline--docs) -->
|
||||
<script lang="ts">
|
||||
import { getAuthorPageUrl } from '../../utils';
|
||||
import Block from '../Block/Block.svelte';
|
||||
import slugify from 'slugify';
|
||||
import { apdate } from 'journalize';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Array of author names, which will be slugified to create links to Reuters author pages
|
||||
*/
|
||||
authors?: string[];
|
||||
/**
|
||||
* Publish time as a datetime string.
|
||||
*/
|
||||
publishTime: string;
|
||||
/**
|
||||
* Update time as a datetime string.
|
||||
*/
|
||||
updateTime: string;
|
||||
/**
|
||||
* Alignment of the byline.
|
||||
*/
|
||||
align?: 'left' | 'center';
|
||||
/**
|
||||
* Add an id to to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Add extra classes to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
cls?: string;
|
||||
/**
|
||||
* Custom function that returns an author page URL.
|
||||
*/
|
||||
getAuthorPage?: (author: string) => string;
|
||||
/**
|
||||
* Optional snippet for a custom byline.
|
||||
*/
|
||||
byline?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom published dateline.
|
||||
*/
|
||||
published?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom updated dateline.
|
||||
*/
|
||||
updated?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
authors = [],
|
||||
publishTime,
|
||||
updateTime,
|
||||
align = 'left',
|
||||
id = '',
|
||||
cls = '',
|
||||
getAuthorPage = getAuthorPageUrl,
|
||||
byline,
|
||||
published,
|
||||
updated,
|
||||
}: Props = $props();
|
||||
|
||||
let alignmentClass = $derived(align === 'left' ? 'text-left' : 'text-center');
|
||||
|
||||
/**
|
||||
* Array of author names, which will be slugified to create links to Reuters author pages
|
||||
/* Date validation and formatter functions
|
||||
*/
|
||||
export let authors: string[] = [];
|
||||
/**
|
||||
* Publish time as a datetime string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let publishTime: string = '';
|
||||
/**
|
||||
* Update time as a datetime string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let updateTime: string = '';
|
||||
/**
|
||||
* Alignment of the byline.
|
||||
* @type {string}
|
||||
*/
|
||||
export let align: 'left' | 'center' = 'left';
|
||||
/**
|
||||
* Add an id to to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
export let id: string = '';
|
||||
/**
|
||||
* Add extra classes to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/**
|
||||
* Custom function that returns an author page URL.
|
||||
* @param author
|
||||
*/
|
||||
export let getAuthorPage = (author: string): string => {
|
||||
const authorSlug = slugify(author.trim(), { lower: true });
|
||||
return `https://www.reuters.com/authors/${authorSlug}/`;
|
||||
};
|
||||
|
||||
$: alignmentClass = align === 'left' ? 'text-left' : 'text-center';
|
||||
|
||||
const isValidDate = (datetime) => {
|
||||
const isValidDate = (datetime: string) => {
|
||||
if (!datetime) return false;
|
||||
if (!Date.parse(datetime)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const formatTime = (datetime) =>
|
||||
const formatTime = (datetime: string) =>
|
||||
new Date(datetime).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short',
|
||||
});
|
||||
|
||||
const areSameDay = (first, second) =>
|
||||
const areSameDay = (first: Date, second: Date) =>
|
||||
first.getFullYear() === second.getFullYear() &&
|
||||
first.getMonth() === second.getMonth() &&
|
||||
first.getDate() === second.getDate();
|
||||
|
|
@ -69,16 +91,16 @@
|
|||
<Block {id} class="byline-container {alignmentClass} {cls}" width="normal">
|
||||
<aside class="article-metadata font-subhed">
|
||||
<div class="byline body-caption fmb-1">
|
||||
{#if $$slots.byline}
|
||||
{#if byline}
|
||||
<!-- Custom byline -->
|
||||
<slot name="byline" />
|
||||
{@render byline()}
|
||||
{:else}
|
||||
By
|
||||
{#if authors.length > 0}
|
||||
{#each authors as author, i}
|
||||
<a
|
||||
class="no-underline whitespace-nowrap text-primary font-bold"
|
||||
href="{getAuthorPage(author)}"
|
||||
href={getAuthorPage(author)}
|
||||
rel="author"
|
||||
>
|
||||
{author.trim()}</a
|
||||
|
|
@ -95,15 +117,17 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class="dateline body-caption fmt-0">
|
||||
{#if $$slots.published}
|
||||
{#if published}
|
||||
<div class="whitespace-nowrap inline-block">
|
||||
<!-- Custom published dateline -->
|
||||
<slot name="published" />
|
||||
<!-- Custom published dateline snippet -->
|
||||
<time datetime={publishTime}>
|
||||
{@render published()}
|
||||
</time>
|
||||
</div>
|
||||
{:else if isValidDate(publishTime)}
|
||||
<div class="whitespace-nowrap inline-block">
|
||||
Published
|
||||
<time datetime="{publishTime}">
|
||||
<time datetime={publishTime}>
|
||||
{#if isValidDate(updateTime)}
|
||||
{apdate(new Date(publishTime))}
|
||||
{:else}
|
||||
|
|
@ -114,15 +138,17 @@
|
|||
</time>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.updated}
|
||||
{#if updated}
|
||||
<div class="whitespace-nowrap inline-block">
|
||||
<!-- Custom updated dateline -->
|
||||
<slot name="updated" />
|
||||
<!-- Custom updated dateline snippet -->
|
||||
<time datetime={updateTime}>
|
||||
{@render updated()}
|
||||
</time>
|
||||
</div>
|
||||
{:else if isValidDate(publishTime) && isValidDate(updateTime)}
|
||||
<div class="whitespace-nowrap inline-block">
|
||||
Last updated
|
||||
<time datetime="{updateTime}">
|
||||
<time datetime={updateTime}>
|
||||
{#if areSameDay(new Date(publishTime), new Date(updateTime))}
|
||||
{formatTime(updateTime)}
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
Byline and dateline.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Byline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Byline
|
||||
authors="{[
|
||||
'Dea Bankova',
|
||||
'Aditi Bhandari',
|
||||
'Prasanta Kumar Dutta',
|
||||
'Anurag Rao',
|
||||
'Mariano Zafra',
|
||||
]}"
|
||||
publishTime="2021-09-12T00:00:00.000Z"
|
||||
updateTime="2021-09-12T12:57:00.000Z"
|
||||
/>
|
||||
```
|
||||
26
src/components/DocumentCloud/DocumentCloud.mdx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as DocumentCloudStories from './DocumentCloud.stories.svelte';
|
||||
|
||||
<Meta of={DocumentCloudStories} />
|
||||
|
||||
# DocumentCloud
|
||||
|
||||
The `DocumentCloud` component embeds a document hosted by [DocumentCloud](https://documentcloud.org).
|
||||
|
||||
The document must have its access level set to **public** before it can be embedded. The `slug` can be found after the final slash in the document's URL.
|
||||
|
||||
For instance, the document included in the example is found at [documentcloud.org/documents/3259984-Trump-Intelligence-Allegations](https://www.documentcloud.org/documents/3259984-Trump-Intelligence-Allegations). The `slug` is `3259984-Trump-Intelligence-Allegations`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { DocumentCloud } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<DocumentCloud
|
||||
slug="3259984-Trump-Intelligence-Allegations"
|
||||
altText="These Reports Allege Trump Has Deep Ties To Russia"
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={DocumentCloudStories.Demo} />
|
||||
|
|
@ -1,38 +1,23 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import DocumentCloud from './DocumentCloud.svelte';
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
||||
import { withComponentDocs } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Multimedia/DocumentCloud',
|
||||
component: DocumentCloud,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<DocumentCloud {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
width: 'normal',
|
||||
name="Demo"
|
||||
args={{
|
||||
slug: '3259984-Trump-Intelligence-Allegations',
|
||||
altText: 'These Reports Allege Trump Has Deep Ties To Russia',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,43 +1,41 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `DocumentCloud` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-multimedia-documentcloud--docs) -->
|
||||
<script lang="ts">
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
/** ✏️ DOCUMENT your chart's props using TypeScript and JSDoc comments like below! */
|
||||
|
||||
/**
|
||||
* Width of the container, one of: normal, wide, wider, widest or fluid
|
||||
*/
|
||||
export let width: ContainerWidth = 'normal';
|
||||
|
||||
/**
|
||||
* The unique identifier for the document.
|
||||
* @required
|
||||
*/
|
||||
export let slug: string;
|
||||
|
||||
/**
|
||||
* Alt text for the document.
|
||||
* @required
|
||||
*/
|
||||
export let altText: string;
|
||||
|
||||
/** Add an ID to target with SCSS. */
|
||||
export let id: string = '';
|
||||
|
||||
/** Add a class to target with SCSS. */
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The unique identifier for the document.
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* Alt text for the document.
|
||||
*/
|
||||
altText: string;
|
||||
/**
|
||||
* Width of the container, one of: normal, wide, wider, widest or fluid
|
||||
*/
|
||||
width?: ContainerWidth;
|
||||
/** Add an ID to target with SCSS. */
|
||||
id?: string;
|
||||
/** Add a class to target with SCSS. */
|
||||
class?: string; // Add a class to target with SCSS.
|
||||
}
|
||||
|
||||
let {
|
||||
slug,
|
||||
altText,
|
||||
width = 'normal',
|
||||
id = '',
|
||||
class: cls = '',
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<Block {width} {id} class="photo fmy-6 {cls}">
|
||||
<iframe
|
||||
class="h-screen"
|
||||
src="https://embed.documentcloud.org/documents/{slug}/?embed=1&responsive=1&title=1"
|
||||
title="{altText}"
|
||||
width="700"
|
||||
height="540"
|
||||
title={altText}
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox"
|
||||
></iframe>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
Embed a document hosted by [DocumentCloud](https://documentcloud.org)
|
||||
|
||||
---
|
||||
|
||||
The document must have its access level set to public before it can be embedded. The `slug` argument can be found after the final slash in the document's URL. For instance, the document included in the example is found at [documentcloud.org/documents/3259984-Trump-Intelligence-Allegations](https://www.documentcloud.org/documents/3259984-Trump-Intelligence-Allegations). The slug is "3259984-Trump-Intelligence-Allegations".
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { DocumentCloud } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<DocumentCloud
|
||||
slug="3259984-Trump-Intelligence-Allegations"
|
||||
altText="These Reports Allege Trump Has Deep Ties To Russia"
|
||||
width="normal"
|
||||
/>
|
||||
```
|
||||
19
src/components/EmbedPreviewerLink/EmbedPreviewerLink.mdx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Meta } from '@storybook/blocks';
|
||||
|
||||
import * as EmbedPreviewerLinkStories from './EmbedPreviewerLink.stories.svelte';
|
||||
|
||||
<Meta of={EmbedPreviewerLinkStories} />
|
||||
|
||||
# EmbedPreviewerLink
|
||||
|
||||
The `EmbedPreviewerLink` component is a tool for previewing the embeds in development. It adds an icon at the bottom of the page that, when clicked, opens a previewer with the embeds.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { EmbedPreviewerLink } from '@reuters-graphics/graphics-components';
|
||||
|
||||
import { dev } from '$app/env';
|
||||
</script>
|
||||
|
||||
<EmbedPreviewerLink {dev} />
|
||||
```
|
||||
|
|
@ -1,30 +1,17 @@
|
|||
<script module lang="ts">
|
||||
import EmbedPreviewerLink from './EmbedPreviewerLink.svelte';
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
|
||||
import { withComponentDocs } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Utilities/EmbedPreviewerLink',
|
||||
component: EmbedPreviewerLink,
|
||||
...withComponentDocs(componentDocs),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<EmbedPreviewerLink {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
name="Demo"
|
||||
tags={['!autodocs', '!dev']}
|
||||
args={{
|
||||
dev: true,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Fa from 'svelte-fa/src/fa.svelte';
|
||||
import { faWindowRestore } from '@fortawesome/free-regular-svg-icons';
|
||||
interface Props {
|
||||
|
|
@ -12,7 +11,7 @@
|
|||
{#if dev}
|
||||
<div>
|
||||
<a rel="external" href="/embed-previewer">
|
||||
<Fa icon="{faWindowRestore}" />
|
||||
<Fa icon={faWindowRestore} />
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
An embed tool for development in graphics kit.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { EmbedPreviewerLink } from '@reuters-graphics/graphics-components';
|
||||
|
||||
import { dev } from '$app/env';
|
||||
</script>
|
||||
|
||||
<EmbedPreviewerLink dev="{dev}" />
|
||||
```
|
||||
67
src/components/EndNotes/EndNotes.mdx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as EndNotesStories from './EndNotes.stories.svelte';
|
||||
|
||||
<Meta of={EndNotesStories} />
|
||||
|
||||
# EndNotes
|
||||
|
||||
The `EndNotes` component adds notes such as sources, clarifiying notes and minor corrections that come at the end of a story.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { EndNotes } from '@reuters-graphics/graphics-components';
|
||||
|
||||
const notes = [
|
||||
{
|
||||
title: 'Note',
|
||||
text: 'Data is current as of today.',
|
||||
},
|
||||
{
|
||||
title: 'Sources',
|
||||
text: 'Data, Inc.',
|
||||
},
|
||||
{
|
||||
title: 'Edited by',
|
||||
text: '<a href="https://www.reuters.com/graphics/">Editor</a>, Copyeditor',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<EndNotes {notes} />
|
||||
```
|
||||
|
||||
<Canvas of={EndNotesStories.Demo} />
|
||||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[endNotes]
|
||||
title: Note
|
||||
text: Data is current as of today
|
||||
|
||||
title: Sources
|
||||
text: Data, Inc.
|
||||
|
||||
title: Edited by
|
||||
text: Editor, Copyeditor
|
||||
[]
|
||||
```
|
||||
|
||||
... which you'll pass to the `EndNotes` component.
|
||||
|
||||
```svelte
|
||||
<!-- Graphics Kit -->
|
||||
<script>
|
||||
import { EndNotes } from '@reuters-graphics/graphics-components';
|
||||
|
||||
import content from '$locales/en/content.json';
|
||||
</script>
|
||||
|
||||
<EndNotes notes={content.endNotes} />
|
||||
```
|
||||
|
||||
<Canvas of={EndNotesStories.Demo} />
|
||||
|
|
@ -1,20 +1,15 @@
|
|||
<script module lang="ts">
|
||||
import EndNotes from './EndNotes.svelte';
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
||||
import { withComponentDocs } from '$lib/docs/utils/withParams.js';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/EndNotes',
|
||||
component: EndNotes,
|
||||
...withComponentDocs(componentDocs),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
const notes = [
|
||||
{
|
||||
title: 'Note',
|
||||
|
|
@ -31,10 +26,4 @@
|
|||
];
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<EndNotes {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story name="Default" args="{{ notes }}" />
|
||||
<Story name="Demo" args={{ notes }} />
|
||||
|
|
|
|||
|
|
@ -7,40 +7,33 @@
|
|||
title: string;
|
||||
/**
|
||||
* Contents of the note as a markdown string
|
||||
* @required
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
interface Props {
|
||||
/**
|
||||
* An array of endnote items.
|
||||
* @required
|
||||
*/
|
||||
notes?: EndNote[];
|
||||
* An array of endnote items.
|
||||
*/
|
||||
notes: EndNote[];
|
||||
}
|
||||
|
||||
let { notes = [] }: Props = $props();
|
||||
let { notes }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Block class="notes fmt-6 fmb-8">
|
||||
{#if notes}
|
||||
{#each notes as note}
|
||||
<div class="note-title">
|
||||
<Markdown source="{note.title}" />
|
||||
</div>
|
||||
<div class="note-content">
|
||||
<Markdown source="{note.text}" />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#each notes as note}
|
||||
<div class="note-title">
|
||||
<Markdown source={note.title} />
|
||||
</div>
|
||||
<div class="note-content">
|
||||
<Markdown source={note.text} />
|
||||
</div>
|
||||
{/each}
|
||||
</Block>
|
||||
|
||||
<!-- svelte-ignore css_unused_selector -->
|
||||
<style lang="scss">
|
||||
@use '../../scss/mixins' as mixins;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
End notes includes notes to the main article — usually things like sources, clarifiying notes and minor corrections at the end of a story.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { EndNotes } from '@reuters-graphics/graphics-components';
|
||||
|
||||
const notes = [
|
||||
{
|
||||
title: 'Note',
|
||||
text: 'Data is current as of today.',
|
||||
},
|
||||
{
|
||||
title: 'Sources',
|
||||
text: 'Data, Inc.',
|
||||
},
|
||||
{
|
||||
title: 'Edited by',
|
||||
text: '<a href="https://www.reuters.com/graphics/">Editor</a>, Copyeditor',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<EndNotes notes="{notes}" />
|
||||
```
|
||||
72
src/components/FeaturePhoto/FeaturePhoto.mdx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as FeaturePhotoStories from './FeaturePhoto.stories.svelte';
|
||||
|
||||
<Meta of={FeaturePhotoStories} />
|
||||
|
||||
# FeaturePhoto
|
||||
|
||||
The `FeaturePhoto` component adds a full-width photo.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // 👈 If using in the Graphics Kit...
|
||||
</script>
|
||||
|
||||
<FeaturePhoto
|
||||
src={`${assets}/images/myImage.jpg`}
|
||||
altText="Some alt text"
|
||||
caption="A caption"
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={FeaturePhotoStories.Demo} />
|
||||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the Graphics Kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[blocks]
|
||||
|
||||
type: photo
|
||||
width: normal
|
||||
src: images/shark.jpg
|
||||
altText: The king of the sea
|
||||
caption: Carcharodon carcharias - REUTERS
|
||||
|
||||
[]
|
||||
```
|
||||
|
||||
... which you'll parse out of a ArchieML block object before passing to the `FeaturePhoto` component.
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
||||
|
||||
import content from '$locales/en/content.json';
|
||||
import { assets } from '$app/paths';
|
||||
</script>
|
||||
|
||||
{#each content.blocks as block}
|
||||
{#if block.Type === 'text'}
|
||||
<!-- ... -->
|
||||
{:else if block.type === 'photo'}
|
||||
<FeaturePhoto
|
||||
width={block.width}
|
||||
src={`${assets}/${block.src}`}
|
||||
altText={block.altText}
|
||||
caption={block.caption}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
## Missing alt text
|
||||
|
||||
`altText` is required in this component. If your photo is missing it, a small red text box will overlay the image.
|
||||
|
||||
<Canvas of={FeaturePhotoStories.MissingAltText} />
|
||||
|
|
@ -1,26 +1,10 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import archieMLDocs from './stories/docs/archieML.md?raw';
|
||||
// @ts-ignore raw
|
||||
import missingAltTextDocs from './stories/docs/missingAltText.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import FeaturePhoto from './FeaturePhoto.svelte';
|
||||
|
||||
// import {
|
||||
// withComponentDocs,
|
||||
// withStoryDocs,
|
||||
// } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Multimedia/FeaturePhoto',
|
||||
component: FeaturePhoto,
|
||||
// ...withComponentDocs(componentDocs),
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
|
|
@ -35,38 +19,23 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
// @ts-ignore jpg
|
||||
import sharkSrc from './stories/shark.jpg';
|
||||
import sharkSrc from './images/shark.jpg';
|
||||
</script>
|
||||
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
name="Demo"
|
||||
args={{
|
||||
src: sharkSrc,
|
||||
altText: 'A shark!',
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="ArchieML"
|
||||
args="{{
|
||||
src: sharkSrc,
|
||||
altText: 'A shark!',
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
/>
|
||||
<!-- {...withStoryDocs(archieMLDocs)} -->
|
||||
|
||||
<Story
|
||||
name="Missing altText"
|
||||
args="{{
|
||||
exportName="MissingAltText"
|
||||
args={{
|
||||
src: sharkSrc,
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
<!-- {...withStoryDocs(missingAltTextDocs)} -->
|
||||
|
|
|
|||
|
|
@ -1,75 +1,89 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `FeaturePhoto` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-multimedia-featurephoto--docs) -->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Block from '../Block/Block.svelte';
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
|
||||
|
||||
/**
|
||||
* Photo src
|
||||
* @type {string}
|
||||
* @required
|
||||
*/
|
||||
export let src: string;
|
||||
/**
|
||||
* Photo altText
|
||||
* @type {string}
|
||||
* @required
|
||||
*/
|
||||
export let altText: string;
|
||||
/**
|
||||
* Add an id to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
export let id: string = '';
|
||||
/**
|
||||
* Add extra classes to target with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
/**
|
||||
* Caption below the photo
|
||||
* @type {string}
|
||||
*/
|
||||
export let caption: string;
|
||||
/**
|
||||
* Height of the photo placeholder when lazy-loading
|
||||
*/
|
||||
export let height: number = 100;
|
||||
/**
|
||||
* Width of the container, one of: normal, wide, wider, widest or fluid
|
||||
*/
|
||||
export let width: ContainerWidth = 'normal';
|
||||
interface Props {
|
||||
/**
|
||||
* Photo source
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* Photo altText
|
||||
*/
|
||||
altText: string;
|
||||
/**
|
||||
* Add an id to target with custom CSS.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Add classes to target with custom CSS.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
* Photo caption
|
||||
*/
|
||||
caption?: string;
|
||||
/**
|
||||
* Height of the photo placeholder when lazy-loading
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* Width of the container: normal, wide, wider, widest or fluid
|
||||
*/
|
||||
width?: ContainerWidth;
|
||||
/**
|
||||
* Set a different width for the text vs the photo. For example, "normal" to keep the title, description and notes inline with the rest of the text well. Can't ever be wider than `width`.
|
||||
*/
|
||||
textWidth?: ContainerWidth;
|
||||
/**
|
||||
* Whether to lazy load the photo using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
|
||||
*/
|
||||
lazy?: boolean;
|
||||
/**
|
||||
* Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top` when lazy loading.
|
||||
*/
|
||||
top?: number;
|
||||
/**
|
||||
* Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `bottom` when lazy loading.
|
||||
*/
|
||||
bottom?: number;
|
||||
/**
|
||||
* Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `left` when lazy loading.
|
||||
*/
|
||||
left?: number;
|
||||
/**
|
||||
* Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `right` when lazy loading.
|
||||
*/
|
||||
right?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a different width for the text within the text well, for example,
|
||||
* "normal" to keep the title, description and notes inline with the rest
|
||||
* of the text well. Can't ever be wider than `width`.
|
||||
* @type {string}
|
||||
*/
|
||||
export let textWidth: ContainerWidth | null = 'normal';
|
||||
let {
|
||||
src,
|
||||
altText,
|
||||
id = '',
|
||||
class: cls = '',
|
||||
caption,
|
||||
height = 100,
|
||||
width = 'normal',
|
||||
textWidth = 'normal',
|
||||
lazy = true,
|
||||
top = 0,
|
||||
bottom = 0,
|
||||
left = 0,
|
||||
right = 0,
|
||||
}: Props = $props();
|
||||
|
||||
/**
|
||||
* Whether to lazy load the photo using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
|
||||
*/
|
||||
export let lazy: boolean = false;
|
||||
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top` when lazy loading. */
|
||||
export let top = 0;
|
||||
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `bottom` when lazy loading. */
|
||||
export let bottom = 0;
|
||||
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `left` when lazy loading. */
|
||||
export let left = 0;
|
||||
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `right` when lazy loading. */
|
||||
export let right = 0;
|
||||
|
||||
let intersecting = false;
|
||||
let container;
|
||||
let intersecting = $state(false);
|
||||
let container: HTMLElement;
|
||||
const intersectable = typeof IntersectionObserver !== 'undefined';
|
||||
|
||||
onMount(() => {
|
||||
if (!lazy) return;
|
||||
|
||||
if (intersectable) {
|
||||
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
|
||||
|
||||
|
|
@ -93,18 +107,18 @@
|
|||
|
||||
<Block {width} class="photo fmy-6 {cls}" {id}>
|
||||
<figure
|
||||
bind:this="{container}"
|
||||
bind:this={container}
|
||||
aria-label="media"
|
||||
class="w-full flex flex-col relative"
|
||||
>
|
||||
{#if !lazy || (intersectable && intersecting)}
|
||||
<img class="w-full my-0" {src} alt="{altText}" />
|
||||
<img class="w-full my-0" {src} alt={altText} />
|
||||
{:else}
|
||||
<div class="placeholder w-full" style="{`height: ${height}px;`}"></div>
|
||||
<div class="placeholder w-full" style={`height: ${height}px;`}></div>
|
||||
{/if}
|
||||
{#if caption}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<Block width="{textWidth}" class="notes w-full fmy-0">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<Block width={textWidth} class="notes w-full fmy-0">
|
||||
<figcaption>
|
||||
{caption}
|
||||
</figcaption>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
|
@ -1,37 +0,0 @@
|
|||
```yaml
|
||||
[blocks]
|
||||
|
||||
type: photo
|
||||
width: normal
|
||||
src: images/shark.jpg
|
||||
altText: The king of the sea
|
||||
caption: Carcharodon carcharias - REUTERS
|
||||
|
||||
[]
|
||||
```
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
||||
|
||||
import content from '$locales/en/content.json';
|
||||
import { assets } from '$app/paths';
|
||||
</script>
|
||||
|
||||
{#each content.blocks as block}
|
||||
{#if block.Type === 'text'}
|
||||
<!-- ... -->
|
||||
|
||||
{:else if block.type === 'photo'}
|
||||
<FeaturePhoto
|
||||
width="{block.width}"
|
||||
src="{`${assets}/${block.src}`}"
|
||||
altText="{block.altText}"
|
||||
caption="{block.caption}"
|
||||
/>
|
||||
|
||||
<!-- ... -->
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
A full-width photo inside the text well.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // 👈 If using in the Graphics Kit...
|
||||
</script>
|
||||
|
||||
<FeaturePhoto
|
||||
src="{`${assets}/images/myImage.jpg`}"
|
||||
altText="Some alt text"
|
||||
caption="A caption"
|
||||
lazy="{false}"
|
||||
width="normal"
|
||||
/>
|
||||
```
|
||||
|
|
@ -1 +0,0 @@
|
|||
If your photo is missing `altText` a small warning will overlay the image.
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Framer {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -25,9 +25,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
embeds: [
|
||||
'https://graphics.reuters.com/USA-CONGRESS/FUNDRAISING/zjvqkawjlvx/embeds/en/embed/?zzz',
|
||||
],
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -62,25 +62,20 @@
|
|||
|
||||
<div id="typeahead-container">
|
||||
<div class="embed-link">
|
||||
<a
|
||||
rel="external"
|
||||
target="_blank"
|
||||
href="{activeEmbed}"
|
||||
title="{activeEmbed}"
|
||||
>
|
||||
Live link <Fa icon="{faLink}" />
|
||||
<a rel="external" target="_blank" href={activeEmbed} title={activeEmbed}>
|
||||
Live link <Fa icon={faLink} />
|
||||
</a>
|
||||
</div>
|
||||
<Typeahead
|
||||
label="Select an embed"
|
||||
value="{embedTitles[embeds.indexOf(activeEmbed)] ||
|
||||
value={embedTitles[embeds.indexOf(activeEmbed)] ||
|
||||
embedTitles[activeEmbedIndex] ||
|
||||
embedTitles[0]}"
|
||||
extract="{(d) => embedTitles[d.index]}"
|
||||
data="{embeds.map((embed, index) => ({ index, embed }))}"
|
||||
placeholder="{'Search'}"
|
||||
showDropdownOnFocus="{true}"
|
||||
on:select="{({ detail }) => {
|
||||
embedTitles[0]}
|
||||
extract={(d) => embedTitles[d.index]}
|
||||
data={embeds.map((embed, index) => ({ index, embed }))}
|
||||
placeholder={'Search'}
|
||||
showDropdownOnFocus={true}
|
||||
on:select={({ detail }) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(
|
||||
'framer-active-embed',
|
||||
|
|
@ -89,7 +84,7 @@
|
|||
}
|
||||
activeEmbed = detail.original.embed;
|
||||
activeEmbedIndex = detail.original.index;
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -101,7 +96,7 @@
|
|||
|
||||
<div id="home-link">
|
||||
<a rel="external" href="./../">
|
||||
<Fa icon="{faDesktop}" />
|
||||
<Fa icon={faDesktop} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@
|
|||
minFrameWidth?: number;
|
||||
}
|
||||
|
||||
let { breakpoints = [330, 510, 660, 930, 1200], maxFrameWidth = 1200, minFrameWidth = 320 }: Props = $props();
|
||||
let {
|
||||
breakpoints = [330, 510, 660, 930, 1200],
|
||||
maxFrameWidth = 1200,
|
||||
minFrameWidth = 320,
|
||||
}: Props = $props();
|
||||
|
||||
let container = $state();
|
||||
|
||||
|
|
@ -94,25 +98,25 @@
|
|||
onmousemove={move}
|
||||
onmouseup={end}
|
||||
onkeydown={handleKeyDown}
|
||||
bind:innerWidth="{windowInnerWidth}"
|
||||
bind:innerWidth={windowInnerWidth}
|
||||
/>
|
||||
|
||||
<div id="resizer">
|
||||
<div class="slider">
|
||||
<div class="label" style="{`opacity: ${sliding || isFocused ? 1 : 0};`}">
|
||||
<div class="label" style={`opacity: ${sliding || isFocused ? 1 : 0};`}>
|
||||
{pixelLabel || $width}px
|
||||
</div>
|
||||
<button
|
||||
class="icon left"
|
||||
disabled="{$width === minWidth}"
|
||||
disabled={$width === minWidth}
|
||||
onclick={decrement}
|
||||
onfocus={onFocus}
|
||||
onmouseover={onFocus}
|
||||
onmouseleave={onBlur}
|
||||
>
|
||||
<Fa icon="{faMobileAlt}" fw />
|
||||
<Fa icon={faMobileAlt} fw />
|
||||
</button>
|
||||
<div class="slider-container" bind:this="{container}">
|
||||
<div class="slider-container" bind:this={container}>
|
||||
<div class="track"></div>
|
||||
<div
|
||||
class="handle"
|
||||
|
|
@ -126,13 +130,13 @@
|
|||
</div>
|
||||
<button
|
||||
class="icon right"
|
||||
disabled="{$width === maxWidth}"
|
||||
disabled={$width === maxWidth}
|
||||
onclick={increment}
|
||||
onfocus={onFocus}
|
||||
onmouseover={onFocus}
|
||||
onmouseleave={onBlur}
|
||||
>
|
||||
<Fa icon="{faDesktop}" fw />
|
||||
<Fa icon={faDesktop} fw />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -158,55 +158,55 @@
|
|||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:click="{({ target }) => {
|
||||
on:click={({ target }) => {
|
||||
if (!hideDropdown && !comboboxRef?.contains(target)) {
|
||||
close();
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
data-svelte-typeahead
|
||||
bind:this="{comboboxRef}"
|
||||
bind:this={comboboxRef}
|
||||
role="combobox"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="{id}-listbox"
|
||||
class:dropdown="{results.length > 0}"
|
||||
class:dropdown={results.length > 0}
|
||||
aria-controls="{id}-listbox"
|
||||
aria-expanded="{showResults ||
|
||||
(isFocused && value.length > 0 && results.length === 0)}"
|
||||
aria-expanded={showResults ||
|
||||
(isFocused && value.length > 0 && results.length === 0)}
|
||||
id="{id}-typeahead"
|
||||
>
|
||||
<Search
|
||||
{id}
|
||||
removeFormAriaAttributes="{true}"
|
||||
removeFormAriaAttributes={true}
|
||||
{...$$restProps}
|
||||
bind:ref="{searchRef}"
|
||||
bind:ref={searchRef}
|
||||
aria-autocomplete="list"
|
||||
aria-controls="{id}-listbox"
|
||||
aria-labelledby="{id}-label"
|
||||
aria-activedescendant="{(
|
||||
aria-activedescendant={(
|
||||
selectedIndex >= 0 && !hideDropdown && results.length > 0
|
||||
) ?
|
||||
`${id}-result-${selectedIndex}`
|
||||
: null}"
|
||||
: null}
|
||||
bind:value
|
||||
on:type
|
||||
on:input
|
||||
on:change
|
||||
on:focus
|
||||
on:focus="{() => {
|
||||
on:focus={() => {
|
||||
open();
|
||||
if (showDropdownOnFocus) {
|
||||
showResults = true;
|
||||
isFocused = true;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
on:clear
|
||||
on:clear="{open}"
|
||||
on:clear={open}
|
||||
on:blur
|
||||
on:keydown
|
||||
on:keydown="{(e) => {
|
||||
on:keydown={(e) => {
|
||||
if (results.length === 0) return;
|
||||
|
||||
switch (e.key) {
|
||||
|
|
@ -228,10 +228,10 @@
|
|||
close();
|
||||
break;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
<ul
|
||||
class:svelte-typeahead-list="{true}"
|
||||
class:svelte-typeahead-list={true}
|
||||
role="listbox"
|
||||
aria-labelledby="{id}-label"
|
||||
id="{id}-listbox"
|
||||
|
|
@ -241,24 +241,24 @@
|
|||
<li
|
||||
role="option"
|
||||
id="{id}-result-{index}"
|
||||
class:selected="{selectedIndex === index}"
|
||||
class:disabled="{result.disabled}"
|
||||
aria-selected="{selectedIndex === index}"
|
||||
on:click="{() => {
|
||||
class:selected={selectedIndex === index}
|
||||
class:disabled={result.disabled}
|
||||
aria-selected={selectedIndex === index}
|
||||
on:click={() => {
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
select();
|
||||
}}"
|
||||
on:keyup="{(e) => {
|
||||
}}
|
||||
on:keyup={(e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
select();
|
||||
}}"
|
||||
on:mouseenter="{() => {
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<slot {result} {index} {value}>
|
||||
{@html result.string}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<script lang="ts">
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Whether to wrap the graphic with an aria hidden tag.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { hidden = false, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if hidden}
|
||||
<div aria-hidden="true">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
215
src/components/GraphicBlock/GraphicBlock.mdx
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as GraphicBlockStories from './GraphicBlock.stories.svelte';
|
||||
|
||||
<Meta of={GraphicBlockStories} />
|
||||
|
||||
# GraphicBlock
|
||||
|
||||
The `GraphicBlock` component is a special derivative of the [Block](?path=/docs/components-page-layout-block--docs) component that wraps around your graphic. It also adds a title, description, notes and other text elements.
|
||||
|
||||
Many other Reuters Graphics components use `GraphicBlock` to wrap graphics with styled text.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { GraphicBlock } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<GraphicBlock
|
||||
title="Title for my chart"
|
||||
description="Some description for your chart."
|
||||
notes={`Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)`}
|
||||
>
|
||||
<!-- Your chart goes here -->
|
||||
<div id="my-chart" />
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
||||
<Canvas of={GraphicBlockStories.Demo} />
|
||||
|
||||
## Using with ai2svelte and ArchieML docs
|
||||
|
||||
The `GraphicBlock` component is built to handle [ai2svelte](https://github.com/reuters-graphics/ai2svelte) graphics in Graphics Kit.
|
||||
|
||||
You'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# ArchieML doc
|
||||
[blocks]
|
||||
type: ai-graphic
|
||||
width: normal
|
||||
chart: AiMap # IMPORTANT: This must match the name of the ai2svelte chart you import in App.svelte
|
||||
title: Earthquake in Haiti
|
||||
description: The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021.
|
||||
notes: \Note: A shakemap represents the ground shaking produced by an earthquake.
|
||||
|
||||
\Source: USGIS
|
||||
:end
|
||||
altText: A map that shows the shake intensity of the earthquake, which was worst in central Haiti.
|
||||
:end
|
||||
[]
|
||||
```
|
||||
|
||||
... which you'll parse out of a ArchieML block object before passing to the `GraphicBlock` component.
|
||||
|
||||
To pass your ai2svelte graphic into `GraphicBlock` component, import your ai2svelte graphic at the top of `App.svelte` and add it to the `aiCharts` object.
|
||||
|
||||
> **Important❗:** Make sure that the value for `chart` in the ArchieML doc matches the name of the ai2svelte file imported in `App.svelte`.
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
// IMPORTANT: The name of your ai2svelte chart must match `chart` in your ArchieML doc
|
||||
import AiMap from './ai2svelte/my-map.svelte';
|
||||
// Error handler for missing ai2svelte charts
|
||||
import LogBlock from './components/dev/LogBlock.svelte';
|
||||
|
||||
// If using with the Graphics Kit
|
||||
import { assets } from '$app/paths';
|
||||
|
||||
// A built-in helper function in Graphis Kit for validating container width
|
||||
import { containerWidth } from '$utils/propValidators';
|
||||
|
||||
// Add your imported ai2svelte charts to this object
|
||||
const aiCharts = {
|
||||
AiMap,
|
||||
// Other ai2svelte graphics...
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Loop through ArchieML blocks -->
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'ai-graphic'}
|
||||
{#if !aiCharts[block.chart]}
|
||||
<!-- Error message for when the ai2svelte chart is missing -->
|
||||
<LogBlock message={`Unable to find "${block.chart}" in aiCharts`} />
|
||||
{:else}
|
||||
<!-- Get the ai2svelte graphic specified by `chart` in ArchieML -->
|
||||
{@const AiChart = aiCharts[block.chart]}
|
||||
<GraphicBlock
|
||||
id={block.chart}
|
||||
width={containerWidth(block.width)}
|
||||
title={block.title}
|
||||
description={block.description}
|
||||
notes={block.notes}
|
||||
ariaDescription={block.altText}
|
||||
>
|
||||
<!-- In Graphics Kit, pass the `assetsPath` prop -->
|
||||
<AiChart assetsPath={assets || '/'} />
|
||||
</GraphicBlock>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
<Canvas of={GraphicBlockStories.Ai2SvelteAndArchieML} />
|
||||
|
||||
## Custom text
|
||||
|
||||
You can override the default styles for title and notes by making your own custom elements and passing them as `title` and `notes` [snippets](https://svelte.dev/docs/svelte/snippet) instead of as strings:
|
||||
|
||||
```svelte
|
||||
<GraphicBlock>
|
||||
<!-- Custom title snippet -->
|
||||
{#snippet title()}
|
||||
<h5>My smaller title</h5>
|
||||
{/snippet}
|
||||
|
||||
<!-- Your graphic -->
|
||||
<div id="my-chart"></div>
|
||||
|
||||
<!-- Custom notes snippet -->
|
||||
{#snippet notes()}
|
||||
<aside>
|
||||
<p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p>
|
||||
</aside>
|
||||
{/snippet}
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
||||
<Canvas of={GraphicBlockStories.CustomText} />
|
||||
|
||||
## ARIA descriptions
|
||||
|
||||
If the text in your chart isn't easily read by screen readers — for example, a map with annotations that wouldn't make sense without the visual — add an `ariaDescription` that describes the chart.
|
||||
|
||||
The `ariaDescription` string will be processed as markdown, so you can add multiple paragraphs, links, headers, etc. in markdown.
|
||||
|
||||
> **Note:** When you set an `ariaDescription`, your graphic will be automatically wrapped in a div with [aria-hidden="true"](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden), which tells screen readers to read the hidden ARIA description and skip the text in the graphic.
|
||||
|
||||
```svelte
|
||||
<GraphicBlock
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
ariaDescription="A map showing the shake intensity produced by the earthquake."
|
||||
>
|
||||
<!-- In Graphics Kit, pass the `assetsPath` prop -->
|
||||
<AiChart assetsPath={assets || '/'} />
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
||||
<Canvas of={GraphicBlockStories.AriaDescription} />
|
||||
|
||||
## Custom ARIA descriptions
|
||||
|
||||
Sometimes, instead of a simple sentence, we want to provide a data table or something more complex as an ARIA description. To do this, pass the custom elements as an `ariaDescription` [snippet](https://svelte.dev/docs/svelte/snippet) instead of as a string, as in the [example above](?path=/docs/components-graphics-graphicblock--docs#aria-descriptions).
|
||||
|
||||
[Read this](https://accessibility.psu.edu/images/charts/) for more information on using screen reader data tables for charts.
|
||||
|
||||
> **Note:** The `customAria` snippet will override the `ariaDescription` and will also hide the text in your graphic from screen readers.
|
||||
|
||||
```svelte
|
||||
<GraphicBlock
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
>
|
||||
<!-- In Graphics Kit, pass the `assetsPath` prop -->
|
||||
<AiChart assetsPath={assets || '/'} />
|
||||
|
||||
<!-- Custom ARIA description snippet -->
|
||||
{#snippet ariaDescription()}
|
||||
<p>
|
||||
A shakemap shows the intensity of the 7.2-magnitude earthquake that struck
|
||||
Haiti at 8:29 a.m. EST, Aug. 14, 2021.
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<th>Felt shake strength</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Les Cayes</td>
|
||||
<td>Very strong</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jeremie</td>
|
||||
<td>Strong</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/snippet}
|
||||
</GraphicBlock>
|
||||
|
||||
<!-- Optionally, style the visually hidden table nicely for sighted readers who use screen readers -->
|
||||
<style lang="scss">
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
<Canvas of={GraphicBlockStories.CustomAriaDescription} />
|
||||
|
|
@ -1,23 +1,10 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import archieMLDocs from './stories/docs/archieML.md?raw';
|
||||
// @ts-ignore raw
|
||||
import customTextDocs from './stories/docs/customText.md?raw';
|
||||
// @ts-ignore raw
|
||||
import ai2svelteDocs from './stories/docs/ai2svelte.md?raw';
|
||||
// @ts-ignore raw
|
||||
import ariaDocs from './stories/docs/aria.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import GraphicBlock from './GraphicBlock.svelte';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Graphics/GraphicBlock',
|
||||
component: GraphicBlock,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
|
|
@ -28,43 +15,28 @@
|
|||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
import AiMap from './stories/ai2svelte/ai-chart.svelte';
|
||||
|
||||
// @ts-ignore img
|
||||
import PlaceholderImg from './stories/placeholder.png';
|
||||
import AiMap from './demo/ai2svelte/ai-chart.svelte';
|
||||
import PlaceholderImg from './demo/placeholder.png';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<GraphicBlock {...args}>
|
||||
<div class="demo-graphic">
|
||||
<img src="{PlaceholderImg}" alt="placeholder" />
|
||||
</div>
|
||||
</GraphicBlock>
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
width: 'normal',
|
||||
title: 'Bacon ipsum dolor amet t-bone',
|
||||
description:
|
||||
'Pork loin t-bone jowl prosciutto, short loin flank kevin tri-tip cupim pig pork. Meatloaf tri-tip frankfurter short ribs, cupim brisket bresaola chislic tail jerky burgdoggen pancetta.',
|
||||
notes:
|
||||
'Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)',
|
||||
}}"
|
||||
/>
|
||||
|
||||
<Story name="ArchieML" {...withStoryDocs(archieMLDocs)}>
|
||||
<Story name="Demo">
|
||||
<GraphicBlock
|
||||
title="Title for my chart"
|
||||
description="Some description for your chart."
|
||||
notes={`Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)`}
|
||||
>
|
||||
<div id="my-chart">
|
||||
<img src={PlaceholderImg} alt="placeholder" />
|
||||
</div>
|
||||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<Story name="Ai2svelte and ArchieML" exportName="Ai2SvelteAndArchieML">
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
|
|
@ -73,38 +45,26 @@
|
|||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<Story name="Custom text" {...withStoryDocs(customTextDocs)}>
|
||||
<GraphicBlock width="normal">
|
||||
{#snippet title()}
|
||||
<div >
|
||||
<h5>My smaller title</h5>
|
||||
</div>
|
||||
{/snippet}
|
||||
<Story name="Custom text" exportName="CustomText">
|
||||
<GraphicBlock>
|
||||
<div class="demo-graphic">
|
||||
<img src="{PlaceholderImg}" alt="placeholder" />
|
||||
<img src={PlaceholderImg} alt="placeholder" />
|
||||
</div>
|
||||
|
||||
{#snippet title()}
|
||||
<h5>My smaller title</h5>
|
||||
{/snippet}
|
||||
|
||||
{#snippet notes()}
|
||||
<aside >
|
||||
<aside>
|
||||
<p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p>
|
||||
</aside>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<Story name="Ai2svelte" {...withStoryDocs(ai2svelteDocs)}>
|
||||
<Story name="AREA description" exportName="AriaDescription">
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
>
|
||||
<AiMap />
|
||||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<Story name="ARIA" {...withStoryDocs(ariaDocs)}>
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
|
|
@ -114,6 +74,38 @@
|
|||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<Story name="Custom AREA description" exportName="CustomAriaDescription">
|
||||
<GraphicBlock
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
>
|
||||
<AiMap />
|
||||
{#snippet ariaDescription()}
|
||||
<p>
|
||||
A shakemap shows the intensity of the 7.2-magnitude earthquake that
|
||||
struck Haiti at 8:29 a.m. EST, Aug. 14, 2021.
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<th>Felt shake strength</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Les Cayes</td>
|
||||
<td>Very strong</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jeremie</td>
|
||||
<td>Strong</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/snippet}
|
||||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
<style lang="scss">
|
||||
div.demo-graphic {
|
||||
height: 400px;
|
||||
|
|
@ -125,4 +117,18 @@
|
|||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
// Style the table nicely
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,145 +1,148 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `GraphicBlock` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-graphics-graphicblock--docs) -->
|
||||
<script lang="ts">
|
||||
// Types
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
/**
|
||||
* Width of the component within the text well.
|
||||
* @type {string}
|
||||
*/
|
||||
export let width: ContainerWidth = 'normal';
|
||||
|
||||
/**
|
||||
* Add an id to the block tag to target it with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
export let id: string = '';
|
||||
/**
|
||||
* Add extra classes to the block tag to target it with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/** Snap block to column widths, rather than fluidly resizing them. */
|
||||
export let snap: boolean = false;
|
||||
|
||||
/**
|
||||
* ARIA [role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) for the block
|
||||
* @type {string}
|
||||
*/
|
||||
export let role: string | null = null;
|
||||
|
||||
/**
|
||||
* Notes to the graphic, passed in as a markdown string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let notes: string | null = null;
|
||||
|
||||
/**
|
||||
* Set a different width for the text within the text well, for example,
|
||||
* "normal" to keep the title, description and notes inline with the rest
|
||||
* of the text well. Can't ever be wider than `width`.
|
||||
* @type {string}
|
||||
*/
|
||||
export let textWidth: ContainerWidth | null = 'normal';
|
||||
|
||||
/**
|
||||
* Title of the graphic
|
||||
* @type {string}
|
||||
*/
|
||||
export let title: string | null = null;
|
||||
|
||||
/**
|
||||
* Description of the graphic, passed in as a markdown string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let description: string | null = null;
|
||||
|
||||
/**
|
||||
* ARIA [label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) for the block
|
||||
* @type {string}
|
||||
*/
|
||||
export let ariaLabel: string = 'chart';
|
||||
/**
|
||||
* ARIA description, passed in as a markdown string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let ariaDescription: string | null = null;
|
||||
|
||||
import AriaHidden from './AriaHidden.svelte';
|
||||
import TextBlock from './TextBlock.svelte';
|
||||
// Components
|
||||
import AriaHidden from './components/AriaHidden.svelte';
|
||||
import TextBlock from './components/TextBlock.svelte';
|
||||
import Block from '../Block/Block.svelte';
|
||||
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Content to place inside `GraphicBlock` */
|
||||
children: Snippet;
|
||||
/**
|
||||
* Add an id to the block tag to target it with custom CSS.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Add classes to the block tag to target it with custom CSS.
|
||||
*/
|
||||
class?: string;
|
||||
/** Snap block to column widths, rather than fluidly resizing them. */
|
||||
snap?: boolean;
|
||||
/**
|
||||
* ARIA [role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) for the block
|
||||
*/
|
||||
role?: string;
|
||||
/**
|
||||
* Notes to the graphic, passed in as a markdown string OR as a custom snippet.
|
||||
*/
|
||||
notes?: string | Snippet;
|
||||
/**
|
||||
* Width of the component within the text well.
|
||||
*/
|
||||
width?: ContainerWidth;
|
||||
/**
|
||||
* Set a different width for the text within the text well, for example, "normal" to keep the title, description and notes inline with the rest of the text well. Can't ever be wider than `width`.
|
||||
*/
|
||||
textWidth?: ContainerWidth;
|
||||
/**
|
||||
* Title of the graphic as a string or a custom snippet.
|
||||
*/
|
||||
title?: string | Snippet;
|
||||
/**
|
||||
* Description of the graphic, passed in as a markdown string.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* ARIA [label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) for the block
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
/**
|
||||
* ARIA description, passed in as a markdown string OR as a custom snippet.
|
||||
*/
|
||||
ariaDescription?: string | Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
id = '',
|
||||
class: cls = '',
|
||||
snap = false,
|
||||
role,
|
||||
notes,
|
||||
width = 'normal',
|
||||
textWidth = 'normal',
|
||||
title,
|
||||
description,
|
||||
ariaLabel = 'chart',
|
||||
ariaDescription,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<Block {id} {snap} {role} {width} {ariaLabel} class="graphic fmy-6 {cls}">
|
||||
{#if $$slots.title}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<!-- Custom title content -->
|
||||
<slot name="title" />
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if title}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<h3>{title}</h3>
|
||||
{#if description}
|
||||
<Markdown source="{description}" />
|
||||
<div class="container">
|
||||
<Block {id} {snap} {role} {width} {ariaLabel} class="graphic fmy-6 {cls}">
|
||||
<!-- Check if `title` is a snippet -->
|
||||
{#if typeof title === 'string'}
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<h3>{title}</h3>
|
||||
{#if description}
|
||||
<Markdown source={description} />
|
||||
{/if}
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if title}
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<!-- Custom title snippet -->
|
||||
{@render title()}
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
<AriaHidden hidden={!!ariaDescription}>
|
||||
<!-- Graphic content -->
|
||||
{@render children()}
|
||||
</AriaHidden>
|
||||
{#if ariaDescription}
|
||||
<div class="visually-hidden">
|
||||
{#if typeof ariaDescription === 'string'}
|
||||
<Markdown source={ariaDescription} />
|
||||
{:else}
|
||||
<!-- Custom ARIA snippet -->
|
||||
{@render ariaDescription()}
|
||||
{/if}
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
<AriaHidden hidden="{!!$$slots.aria || !!ariaDescription}">
|
||||
<!-- Graphic content -->
|
||||
<slot />
|
||||
</AriaHidden>
|
||||
{#if $$slots.aria || ariaDescription}
|
||||
<div class="visually-hidden">
|
||||
{#if $$slots.aria}
|
||||
<!-- Custom ARIA markup -->
|
||||
<slot name="aria" />
|
||||
{:else}
|
||||
<Markdown source="{ariaDescription}" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.notes}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<!-- Custom notes content -->
|
||||
<slot name="notes" />
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if notes}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<aside>
|
||||
<Markdown source="{notes}" />
|
||||
</aside>
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
{#if typeof notes === 'string'}
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<aside>
|
||||
<Markdown source={notes} />
|
||||
</aside>
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if notes}
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<!-- Custom notes content -->
|
||||
{@render notes()}
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore css-unused-selector -->
|
||||
<style lang="scss" global>
|
||||
<style lang="scss">
|
||||
@use '../../scss/mixins' as mixins;
|
||||
|
||||
.article-block.graphic {
|
||||
div.container {
|
||||
display: contents;
|
||||
|
||||
// Dek
|
||||
p {
|
||||
:global(.article-block.graphic p) {
|
||||
@include mixins.body-note;
|
||||
@include mixins.font-light;
|
||||
}
|
||||
|
||||
// Caption and Sources
|
||||
aside {
|
||||
p {
|
||||
@include mixins.body-caption;
|
||||
@include mixins.fmy-1;
|
||||
}
|
||||
:global(.article-block.graphic aside p) {
|
||||
@include mixins.body-caption;
|
||||
@include mixins.fmy-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
interface Props {
|
||||
/** Width of the component within the text well. */
|
||||
width?: ContainerWidth | null;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { width = null, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if width}
|
||||
<Block {width} class="notes fmy-0">
|
||||
{@render children?.()}
|
||||
</Block>
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
22
src/components/GraphicBlock/components/AriaHidden.svelte
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Whether to wrap the graphic with an aria hidden tag.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/** Content to put inside `AriaHidden`*/
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { hidden = false, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if hidden}
|
||||
<div aria-hidden="true">
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
{@render children()}
|
||||
{/if}
|
||||
23
src/components/GraphicBlock/components/TextBlock.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { ContainerWidth } from '../../@types/global';
|
||||
|
||||
import Block from '../../Block/Block.svelte';
|
||||
|
||||
interface Props {
|
||||
/** Width of the component within the text well. */
|
||||
width?: ContainerWidth;
|
||||
/** Content to put inside `TextBlock`*/
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { width, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if width}
|
||||
<Block {width} class="notes fmy-0">
|
||||
{@render children()}
|
||||
</Block>
|
||||
{:else}
|
||||
{@render children()}
|
||||
{/if}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
// For demo purposes only, hard-wiring img paths from Vite
|
||||
// @ts-ignore img
|
||||
import chartXs from '../imgs/ai-chart-xs.png';
|
||||
|
|
@ -18,7 +17,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2021-09-29 12:37 -->
|
||||
|
||||
<div id="g-_ai-chart-box" bind:clientWidth="{width}">
|
||||
<div id="g-_ai-chart-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-_ai-chart-xs" class="g-artboard" style="">
|
||||
|
|
@ -27,7 +26,7 @@
|
|||
id="g-_ai-chart-xs-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -160,7 +159,7 @@
|
|||
id="g-_ai-chart-sm-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -293,7 +292,7 @@
|
|||
id="g-_ai-chart-md-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
Before Width: | Height: | Size: 618 KiB After Width: | Height: | Size: 618 KiB |
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
|
@ -1,19 +0,0 @@
|
|||
A more detailed example of using `GraphicBlock` with components created by [ai2svelte](https://github.com/reuters-graphics/ai2svelte).
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { GraphicBlock } from '@reuters-graphics/graphics-components';
|
||||
import MyAiMap from './ai2svelte/my-map.svelte';
|
||||
import { assets } from '$app/paths'; // If using with the Graphics Kit
|
||||
</script>
|
||||
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
ariaDescription="A map showing shake intensity of the quake."
|
||||
>
|
||||
<MyAiMap assetsPath="{assets}" />
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
For Graphics Kit users, the `GraphicBlock` component is built-in to handle [ai2svelte](https://github.com/reuters-graphics/ai2svelte) graphics.
|
||||
|
||||
First, import your ai2svelte graphic in `App.svelte` and add it to the `aiCharts` object;
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
<script>
|
||||
// Other stuff...
|
||||
|
||||
import AiMap from './ai2svelte/my-map.svelte';
|
||||
|
||||
const aiCharts = {
|
||||
// Other charts...
|
||||
AiMap,
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Then add the following structure to your ArchieML doc, taking care that the name of your chart in the `aiCharts` object matches the name of your `chart`:
|
||||
|
||||
```yaml
|
||||
[blocks]
|
||||
# ...
|
||||
|
||||
type: ai-graphic
|
||||
chart: AiMap
|
||||
width: normal
|
||||
textWidth: normal
|
||||
title: Earthquake in Haiti
|
||||
description: The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021.
|
||||
notes: \Note: A shakemap represents the ground shaking produced by an earthquake.
|
||||
|
||||
\Source: USGIS
|
||||
:end
|
||||
altText: A map that shows the shake intensity of the earthquake, which was worst in central Haiti.
|
||||
:end
|
||||
|
||||
# ...
|
||||
[]
|
||||
```
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
If your chart isn't easily read by screen readers — for example, it has annotations that when read without the visual won't make sense — you should add an `ariaDescription` that better describes the chart.
|
||||
|
||||
The `ariaDescription` string will be processed as markdown, so you can add multiple paragraphs, links, headers or whatever else you need in markdown.
|
||||
|
||||
> **Note:** When you set an `ariaDescription`, your graphic will be automatically wrapped in a div that tells screen readers not to read the text in the graphic, but instead read the hidden ARIA description.
|
||||
|
||||
```svelte
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
ariaDescription="A map showing the shake intensity produced by the earthquake."
|
||||
>
|
||||
<MyAiMap assetsPath="{assets}" />
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
||||
Sometimes, instead of a simple ARIA description for graphics, we want to also provide a data table or something else that requires more complex markup.
|
||||
|
||||
You can add custom markup for screen readers only by using the `aria` named slot.
|
||||
|
||||
> **Note:** The `aria` slot will override the `ariaDescription` and will also hide the text in your graphic from screen readers.
|
||||
|
||||
```svelte
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Earthquake in Haiti"
|
||||
description="The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021."
|
||||
notes="Note: A shakemap represents the ground shaking produced by an earthquake."
|
||||
>
|
||||
<MyAiMap basePath="{assets}" />
|
||||
<div slot="aria">
|
||||
<p>
|
||||
A shakemap shows the intensity of the 7.2-magnitude earthquake that struck
|
||||
Haiti at 8:29 a.m. EST, Aug. 14, 2021.
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<th>Felt shake strength</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Les Cayes</td>
|
||||
<td>Very strong</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jeremie</td>
|
||||
<td>Strong</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
||||
[Read this](https://accessibility.psu.edu/images/charts/) for more information on using screen reader data tables for charts.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
The `GraphicBlock` component is a special derivative of the [`Block`](./?path=/docs/layout-block) component that also handles text elements around a graphic.
|
||||
|
||||
Many other components use this one to wrap graphics with styled text. When you use it, you'll also wrap your chart elements or component with it like this:
|
||||
|
||||
---
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { GraphicBlock } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
title="Title for my chart"
|
||||
description="Pork loin t-bone jowl prosciutto, short loin flank kevin tri-tip cupim pig pork. Meatloaf tri-tip frankfurter short ribs, cupim brisket bresaola chislic tail jerky burgdoggen pancetta."
|
||||
notes="Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)"
|
||||
>
|
||||
<div id="my-chart"></div>
|
||||
</GraphicBlock>
|
||||
```
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
You can override the markup used to generate the chart text elements by using the `title` and/or `notes` named slots like this:
|
||||
|
||||
```svelte
|
||||
<GraphicBlock
|
||||
width="normal"
|
||||
notes="{'Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)'}"
|
||||
>
|
||||
<h5 slot="title">My smaller title</h5>
|
||||
<div id="my-chart"></div>
|
||||
<aside slot="notes">
|
||||
<p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p>
|
||||
</aside>
|
||||
</GraphicBlock>
|
||||
```
|
||||
152
src/components/Headline/Headline.mdx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as HeadlineStories from './Headline.stories.svelte';
|
||||
|
||||
<Meta of={HeadlineStories} />
|
||||
|
||||
# Headline
|
||||
|
||||
The `Headline` component creates headlines in the legacy Reuters Graphics style, with the text centred on the page.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
hed="Reuters Graphics Interactive"
|
||||
dek="The beginning of a beautiful page"
|
||||
section="World News"
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={HeadlineStories.Demo} />
|
||||
|
||||
## With bylines and dateline
|
||||
|
||||
Optionally, you can add authors and a publish time to the headline, which the `Headline` component internally renders with the [Byline](./?path=/docs/components-text-elements-byline--docs) component.
|
||||
|
||||
> **Note**: Since `Headline` uses `Byline`, you can customise the author page hyperlink and bylines with the `getAuthorPage`, `byline`, `published` and `updated` props.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
authors={['Jane Doe']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
getAuthorPage={(author: string) => {
|
||||
return `mailto:${author.replace(' ', '')}@example.com`;
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={HeadlineStories.Byline} />
|
||||
|
||||
## Custom hed and dek
|
||||
|
||||
Use the `hed` and/or `dek` [snippets](https://svelte.dev/docs/svelte/snippet) to override those elements with custom elements.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline width="wide">
|
||||
<!-- Custom hed snippet -->
|
||||
{#snippet hed()}
|
||||
<h1 class="custom-hed">
|
||||
<span class="small block text-base">The secret to</span>
|
||||
“The Nutcracker's”
|
||||
<span class="small block text-base fpt-1">success</span>
|
||||
</h1>
|
||||
{/snippet}
|
||||
|
||||
<!-- Custom dek snippet -->
|
||||
{#snippet dek()}
|
||||
<p class="custom-dek !fmt-3">
|
||||
How “The Nutcracker” ballet became an<span
|
||||
class="font-medium mx-1 px-1.5 py-1">American holday staple</span
|
||||
>and a financial pillar of ballet companies across the country
|
||||
</p>
|
||||
{/snippet}
|
||||
</Headline>
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style lang="scss">
|
||||
.custom-hed {
|
||||
line-height: 0.9;
|
||||
}
|
||||
.custom-dek {
|
||||
span {
|
||||
background-color: #fde68a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
<Canvas of={HeadlineStories.CustomHedDek} />
|
||||
|
||||
## With crown image
|
||||
|
||||
To add a crown image, use the `crown` [snippet](https://svelte.dev/docs/svelte/snippet).
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
class="!fmt-3"
|
||||
hed="Europa"
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<!-- Add a crown -->
|
||||
{#snippet crown()}
|
||||
<img
|
||||
src={crownImgSrc}
|
||||
width="100"
|
||||
class="mx-auto mb-0"
|
||||
alt="Illustration of Europe"
|
||||
/>
|
||||
{/snippet}
|
||||
</Headline>
|
||||
```
|
||||
|
||||
<Canvas of={HeadlineStories.CrownImage} />
|
||||
|
||||
## With crown graphic
|
||||
|
||||
Add a full graphic or any other component in the crown.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths'; // If in Graphis Kit
|
||||
|
||||
import Map from './ai2svelte/graphic.svelte'; // Import the crown graphic component
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
width="wider"
|
||||
class="!fmt-1"
|
||||
hed={'Unfriendly skies'}
|
||||
dek={'How Russia’s invasion of Ukraine is redrawing air routes'}
|
||||
section={'Ukraine Crisis'}
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2022-03-04').toISOString()}
|
||||
>
|
||||
<!-- Add a crown graphic -->
|
||||
{#snippet crown()}
|
||||
<!-- Pass `assetsPath` if in Graphics Kit -->
|
||||
<Map assetsPath={assets || '/'} />
|
||||
{/snippet}
|
||||
</Headline>
|
||||
```
|
||||
|
||||
<Canvas of={HeadlineStories.CrownGraphic} />
|
||||
|
|
@ -1,28 +1,10 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import withBylineDocs from './stories/docs/withByline.md?raw';
|
||||
// @ts-ignore raw
|
||||
import withDekDocs from './stories/docs/withDek.md?raw';
|
||||
// @ts-ignore raw
|
||||
import customHedDocs from './stories/docs/customHed.md?raw';
|
||||
// @ts-ignore raw
|
||||
import withCrownImgDocs from './stories/docs/withCrownImage.md?raw';
|
||||
// @ts-ignore raw
|
||||
import withCrownGraphicDocs from './stories/docs/withCrownGraphic.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Headline from './Headline.svelte';
|
||||
|
||||
import {
|
||||
withComponentDocs,
|
||||
withStoryDocs,
|
||||
} from '$lib/docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/Headline',
|
||||
component: Headline,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
hedSize: {
|
||||
control: 'select',
|
||||
|
|
@ -33,115 +15,97 @@
|
|||
options: ['normal', 'wide', 'wider', 'widest'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
// @ts-ignore img
|
||||
import crownImgSrc from './stories/crown.png';
|
||||
import Map from './stories/graphic.svelte';
|
||||
import crownImgSrc from './demo/crown.png';
|
||||
import Map from './demo/graphic.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<Headline {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
section: 'World News',
|
||||
name="Demo"
|
||||
args={{
|
||||
hed: 'Reuters Graphics interactive',
|
||||
hedSize: 'normal',
|
||||
dek: '',
|
||||
authors: [],
|
||||
}}"
|
||||
dek: 'The beginning of a beautiful page',
|
||||
section: 'World News',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="With dek" {...withStoryDocs(withDekDocs)}>
|
||||
<Story name="With byline and dateline" exportName="Byline">
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
authors={['Jane Doe']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
getAuthorPage={(author: string) => {
|
||||
return `mailto:${author.replace(' ', '')}@example.com`;
|
||||
}}
|
||||
/>
|
||||
</Story>
|
||||
|
||||
<Story name="With byline" {...withStoryDocs(withBylineDocs)}>
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Dea Bankova', 'Aditi Bhandari']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
/>
|
||||
</Story>
|
||||
|
||||
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
|
||||
<Story name="Custom hed and dek" exportName="CustomHedDek">
|
||||
<Headline width="wide">
|
||||
{#snippet hed()}
|
||||
<h1 class="custom-hed" >
|
||||
<h1 class="custom-hed">
|
||||
<span class="small block text-base">The secret to</span>
|
||||
“The Nutcracker's”
|
||||
<span class="small block text-base fpt-1">success</span>
|
||||
</h1>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
{#snippet dek()}
|
||||
<p class="custom-dek !fmt-3" >
|
||||
<p class="custom-dek !fmt-3">
|
||||
How “The Nutcracker” ballet became an<span
|
||||
class="font-medium mx-1 px-1.5 py-1">American holday staple</span
|
||||
>and a financial pillar of ballet companies across the country
|
||||
</p>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</Headline>
|
||||
<style lang="scss">
|
||||
.custom-hed {
|
||||
line-height: 0.9;
|
||||
}
|
||||
.custom-dek {
|
||||
span {
|
||||
background-color: #fde68a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</Story>
|
||||
|
||||
<Story name="With crown image" {...withStoryDocs(withCrownImgDocs)}>
|
||||
<Headline class="!fmt-3" publishTime="{new Date('2020-01-01').toISOString()}">
|
||||
<Story name="Crown image" exportName="CrownImage">
|
||||
<Headline
|
||||
class="!fmt-3"
|
||||
hed="Europa"
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<!-- Add a crown -->
|
||||
{#snippet crown()}
|
||||
<img
|
||||
|
||||
src="{crownImgSrc}"
|
||||
<img
|
||||
src={crownImgSrc}
|
||||
width="100"
|
||||
class="mx-auto mb-0"
|
||||
alt="Illustration of Europe"
|
||||
/>
|
||||
{/snippet}
|
||||
<!-- Override the hed with a named slot -->
|
||||
{#snippet hed()}
|
||||
<h1 class="!font-serif !tracking-wide">Europa</h1>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</Headline>
|
||||
</Story>
|
||||
|
||||
<Story name="With crown graphic" {...withStoryDocs(withCrownGraphicDocs)}>
|
||||
<Story name="Crown graphic" exportName="CrownGraphic">
|
||||
<Headline
|
||||
width="wider"
|
||||
class="!fmt-1"
|
||||
hed="{'Unfriendly skies'}"
|
||||
dek="{'How Russia’s invasion of Ukraine is redrawing air routes'}"
|
||||
section="{'Ukraine Crisis'}"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2022-03-04').toISOString()}"
|
||||
hed={'Unfriendly skies'}
|
||||
dek={'How Russia’s invasion of Ukraine is redrawing air routes'}
|
||||
section={'Ukraine Crisis'}
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2022-03-04').toISOString()}
|
||||
>
|
||||
<!-- Add a crown graphic -->
|
||||
{#snippet crown()}
|
||||
<div >
|
||||
<Map />
|
||||
</div>
|
||||
{/snippet}
|
||||
<Map />
|
||||
{/snippet}
|
||||
</Headline>
|
||||
</Story>
|
||||
|
||||
<style lang="scss">
|
||||
.custom-hed {
|
||||
line-height: 0.9;
|
||||
}
|
||||
.custom-dek {
|
||||
span {
|
||||
background-color: #fde68a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,60 +1,76 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `Headline` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-headline--docs) -->
|
||||
<script lang="ts">
|
||||
// Types
|
||||
import type { HeadlineSize } from './../@types/global';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
/**
|
||||
* Headline, parsed as an _inline_ markdown string in an `h1` element.
|
||||
* @type {string}
|
||||
*/
|
||||
export let hed: string = 'Reuters Graphics Interactive';
|
||||
|
||||
/** Add extra classes to the block tag to target it with custom CSS. */
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/**
|
||||
* Headline size
|
||||
* @type {string}
|
||||
*/
|
||||
export let hedSize: HeadlineSize = 'normal';
|
||||
|
||||
/**
|
||||
* Dek, parsed as a markdown string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let dek: string | null = null;
|
||||
/**
|
||||
* Section title
|
||||
* @type {string}
|
||||
*/
|
||||
export let section: string | null = null;
|
||||
/**
|
||||
* Array of author names, which will be slugified to create links to Reuters author pages
|
||||
*/
|
||||
export let authors: string[] = [];
|
||||
/**
|
||||
* Publish time as a datetime string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let publishTime: string = '';
|
||||
/**
|
||||
* Update time as a datetime string.
|
||||
* @type {string}
|
||||
*/
|
||||
export let updateTime: string = '';
|
||||
|
||||
/**
|
||||
* Width of the headline.
|
||||
*/
|
||||
export let width: 'normal' | 'wide' | 'wider' | 'widest' = 'normal';
|
||||
|
||||
// Components
|
||||
import Block from '../Block/Block.svelte';
|
||||
import Byline from '../Byline/Byline.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
|
||||
let hedClass: string;
|
||||
$: {
|
||||
interface Props {
|
||||
/** Headline, parsed as an _inline_ markdown string in an `h1` element OR as a custom snippet. */
|
||||
hed: string | Snippet;
|
||||
|
||||
/** Add extra classes to the block tag to target it with custom CSS. */
|
||||
class?: string;
|
||||
/** Headline size: small, normal, big, bigger, biggest */
|
||||
hedSize?: HeadlineSize;
|
||||
/** Dek, parsed as a markdown string OR as a custom snippet. */
|
||||
dek?: string | Snippet;
|
||||
|
||||
/** Section title */
|
||||
section?: string;
|
||||
/** Array of author names, which will be slugified to create links to Reuters author pages */
|
||||
authors?: string[];
|
||||
|
||||
/** Publish time as a datetime string. */
|
||||
publishTime?: string;
|
||||
/** Update time as a datetime string. */
|
||||
updateTime?: string;
|
||||
/** Width of the headline: normal, wide, wider, widest */
|
||||
width?: 'normal' | 'wide' | 'wider' | 'widest';
|
||||
/**
|
||||
* Custom function that returns an author page URL.
|
||||
*/
|
||||
getAuthorPage?: (author: string) => string;
|
||||
/** Custom crown snippet */
|
||||
crown?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom byline.
|
||||
*/
|
||||
byline?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom published dateline.
|
||||
*/
|
||||
published?: Snippet;
|
||||
/**
|
||||
* Optional snippet for a custom updated dateline.
|
||||
*/
|
||||
updated?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
hed = 'Reuters Graphics Interactive',
|
||||
class: cls = '',
|
||||
hedSize = 'normal',
|
||||
dek,
|
||||
section,
|
||||
authors = [],
|
||||
publishTime = '',
|
||||
updateTime = '',
|
||||
width = 'normal',
|
||||
getAuthorPage,
|
||||
crown,
|
||||
byline,
|
||||
published,
|
||||
updated,
|
||||
}: Props = $props();
|
||||
|
||||
// Set the headline text size class based on the `hedSize` prop
|
||||
let hedClass = $state('text-3xl');
|
||||
$effect(() => {
|
||||
switch (hedSize) {
|
||||
case 'biggest':
|
||||
hedClass = 'text-6xl';
|
||||
|
|
@ -71,16 +87,16 @@
|
|||
default:
|
||||
hedClass = 'text-3xl';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="headline-wrapper" style="display:contents;">
|
||||
<Block {width} class="headline text-center fmt-7 fmb-6 {cls}">
|
||||
<header class="relative">
|
||||
{#if $$slots.crown}
|
||||
{#if crown}
|
||||
<div class="crown-container">
|
||||
<!-- Crown named slot -->
|
||||
<slot name="crown" />
|
||||
<!-- Crown snippet -->
|
||||
{@render crown()}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="title">
|
||||
|
|
@ -91,53 +107,56 @@
|
|||
{section}
|
||||
</p>
|
||||
{/if}
|
||||
{#if $$slots.hed}
|
||||
<!-- Headline named slot -->
|
||||
<slot name="hed" />
|
||||
{:else}
|
||||
<h1 class="{hedClass}">
|
||||
<Markdown source="{hed}" parseInline />
|
||||
{#if typeof hed === 'string'}
|
||||
<h1 class={hedClass}>
|
||||
<Markdown source={hed} parseInline />
|
||||
</h1>
|
||||
{:else if hed}
|
||||
<!-- Headline snippet -->
|
||||
{@render hed()}
|
||||
{/if}
|
||||
{#if $$slots.dek}
|
||||
<!-- Dek named slot-->
|
||||
{#if typeof dek === 'string'}
|
||||
<div class="dek fmx-auto fmb-6">
|
||||
<slot name="dek" />
|
||||
<Markdown source={dek} />
|
||||
</div>
|
||||
{:else if dek}
|
||||
<!-- Dek snippet-->
|
||||
<div class="dek fmx-auto fmb-6">
|
||||
<Markdown source="{dek}" />
|
||||
{@render dek()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $$slots.byline}
|
||||
<!-- Custom byline/dateline -->
|
||||
<slot name="byline" />
|
||||
{:else if authors.length > 0 || publishTime}
|
||||
{#if authors.length > 0 || publishTime}
|
||||
<Byline
|
||||
class="fmy-4"
|
||||
cls="fmy-4"
|
||||
{authors}
|
||||
{publishTime}
|
||||
{updateTime}
|
||||
{getAuthorPage}
|
||||
{published}
|
||||
{updated}
|
||||
align="center"
|
||||
/>
|
||||
{:else if byline}
|
||||
<!-- Custom byline/dateline -->
|
||||
{@render byline()}
|
||||
{/if}
|
||||
</header>
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '../../scss/mixins' as *;
|
||||
@use '../../scss/mixins' as mixins;
|
||||
.headline-wrapper {
|
||||
:global(.dek) {
|
||||
max-width: $column-width-normal;
|
||||
max-width: mixins.$column-width-normal;
|
||||
}
|
||||
:global(.dek p) {
|
||||
@include fmt-0;
|
||||
@include font-note;
|
||||
@include leading-tight;
|
||||
@include font-light;
|
||||
@include fmb-3;
|
||||
@include mixins.fmt-0;
|
||||
@include mixins.font-note;
|
||||
@include mixins.leading-tight;
|
||||
@include mixins.font-light;
|
||||
@include mixins.fmb-3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 519 KiB After Width: | Height: | Size: 519 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
|
@ -14,7 +14,7 @@
|
|||
let width = $state(null);
|
||||
</script>
|
||||
|
||||
<div id="g-graphic-box" bind:clientWidth="{width}">
|
||||
<div id="g-graphic-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-graphic-xs" class="g-artboard" style="">
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<div
|
||||
id="g-graphic-xs-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-3"
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
<div
|
||||
id="g-graphic-sm-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
<div
|
||||
id="g-graphic-md-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
|
||||
<div
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
<div
|
||||
id="g-graphic-lg-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -485,7 +485,7 @@
|
|||
<div
|
||||
id="g-graphic-xl-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
Reuters Graphics headline
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'World News'}"
|
||||
/>
|
||||
```
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
Use the `hed` and/or `dek` named slots to override those elements with completely custom markup.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline width="wide">
|
||||
<h1 class="custom-hed" slot="hed">
|
||||
<span class="small block text-base">The secret to</span>
|
||||
“The Nutcracker's”
|
||||
<span class="small block text-base fpt-1">success</span>
|
||||
</h1>
|
||||
<p class="custom-dek !fmt-3" slot="dek">
|
||||
How “The Nutcracker” ballet became an<span
|
||||
class="font-medium mx-1 px-1.5 py-1">American holday staple</span
|
||||
>and a financial pillar of ballet companies across the country
|
||||
</p>
|
||||
</Headline>
|
||||
|
||||
<style lang="scss">
|
||||
.custom-hed {
|
||||
line-height: 0.9;
|
||||
}
|
||||
.custom-dek {
|
||||
span {
|
||||
background-color: #fde68a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Dea Bankova', 'Aditi Bhandari']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
/>
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
Add a full graphic or any other component in the crown.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
import Map from './ai2svelte/graphic.svelte';
|
||||
import { assets } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
width="wider"
|
||||
class="!fmt-1"
|
||||
hed="{'Unfriendly skies'}"
|
||||
dek="{'How Russia’s invasion of Ukraine is redrawing air routes'}"
|
||||
section="{'Ukraine Crisis'}"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2022-03-04').toISOString()}"
|
||||
>
|
||||
<!-- Add a crown graphic -->
|
||||
<div slot="crown">
|
||||
<Map assetsPath="{assets}" />
|
||||
</div>
|
||||
</Headline>
|
||||
```
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
Add a crown image in the `crown` named slot and override the headline in the `hed` named slot.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
import { assets } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<Headline class="!fmt-3" publishTime="{new Date('2020-01-01').toISOString()}">
|
||||
<!-- Add a crown -->
|
||||
<img
|
||||
slot="crown"
|
||||
src="{crownImgSrc}"
|
||||
width="100"
|
||||
class="mx-auto mb-0"
|
||||
alt="Illustration of Europe"
|
||||
/>
|
||||
<!-- Override the hed with a named slot -->
|
||||
<h1 slot="hed" class="!font-serif !tracking-wide">Europa</h1>
|
||||
</Headline>
|
||||
```
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
```svelte
|
||||
<script>
|
||||
import { Headline } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
/>
|
||||
```
|
||||
|
|
@ -75,14 +75,14 @@
|
|||
|
||||
<Story
|
||||
name="With backdrop photo"
|
||||
args="{{
|
||||
args={{
|
||||
section: 'World News',
|
||||
hed: 'Reuters Graphics Interactive',
|
||||
dek: 'The beginning of a beautiful page',
|
||||
authors: ['Simon Scarr', 'Vijdan Mohammad Kawoosa'],
|
||||
publishTime: new Date('2022-03-04').toISOString(),
|
||||
img: polarImgSrc,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="With transparent header" {...withStoryDocs(transparentHeaderDocs)}>
|
||||
|
|
@ -95,9 +95,9 @@
|
|||
section="World News"
|
||||
hed="Reuters Graphics Interactive"
|
||||
dek="The beginning of a beautiful page"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2022-03-04').toISOString()}"
|
||||
img="{polarImgSrc}"
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2022-03-04').toISOString()}
|
||||
img={polarImgSrc}
|
||||
/>
|
||||
</div>
|
||||
</Story>
|
||||
|
|
@ -108,12 +108,12 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Earthquake devastates Afghanistan'}"
|
||||
hedSize="{'big'}"
|
||||
hed={'Earthquake devastates Afghanistan'}
|
||||
hedSize={'big'}
|
||||
hedWidth="wide"
|
||||
class="custom-hero mb-0"
|
||||
dek=""
|
||||
authors="{[
|
||||
authors={[
|
||||
'Anand Katakam',
|
||||
'Vijdan Mohammad Kawoosa',
|
||||
'Adolfo Arranz',
|
||||
|
|
@ -123,8 +123,8 @@
|
|||
'Jitesh Chowdhury',
|
||||
'Manas Sharma',
|
||||
'Aditi Bhandari',
|
||||
]}"
|
||||
publishTime="{new Date('2022-06-24').toISOString()}"
|
||||
]}
|
||||
publishTime={new Date('2022-06-24').toISOString()}
|
||||
>
|
||||
<div slot="background">
|
||||
<GraphicBlock
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
notes=""
|
||||
ariaDescription="Earthquake impact map"
|
||||
>
|
||||
<svelte:component this="{QuakeMap}" />
|
||||
<svelte:component this={QuakeMap} />
|
||||
</GraphicBlock>
|
||||
</div>
|
||||
</HeroHeadline>
|
||||
|
|
@ -186,16 +186,16 @@
|
|||
hed="The conflict in Ethiopia"
|
||||
hedSize="bigger"
|
||||
hedWidth="wide"
|
||||
authors="{['Aditi Bhandari ', 'David Lewis']}"
|
||||
publishTime="{new Date('2020-12-18').toISOString()}"
|
||||
authors={['Aditi Bhandari ', 'David Lewis']}
|
||||
publishTime={new Date('2020-12-18').toISOString()}
|
||||
>
|
||||
<div slot="background">
|
||||
<Video
|
||||
width="widest"
|
||||
class="my-0"
|
||||
showControls="{false}"
|
||||
showControls={false}
|
||||
preloadVideo="auto"
|
||||
playVideoWhenInView="{false}"
|
||||
playVideoWhenInView={false}
|
||||
src="https://vm.reuters.tv/9c72e/titlef2ac(425954_R21MP41500).mp4"
|
||||
poster="https://www.reuters.com/resizer/vexYmtEuXKmfnsCbfS6jSMVbHms=/1080x0/filters:quality(80)/cloudfront-us-east-2.images.arcpublishing.com/reuters/VKJHKJEENVO4DASDND3VLHPV5Y.jpg"
|
||||
notes="Drone footage from the Village 8 refugee camp in Sudan."
|
||||
|
|
@ -227,13 +227,13 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Buried under the bricks'}"
|
||||
hed={'Buried under the bricks'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How mud-brick housing made the Morocco earthquake so deadly'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Mariano Zafra']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How mud-brick housing made the Morocco earthquake so deadly'}
|
||||
section={'Global news'}
|
||||
authors={['Mariano Zafra']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<FeaturePhoto
|
||||
|
|
@ -253,13 +253,13 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'The plunge from 29,000 feet'}"
|
||||
hed={'The plunge from 29,000 feet'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}
|
||||
section={'Global news'}
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<GraphicBlock
|
||||
|
|
@ -270,7 +270,7 @@
|
|||
notes="Source: Satellite image from Google, Maxar Technologies, CNES/Airbus, Landsat/Copernicus"
|
||||
ariaDescription="Aerial map showing trajectory of crash"
|
||||
>
|
||||
<svelte:component this="{CrashMap}" />
|
||||
<svelte:component this={CrashMap} />
|
||||
</GraphicBlock>
|
||||
</div>
|
||||
</HeroHeadline>
|
||||
|
|
@ -282,21 +282,21 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Devastation in Derna'}"
|
||||
hed={'Devastation in Derna'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Simon Scarr']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}
|
||||
section={'Global news'}
|
||||
authors={['Simon Scarr']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<Video
|
||||
width="widest"
|
||||
class="my-0"
|
||||
showControls="{false}"
|
||||
showControls={false}
|
||||
preloadVideo="auto"
|
||||
playVideoWhenInView="{false}"
|
||||
playVideoWhenInView={false}
|
||||
src="https://www.reuters.com/graphics/LIBYA-STORM/EXPLAINER/klvyzqebzpg/cdn/video/drone.mp4"
|
||||
notes="Drone shots of Derna, Libya. September 14, 2023. REUTERS"
|
||||
ariaDescription="alttext fot video"
|
||||
|
|
@ -308,14 +308,14 @@
|
|||
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
|
||||
<HeroHeadline
|
||||
class="custom-hed"
|
||||
authors="{[
|
||||
authors={[
|
||||
'Prasanta Kumar Dutta',
|
||||
'Dea Bankova',
|
||||
'Aditi Bhandari',
|
||||
'Anurag Rao',
|
||||
]}"
|
||||
publishTime="{new Date('2023-05-11').toISOString()}"
|
||||
img="{eurovisImgSrc}"
|
||||
]}
|
||||
publishTime={new Date('2023-05-11').toISOString()}
|
||||
img={eurovisImgSrc}
|
||||
>
|
||||
<h1 slot="hed">
|
||||
<div class="body-note">A visual guide to</div>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
{#if $$slots.hed}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
{:else}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -144,11 +144,11 @@
|
|||
<!-- Inline hero -->
|
||||
{#if $$slots.inline}
|
||||
<Block width="fluid" class="hero-headline inline-hero">
|
||||
<PaddingReset containerIsFluid="{true}">
|
||||
<PaddingReset containerIsFluid={true}>
|
||||
{#if $$slots.hed}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
{:else}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2022-03-29 17:01 -->
|
||||
|
||||
<div id="g-CRASH_1-box" bind:clientWidth="{width}">
|
||||
<div id="g-CRASH_1-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-CRASH_1-xs" class="g-artboard" style="">
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
id="g-CRASH_1-xs-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
id="g-CRASH_1-sm-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
id="g-CRASH_1-md-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
id="g-CRASH_1-lg-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -255,7 +255,7 @@
|
|||
id="g-CRASH_1-xl-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
id="g-CRASH_1-xl_copy-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXxl});`}"
|
||||
style={`background-image: url(${chartXxl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai5-1"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import chartXl from '././quake-map-top-xl.jpeg';
|
||||
</script>
|
||||
|
||||
<div id="g-quake-map-top-box" bind:clientWidth="{width}">
|
||||
<div id="g-quake-map-top-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-quake-map-top-xs" class="g-artboard" style="">
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-xs-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-sm-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-md-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
|
@ -271,7 +271,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-lg-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-xl-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
|
|||
122
src/components/InfoBox/InfoBox.mdx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as InfoBoxStories from './InfoBox.stories.svelte';
|
||||
|
||||
<Meta of={InfoBoxStories} />
|
||||
|
||||
# InfoBox
|
||||
|
||||
The `InfoBox` component creates a stylised text box that provides additional information that needs to be visually separate from the main content flow, such as methodology, detailed notes about data and extra context.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { InfoBox } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<InfoBox
|
||||
title="About this data"
|
||||
text={'Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day. \n\n Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths.'}
|
||||
notes={'[Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)'}
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={InfoBoxStories.Demo} />
|
||||
|
||||
## Using with ArchieML docs
|
||||
|
||||
With the graphics kit, you'll likely get your text value from an ArchieML doc...
|
||||
|
||||
```yaml
|
||||
# Archie ML doc
|
||||
[blocks]
|
||||
|
||||
type: info-box
|
||||
title: What you need to know about the war
|
||||
text: Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day.
|
||||
|
||||
Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths.
|
||||
:end
|
||||
notes: [Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)
|
||||
[]
|
||||
```
|
||||
|
||||
... which you'll parse out of a ArchieML block object before passing to the `InfoBox` component.
|
||||
|
||||
```svelte
|
||||
<!-- Graphics Kit -->
|
||||
<script>
|
||||
import { InfoBox } from '@reuters-graphics/graphics-components';
|
||||
import content from '$locales/en/content.json';
|
||||
</script>
|
||||
|
||||
# Graphics Kit
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'info-box'}
|
||||
<InfoBox title={block.title} text={block.text} notes={block.notes} />
|
||||
<!-- ... -->
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
<Canvas of={InfoBoxStories.Demo} />
|
||||
|
||||
## Lists
|
||||
|
||||
Use markdown to add lists to `InfoBox`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { InfoBox } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<InfoBox
|
||||
title="What you need to know about the war"
|
||||
text={"- **Food crisis**: [Russia's invasion of Ukraine](#) in late February dramatically worsened the outlook for already inflated global food prices.\n- **Under fire**: Civillian homes destroyed in the conflict and Russia accused of war crimes.\n- **Nordstream sabotage**: A series of clandestine bombings and subsequent underwater gas leaks occurred on the Nord Stream 1 and Nord Stream 2 natural gas pipelines."}
|
||||
notes={'[Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)'}
|
||||
/>
|
||||
```
|
||||
|
||||
<Canvas of={InfoBoxStories.Lists} />
|
||||
## Customisation
|
||||
|
||||
Use [snippets](https://svelte.dev/docs/svelte/snippet) to customise the `InfoBox`, such as adding tables, icons and thumbnail images.
|
||||
|
||||
```svelte
|
||||
<InfoBox title="About this data">
|
||||
<!-- Optional custom header -->
|
||||
{#snippet header()}
|
||||
<h3>Global video game market</h3>
|
||||
{/snippet}
|
||||
<!-- Optional custom body -->
|
||||
{#snippet body()}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Year</th>
|
||||
<th>Market size ($bln)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>2024</td>
|
||||
<td>274.63</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2023</td>
|
||||
<td>281.77</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2022</td>
|
||||
<td>249.55</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/snippet}
|
||||
<!-- Optional custom footer -->
|
||||
{#snippet updated()}
|
||||
<div class="text-xs font-note">Source: Precedence Research</div>
|
||||
{/snippet}
|
||||
</InfoBox>
|
||||
```
|
||||
|
||||
<Canvas of={InfoBoxStories.Customised} />
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import InfoBox from './InfoBox.svelte';
|
||||
import BodyText from '../BodyText/BodyText.svelte';
|
||||
|
||||
import { withComponentDocs } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/InfoBox',
|
||||
component: InfoBox,
|
||||
...withComponentDocs(componentDocs),
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
theme: {
|
||||
control: 'select',
|
||||
|
|
@ -20,38 +17,82 @@
|
|||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
import BodyText from '../BodyText/BodyText.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<InfoBox {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story name="Default">
|
||||
<Story name="Demo">
|
||||
<BodyText
|
||||
text="If you haven't seen Game of Thrones, go watch it right now. If you have then you'll totally get why this Hodor themed lorem ipsum generator is just brilliant."
|
||||
text="Bacon ipsum dolor amet turducken buffalo beef ribs bresaola pancetta ribeye pork belly doner hamburger biltong cupim porchetta chuck ham tenderloin. Turducken bresaola jerky chicken."
|
||||
/>
|
||||
<InfoBox
|
||||
title="About this data"
|
||||
text="{'Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day. \n\n Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths.'}"
|
||||
notes="{'[Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)'}"
|
||||
text={'Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day. \n\n Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths.'}
|
||||
notes={'[Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)'}
|
||||
/>
|
||||
<BodyText
|
||||
text="In case you don't read Twitter, the news, or just can't get enough of The Apprentice host's legendary oration, try this Trump lorem ipsum generator on for size."
|
||||
text="Ham drumstick tail ribeye pancetta, leberkas hamburger chicken spare ribs buffalo jerky sausage ground round meatball. Leberkas kevin short loin, tri-tip shank spare ribs buffalo beef pork belly corned beef chislic tongue."
|
||||
/>
|
||||
</Story>
|
||||
<Story
|
||||
name="Lists"
|
||||
tags={['!autodocs', '!dev']}
|
||||
args={{
|
||||
title: 'What you need to know about the war',
|
||||
text: "- **Food crisis**: [Russia's invasion of Ukraine](#) in late February dramatically worsened the outlook for already inflated global food prices. \n- **Under fire**: Civillian homes destroyed in the conflict and Russia accused of war crimes. \n- **Nordstream sabotage**: A series of clandestine bombings and subsequent underwater gas leaks occurred on the Nord Stream 1 and Nord Stream 2 natural gas pipelines. ",
|
||||
}}
|
||||
/>
|
||||
<Story name="Customised" tags={['!autodocs', '!dev']}>
|
||||
<InfoBox>
|
||||
{#snippet header()}
|
||||
<h3>Global video game market</h3>
|
||||
{/snippet}
|
||||
{#snippet body()}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Year</th>
|
||||
<th>Market size ($bln)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>2024</td>
|
||||
<td>274.63</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2023</td>
|
||||
<td>281.77</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2022</td>
|
||||
<td>249.55</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<div class="text-xs font-note">Source: Precedence Research</div>
|
||||
{/snippet}
|
||||
</InfoBox>
|
||||
</Story>
|
||||
|
||||
<Story name="List">
|
||||
<InfoBox
|
||||
title="What you need to know about the war"
|
||||
text="{"- **Food crisis**: [Russia's invasion of Ukraine](#) in late February dramatically worsened the outlook for already inflated global food prices. \n- **Under fire**: Civillian homes destroyed in the conflict and Russia accused of war crimes. \n- **Nordstream sabotage**: A series of clandestine bombings and subsequent underwater gas leaks occurred on the Nord Stream 1 and Nord Stream 2 natural gas pipelines. "}"
|
||||
/>
|
||||
</Story>
|
||||
<style lang="scss">
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
// Style the table nicely
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,47 +1,59 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `InfoBox` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-infobox--docs) -->
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
/**
|
||||
* Title of the box
|
||||
*/
|
||||
export let title: string | null = null;
|
||||
interface Props {
|
||||
/**
|
||||
* Title of the box
|
||||
*/
|
||||
title?: string | null;
|
||||
/**
|
||||
* Contents of the note as a markdown string
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* Additional footnotes
|
||||
*/
|
||||
notes?: string | null;
|
||||
/**
|
||||
* Width of the component within the text well.
|
||||
*/
|
||||
width?: ContainerWidth;
|
||||
/**
|
||||
* Add extra classes to the block tag to target it with custom CSS.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
* Add an id to the block tag to target it with custom CSS.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Page theme
|
||||
*/
|
||||
theme?: Theme;
|
||||
/** Optional custom header snippet */
|
||||
header?: Snippet;
|
||||
/** Optional custom body snippet */
|
||||
body?: Snippet;
|
||||
/** Optional custom footer snippet */
|
||||
footer?: Snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of the note as a markdown string
|
||||
*/
|
||||
export let text: string = '';
|
||||
|
||||
/**
|
||||
* Additional footnotes
|
||||
*/ export let notes: string | null = null;
|
||||
|
||||
/**
|
||||
* Width of the component within the text well.
|
||||
* @type {string}
|
||||
*/
|
||||
export let width: ContainerWidth = 'normal';
|
||||
|
||||
/**
|
||||
* Add extra classes to the block tag to target it with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
/**
|
||||
* Add an id to the block tag to target it with custom CSS.
|
||||
* @type {string}
|
||||
*/
|
||||
export let id: string = '';
|
||||
|
||||
/**
|
||||
* Page theme
|
||||
*/
|
||||
export let theme: Theme = 'light';
|
||||
let {
|
||||
title = null,
|
||||
text,
|
||||
notes = null,
|
||||
width = 'normal',
|
||||
class: cls = '',
|
||||
id = '',
|
||||
theme = 'light',
|
||||
header,
|
||||
body,
|
||||
footer,
|
||||
}: Props = $props();
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
|
|
@ -53,36 +65,36 @@
|
|||
{id}
|
||||
class="{cls} fmy-6 fpx-6 fpy-5 border border-solid rounded"
|
||||
>
|
||||
{#if $$slots.header}
|
||||
{#if header}
|
||||
<div class="header fmb-2">
|
||||
<!-- Custom title content -->
|
||||
<slot name="header" />
|
||||
<!-- Custom header content -->
|
||||
{@render header()}
|
||||
</div>
|
||||
{:else if title}
|
||||
<div class="header fmb-2">
|
||||
<Markdown source="{title}" />
|
||||
<Markdown source={title} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $$slots.body}
|
||||
{#if body}
|
||||
<div class="body">
|
||||
<!-- Custom content -->
|
||||
<slot name="body" />
|
||||
<!-- Custom body content -->
|
||||
{@render body()}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="body">
|
||||
<Markdown source="{text}" />
|
||||
<Markdown source={text} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $$slots.footer}
|
||||
{#if footer}
|
||||
<div class="footer fmt-2">
|
||||
<!-- Custom footer content -->
|
||||
<slot name="footer" />
|
||||
{@render footer()}
|
||||
</div>
|
||||
{:else if notes}
|
||||
<div class="footer fmt-2">
|
||||
<Markdown source="{notes}" />
|
||||
<Markdown source={notes} />
|
||||
</div>
|
||||
{/if}
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
InfoBox is used to provide additional information that needs to be visually set aside from the main content flow. e.g. Methodology, Detailed notes about data, Extra context as text stories.
|
||||
|
||||
Switch the theme prop to `dark` for a dark page infobox.
|
||||
|
||||
Use the slots to customize the content as needed, e.g. adding icons and thumbnail images etc.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { InfoBox } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<InfoBox
|
||||
title="About this data"
|
||||
text="{'Reuters is collecting daily COVID-19 infections and deaths data for 240 countries and territories around the world, updated regularly throughout each day. \n\n Every country reports those figures a little differently and, inevitably, misses undiagnosed infections and deaths. With this project we are focusing on the trends within countries as they try to contain the virus’ spread, whether they are approaching or past peak infection rates, or if they are seeing a resurgence of infections or deaths.'}"
|
||||
notes="{'[Read more about our methodology](https://www.reuters.com/world-coronavirus-tracker-and-maps/en/methodology/)'}"
|
||||
/>
|
||||
```
|
||||
|
|
@ -1,20 +1,27 @@
|
|||
The Markdown component renders markdown into HTML. That's it!
|
||||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
import * as MarkdownStories from './Markdown.stories.svelte';
|
||||
|
||||
---
|
||||
<Meta of={MarkdownStories} />
|
||||
|
||||
# Markdown
|
||||
|
||||
The `Markdown` component renders a markdown string into HTML.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Markdown } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Markdown source="{'My markdown **string**!'}" />
|
||||
<Markdown source={'My *awesome* text in **markdown** with "smart quotes".'} />
|
||||
```
|
||||
|
||||
<Canvas of={MarkdownStories.Demo} />
|
||||
|
||||
... well, almost.
|
||||
|
||||
Owing to a weird quirk of Svelte's [`@html`](https://svelte.dev/docs/special-tags#html) directive (see [this issue](https://github.com/reuters-graphics/graphics-components/issues/148)), if you want your resulting HTML to be dynamic — e.g., update after a SvelteKit app [hydrates](https://kit.svelte.dev/docs/glossary#hydration) — then you may need to set the included `$staticMarkdown` store to `false` in the browser.
|
||||
Owing to a weird quirk of Svelte's [`@html`](https://svelte.dev/docs/special-tags#html) directive (see [this issue](https://github.com/reuters-graphics/graphics-components/issues/148)), if you want your resulting HTML to be dynamic — e.g., update after a SvelteKit app [hydrates](https://kit.svelte.dev/docs/glossary#hydration) — then you may need to set the included `staticMarkdown` rune to `{ static: false }` in the browser.
|
||||
|
||||
For example, if you're refreshing some data with markdown strings in a SvelteKit project using a [load function](https://kit.svelte.dev/docs/load), set the store to reflect the [`building`](https://kit.svelte.dev/docs/modules#$app-environment-building) variable, which will correctly [prerender](https://kit.svelte.dev/docs/glossary#prerendering) your markdown content AND update it after fresh data is fetched in the browser.
|
||||
For example, if you're refreshing some data with markdown strings in a SvelteKit project using a [load function](https://kit.svelte.dev/docs/load), set the rune to reflect the [`building`](https://kit.svelte.dev/docs/modules#$app-environment-building) variable, which will correctly [prerender](https://kit.svelte.dev/docs/glossary#prerendering) your markdown content AND update it after fresh data is fetched in the browser.
|
||||
|
||||
```javascript
|
||||
// +layout.js
|
||||
|
|
@ -23,8 +30,8 @@ import { building } from '$app/environment';
|
|||
|
||||
/** @type {import('./$types').LayoutLoad} */
|
||||
export const load = async () => {
|
||||
// Set the staticMarkdown store with the value of building.
|
||||
staticMarkdown.set(building);
|
||||
// Set the staticMarkdown rune with the value of building.
|
||||
staticMarkdown.static = building;
|
||||
|
||||
// Now this content will correctly refresh when a reader loads your page.
|
||||
const resp = await fetch(
|
||||
|
|
@ -36,4 +43,4 @@ export const load = async () => {
|
|||
};
|
||||
```
|
||||
|
||||
If you're not updating your markdown content as above, you can safely leave the `$staticMarkdown` store alone and your page will do the right thing.
|
||||
If you're not updating your markdown content as above, you can safely leave the `staticMarkdown` rune alone and your page will do the right thing.
|
||||
|
|
@ -1,35 +1,16 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Markdown from './Markdown.svelte';
|
||||
|
||||
import { withComponentDocs } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/Markdown',
|
||||
component: Markdown,
|
||||
...withComponentDocs(componentDocs),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<Block>
|
||||
<Markdown {...args} />
|
||||
</Block>
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
source: 'This is *some* text in **markdown**.',
|
||||
}}"
|
||||
name="Demo"
|
||||
args={{
|
||||
source: 'My *awesome* text in **markdown** with "smart quotes".',
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||