Merge branch 'migrate-to-svelte5' into mf-before-after

This commit is contained in:
MinamiFunakoshiTR 2025-04-07 10:19:05 -05:00 committed by GitHub
commit 2cc8ed8c22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1088 additions and 1046 deletions

View file

@ -6,9 +6,9 @@ import * as ArticleStories from './Article.stories.svelte';
# Article
The `Article` component contains all the content of our story and also establishes the dimensions of our article well, the default central trunk of our page layout.
The `Article` component contains all the contents of our story.
> 📌 In most cases, **you won't need to mess with the `Article` component** because it's already included in our rigs for you!
> 📌 In most cases, **you don't need to mess with the `Article` component** because it's already set up in the Graphics Kit.
```svelte
<script>
@ -16,29 +16,30 @@ The `Article` component contains all the content of our story and also establish
</script>
<Article>
<!-- The story stuff goes in here! -->
<!-- The story content goes here! -->
</Article>
```
<Canvas of={ArticleStories.Demo} />
## Custom well widths
## Custom column widths
The `Article` component also creates several column dimensions inside our article well. The standard widths of columns follow a basic class scheme:
The `Article` component also establishes the widths of columns that contain individual sections of the story, such as text, photos, and charts. The default column widths follow a basic class scheme:
- `narrower` A bit narrower than narrow...
- `narrow` A bit narrower than the text column
- `normal` **The main width of the body text column**
- `wide` A bit wider than the text column
- `narrower` The narrowest...
- `narrow` A bit narrower than the default body text column
- `normal` **The default width of the body text column**
- `wide` A bit wider
- `wider` A bit wider than wide...
- `widest` Edge-to-edge, but _excluding_ the left and right padding on `Article`
- `fluid` Fully edge-to-edge
When combined with the `Block` component, you can set custom column widths by passing an object to the `columnWidths` prop with pixel values for the `narrower`, `narrow`, `normal`, `wide` and `wider` column widths.
You can set custom column widths by passing an object to the `columnWidths` prop with pixel values for the `narrower`, `narrow`, `normal`, `wide` and `wider` classes. These can then be used by the `Block` component or other elements housed inside `<Article>`.
> **For most pages, you shouldn't customise the column widths.** Other tools, like our AI templates, use our default column widths, so customising those widths here has downstream consequences for graphics made outside your code. The main exception is SREP stories.
> **For most Graphics Kit pages, you shouldn't customise the column widths.** Other Reuters tools, like our AI templates, use our default column widths, so customising those widths here has downstream consequences for graphics made outside Graphics Kit. The main exception is SREP stories.
```svelte
<!-- Set custom column widths -->
<Article
columnWidths="{{
narrower: 310,
@ -48,6 +49,7 @@ When combined with the `Block` component, you can set custom column widths by pa
wider: 1400,
}}"
>
<!-- Custom column widths get passed down to the `Block` component -->
<Block width="narrower" />
<Block width="narrow" />
<Block width="normal" />
@ -58,11 +60,11 @@ When combined with the `Block` component, you can set custom column widths by pa
</Article>
```
If you're not using our `Block` component, you can still inherit the column widths from `Article` to create your own custom container with the article well dimensions by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) like this:
If you're not using our `Block` component, you can still inherit the column widths from `Article` and create your own custom containers by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) like this:
```svelte
<div class="my-special-container">
<!-- Stuffs... -->
<!-- Story content -->
</div>
<style lang="scss">
@ -72,7 +74,7 @@ If you're not using our `Block` component, you can still inherit the column widt
</style>
```
... or you can make your component entirely configurable within the article well doing something like this:
... or you can make your column widths entirely configurable by adding classes and manually specifying widths:
```svelte
<script>
@ -80,7 +82,7 @@ If you're not using our `Block` component, you can still inherit the column widt
</script>
<div class="my-special-container {width}">
<!-- Stuffs... -->
<!-- Story content -->
</div>
<style lang="scss">
@ -110,4 +112,6 @@ If you're not using our `Block` component, you can still inherit the column widt
</style>
```
Here's an example of how custom\* `columnWidths` can be used to change the article well columns:
Here's an example of how <span className='custom'>custom</span> `columnWidths` can be used to change column widths:
<Canvas of={ArticleStories.CustomColumns} />

View file

@ -2,36 +2,25 @@
import { defineMeta } from '@storybook/addon-svelte-csf';
import Block from '../Block/Block.svelte';
import Article from './Article.svelte';
// @ts-ignore raw
import customWellWidthsDocs from './stories/docs/customWellWidths.md?raw';
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
const { Story } = defineMeta({
title: 'Components/Page Layout/Article',
title: 'Components/Page layout/Article',
component: Article,
});
</script>
<script lang="ts">
</script>
<Story
name="Demo"
args={{
embedded: false,
id: '',
}}
>
<Story name="Demo">
<Article id="article-story-basic">
<div class="demo-container">
<div class="background-label">Article container</div>
<div class="background-label">Article well</div>
<div class="padding-label"><span></span>15px padding</div>
</div>
</Article>
</Story>
<Story name="Custom columns" {...withStoryDocs(customWellWidthsDocs)}>
<Story name="Custom columns" exportName="CustomColumns">
<h3>Default column widths</h3>
<Article id="article-column-widths-demo">
<div class="article-boundaries">
<Block id="section-demo" width="narrower">narrower</Block>
@ -43,22 +32,23 @@
<Block id="section-demo" width="fluid">fluid</Block>
</div>
</Article>
<h3>Custom column widths</h3>
<Article
id="article-column-widths-demo"
columnWidths={{
narrower: 310,
narrow: 450,
normal: 550,
narrower: 250,
narrow: 400,
normal: 500,
wide: 675,
wider: 1400,
}}
>
<div class="article-boundaries custom">
<Block id="section-demo" width="narrower">narrower*</Block>
<Block id="section-demo" width="narrow">narrow*</Block>
<Block id="section-demo">normal*</Block>
<Block id="section-demo" width="wide">wide*</Block>
<Block id="section-demo" width="wider">wider*</Block>
<Block id="section-demo" width="narrower">narrower</Block>
<Block id="section-demo" width="narrow">narrow</Block>
<Block id="section-demo">normal</Block>
<Block id="section-demo" width="wide">wide</Block>
<Block id="section-demo" width="wider">wider</Block>
<Block id="section-demo" width="widest">widest</Block>
<Block id="section-demo" width="fluid">fluid</Block>
</div>
@ -66,6 +56,13 @@
</Story>
<style lang="scss">
h3 {
text-align: center;
}
:global(span.custom) {
color: rgb(211, 132, 123);
font-weight: 600;
}
:global(#article-story-basic, #article-column-widths-demo) {
width: calc(100% + 30px);
margin-left: -15px;
@ -93,21 +90,22 @@
height: 50px;
padding-left: 3px;
color: white;
font-size: 12px;
font-size: 1rem;
}
div.demo-container {
height: 300px;
background: #ccc;
position: relative;
font-size: 12px;
.background-label {
font-size: 1.5rem;
position: absolute;
bottom: 0;
left: 5px;
top: 40%;
left: 40%;
color: #666;
}
.padding-label {
font-size: 1rem;
position: absolute;
top: 0;
left: -17px;

View file

@ -18,7 +18,7 @@
/** Set to true for embeddables. */
embedded?: boolean;
/** Add an id to the article tag to target it with custom CSS. */
id?: string | null;
id?: string;
/** ARIA role of the article */
role?: string | null;
/** Set custom widths for the normal, wide and wider column dimensions */
@ -28,7 +28,7 @@
let {
embedded = false,
id = null,
id = '',
role = null,
columnWidths = {
narrower: 330,

View file

@ -1,13 +0,0 @@
The `Article` component contains all the content of our story and also establishes the dimensions of our article well, the default central trunk of our page layout.
> 📌 In most cases, **you won't need to mess with the `Article` component** because it's already included in our rigs for you!
```svelte
<script>
import { Article } from '@reuters-graphics/graphics-components';
</script>
<Article>
<!-- The story stuff goes in here! -->
</Article>
```

View file

@ -1,87 +0,0 @@
The `Article` component also creates several column dimensions inside our article well. The standard widths of columns follow a basic class scheme:
- `narrower` A bit narrower than narrow...
- `narrow` A bit narrower than the text column
- `normal` **The main width of the body text column**
- `wide` A bit wider than the text column
- `wider` A bit wider than wide...
- `widest` Edge-to-edge, but _excluding_ the left and right padding on `Article`
- `fluid` Fully edge-to-edge
When combined with the `Block` component, you can set custom column widths by passing an object to the `columnWidths` prop with pixel values for the `narrower`, `narrow`, `normal`, `wide` and `wider` column widths.
> **For most pages, you shouldn't customise the column widths.** Other tools, like our AI templates, use our default column widths, so customising those widths here has downstream consequences for graphics made outside your code. The main exception is SREP stories.
```svelte
<Article
columnWidths="{{
narrower: 310,
narrow: 450,
normal: 550,
wide: 675,
wider: 1400,
}}"
>
<Block width="narrower" />
<Block width="narrow" />
<Block width="normal" />
<Block width="wide" />
<Block width="wider" />
<Block width="widest" />
<Block width="fluid" />
</Article>
```
If you're not using our `Block` component, you can still inherit the column widths from `Article` to create your own custom container with the article well dimensions by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) like this:
```svelte
<div class="my-special-container">
<!-- Stuffs... -->
</div>
<style lang="scss">
div.my-special-container {
max-width: var(--wide-column-width);
}
</style>
```
... or you can make your component entirely configurable within the article well doing something like this:
```svelte
<script>
export let width = 'normal';
</script>
<div class="my-special-container {width}">
<!-- Stuffs... -->
</div>
<style lang="scss">
div.my-special-container {
max-width: var(--normal-column-width);
&.narrower {
max-width: var(--narrower-column-width);
}
&.narrow {
max-width: var(--narrow-column-width);
}
&.wide {
max-width: var(--wide-column-width);
}
&.wider {
max-width: var(--wider-column-width);
}
&.widest {
max-width: 100%;
}
&.fluid {
width: calc(100% + 30px);
margin-left: -15px;
max-width: none;
}
}
</style>
```
Here's an example of how custom\* `columnWidths` can be used to change the article well columns:

View file

@ -58,7 +58,7 @@ updateTime: 2021-09-12T12:57:00.000Z
<Canvas of={BylineStories.Demo} />
## Cutomisation
## Custom byline, published and updated datelines
Use [snippets](https://svelte.dev/docs/svelte/snippet) to customise the byline, published and updated datelines.
@ -82,3 +82,25 @@ Use [snippets](https://svelte.dev/docs/svelte/snippet) to customise the 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} />
````

View file

@ -18,7 +18,6 @@
<Story
name="Demo"
args={{
align: 'left',
authors: [
'Dea Bankova',
'Prasanta Kumar Dutta',
@ -43,3 +42,22 @@
{/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`;
},
}}
/>

View file

@ -1,8 +1,8 @@
<!-- @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';
@ -44,7 +44,6 @@
/**
* Optional snippet for a custom published dateline.
*/
// Specify that this prop should have the type of a Svelte snippet, i.e. basic html
published?: Snippet;
/**
* Optional snippet for a custom updated dateline.
@ -59,10 +58,7 @@
align = 'left',
id = '',
cls = '',
getAuthorPage = (author: string): string => {
const authorSlug = slugify(author.trim(), { lower: true });
return `https://www.reuters.com/authors/${authorSlug}/`;
},
getAuthorPage = getAuthorPageUrl,
byline,
published,
updated,

View 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} />

View file

@ -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',
}}"
}}
/>

View file

@ -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&amp;responsive=1&amp;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>

View file

@ -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"
/>
```

View 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} />

View file

@ -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)} -->

View file

@ -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>

View file

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -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}
```

View file

@ -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"
/>
```

View file

@ -1 +0,0 @@
If your photo is missing `altText` a small warning will overlay the image.

View file

@ -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}

View 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} />

View file

@ -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>

View file

@ -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>

View file

@ -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}

View 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}

View 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}

View file

Before

Width:  |  Height:  |  Size: 618 KiB

After

Width:  |  Height:  |  Size: 618 KiB

View file

Before

Width:  |  Height:  |  Size: 388 KiB

After

Width:  |  Height:  |  Size: 388 KiB

View file

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -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>
```

View file

@ -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
# ...
[]
```

View file

@ -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.

View file

@ -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>
```

View file

@ -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>
```

View 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 Russias 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} />

View file

@ -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 Russias 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 Russias 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>

View file

@ -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>

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 363 KiB

View file

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View file

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View file

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 519 KiB

View file

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View file

@ -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"

View file

@ -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'}"
/>
```

View file

@ -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>
```

View file

@ -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()}"
/>
```

View file

@ -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 Russias 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>
```

View file

@ -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>
```

View file

@ -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'}"
/>
```

View file

@ -1,5 +1,15 @@
import slugify from 'slugify';
/** Helper function to generate a random 4-character string */
export const random4 = () =>
Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
/**
* Custom function that returns an author page URL.
*/
export const getAuthorPageUrl = (author: string): string => {
const authorSlug = slugify(author.trim(), { lower: true });
return `https://www.reuters.com/authors/${authorSlug}/`;
};