@reuters-graphics/graphics-components 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FeaturePhoto/stories/docs/archieML.md +37 -0
- package/dist/components/GraphicBlock/stories/docs/archieML.md +40 -0
- package/dist/components/PhotoPack/stories/docs/archieML.md +63 -0
- package/dist/components/ReferralBlock/ReferralBlock.svelte +2 -1
- package/dist/components/ReferralBlock/filterCurrentPage.d.ts +2 -0
- package/dist/components/ReferralBlock/filterCurrentPage.js +32 -0
- package/dist/components/ReferralBlock/types.d.ts +99 -0
- package/dist/components/ReferralBlock/types.js +1 -0
- package/dist/components/SEO/stories/docs/archieML.md +36 -0
- package/dist/components/Scroller/stories/docs/archieML.md +87 -0
- package/dist/components/SiteHeadline/stories/docs/archieML.md +26 -0
- package/dist/docs/docs-components/CopyColourTable/styles.module.scss +4 -2
- package/dist/docs/docs-components/Herbie/Herbie.d.ts +7 -0
- package/dist/docs/docs-components/Herbie/Herbie.tsx +47 -0
- package/dist/docs/docs-components/Highlight/Highlight.d.ts +6 -0
- package/dist/docs/docs-components/Highlight/Highlight.tsx +19 -0
- package/dist/docs/docs-components/SubtleHighlight/SubtleHighlight.d.ts +6 -0
- package/dist/docs/docs-components/SubtleHighlight/SubtleHighlight.tsx +16 -0
- package/dist/docs/docs-components/ThemeBuilder/NewTheme/styles.module.scss +3 -1
- package/dist/docs/docs-components/syntax/nord.d.ts +7 -0
- package/dist/docs/docs-components/syntax/nord.js +155 -0
- package/dist/docs/guides/archieml.mdx +441 -0
- package/dist/docs/guides/svelte-components.mdx +138 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
```yaml
|
|
2
|
+
[blocks]
|
|
3
|
+
|
|
4
|
+
type: photo
|
|
5
|
+
width: normal
|
|
6
|
+
src: images/shark.jpg
|
|
7
|
+
altText: The king of the sea
|
|
8
|
+
caption: Carcharodon carcharias - REUTERS
|
|
9
|
+
|
|
10
|
+
[]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```svelte
|
|
14
|
+
<!-- App.svelte -->
|
|
15
|
+
<script>
|
|
16
|
+
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
|
17
|
+
|
|
18
|
+
import content from '$locales/en/content.json';
|
|
19
|
+
import { assets } from '$app/paths';
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#each content.blocks as block}
|
|
23
|
+
{#if block.Type === 'text'}
|
|
24
|
+
<!-- ... -->
|
|
25
|
+
|
|
26
|
+
{:else if block.type === 'photo'}
|
|
27
|
+
<FeaturePhoto
|
|
28
|
+
width="{block.width}"
|
|
29
|
+
src="{`${assets}/${block.src}`}"
|
|
30
|
+
altText="{block.altText}"
|
|
31
|
+
caption="{block.caption}"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<!-- ... -->
|
|
35
|
+
{/if}
|
|
36
|
+
{/each}
|
|
37
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
For Graphics Kit users, the `GraphicBlock` component is built-in to handle [ai2svelte](https://github.com/reuters-graphics/ai2svelte) graphics.
|
|
2
|
+
|
|
3
|
+
First, import your ai2svelte graphic in `App.svelte` and add it to the `aiCharts` object;
|
|
4
|
+
|
|
5
|
+
```svelte
|
|
6
|
+
<!-- App.svelte -->
|
|
7
|
+
<script>
|
|
8
|
+
// Other stuff...
|
|
9
|
+
|
|
10
|
+
import AiMap from './ai2svelte/my-map.svelte';
|
|
11
|
+
|
|
12
|
+
const aiCharts = {
|
|
13
|
+
// Other charts...
|
|
14
|
+
AiMap,
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
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`:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
[blocks]
|
|
23
|
+
# ...
|
|
24
|
+
|
|
25
|
+
type: ai-graphic
|
|
26
|
+
chart: AiMap
|
|
27
|
+
width: normal
|
|
28
|
+
textWidth: normal
|
|
29
|
+
title: Earthquake in Haiti
|
|
30
|
+
description: The 7.2-magnitude earthquake struck at 8:29 a.m. EST, Aug. 14, 2021.
|
|
31
|
+
notes: \Note: A shakemap represents the ground shaking produced by an earthquake.
|
|
32
|
+
|
|
33
|
+
\Source: USGIS
|
|
34
|
+
:end
|
|
35
|
+
altText: A map that shows the shake intensity of the earthquake, which was worst in central Haiti.
|
|
36
|
+
:end
|
|
37
|
+
|
|
38
|
+
# ...
|
|
39
|
+
[]
|
|
40
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
```yaml
|
|
2
|
+
[blocks]
|
|
3
|
+
# ...
|
|
4
|
+
|
|
5
|
+
type: photo-pack
|
|
6
|
+
id: my-photo-pack
|
|
7
|
+
class: mb-2
|
|
8
|
+
width: wide
|
|
9
|
+
textWidth: normal
|
|
10
|
+
gap: 10
|
|
11
|
+
[.images]
|
|
12
|
+
src: images/my-img-1.jpg
|
|
13
|
+
altText: Alt text
|
|
14
|
+
caption: Lorem ipsum. REUTERS/Photog
|
|
15
|
+
|
|
16
|
+
src: images/my-img-2.jpg
|
|
17
|
+
altText: Alt text
|
|
18
|
+
caption: Lorem ipsum. REUTERS/Photog
|
|
19
|
+
|
|
20
|
+
src: images/my-img-3.jpg
|
|
21
|
+
altText: Alt text
|
|
22
|
+
caption: Lorem ipsum. REUTERS/Photog
|
|
23
|
+
|
|
24
|
+
src: images/my-img-4.jpg
|
|
25
|
+
altText: Alt text
|
|
26
|
+
caption: Lorem ipsum. REUTERS/Photog
|
|
27
|
+
|
|
28
|
+
src: images/my-img-5.jpg
|
|
29
|
+
altText: Alt text
|
|
30
|
+
caption: Lorem ipsum. REUTERS/Photog
|
|
31
|
+
[]
|
|
32
|
+
|
|
33
|
+
# ...
|
|
34
|
+
[]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```svelte
|
|
38
|
+
<!-- App.svelte -->
|
|
39
|
+
{#each content.blocks as block}
|
|
40
|
+
{#if block.type === 'text'}
|
|
41
|
+
<!-- ... -->
|
|
42
|
+
|
|
43
|
+
{:else if block.type === 'photo-pack'}
|
|
44
|
+
<PhotoPack
|
|
45
|
+
id={block.id}
|
|
46
|
+
class={block.class}
|
|
47
|
+
width={block.width}
|
|
48
|
+
textWidth={block.textWidth}
|
|
49
|
+
images={block.images.map((img) => ({
|
|
50
|
+
src: `${assets}/${img.src}`,
|
|
51
|
+
altText: img.altText,
|
|
52
|
+
caption: img.caption,
|
|
53
|
+
}))}
|
|
54
|
+
layouts={[
|
|
55
|
+
{ breakpoint: 750, rows: [2, 3] },
|
|
56
|
+
{ breakpoint: 450, rows: [1, 2, 2] },
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<!-- ... -->
|
|
61
|
+
{/if}
|
|
62
|
+
{/each}
|
|
63
|
+
```
|
|
@@ -11,6 +11,7 @@ export { cls as class };
|
|
|
11
11
|
import Block from "../Block/Block.svelte";
|
|
12
12
|
import { onMount } from "svelte";
|
|
13
13
|
import { getTime } from "../SiteHeader/NavBar/NavDropdown/StoryCard/time";
|
|
14
|
+
import { articleIsNotCurrentPage } from "./filterCurrentPage";
|
|
14
15
|
let clientWidth;
|
|
15
16
|
const SECTION_API = "recent-stories-by-sections-v1";
|
|
16
17
|
const COLLECTION_API = "articles-by-collection-alias-or-id-v1";
|
|
@@ -30,7 +31,7 @@ const getReferrals = async () => {
|
|
|
30
31
|
})
|
|
31
32
|
);
|
|
32
33
|
const data = await response.json();
|
|
33
|
-
const articles = data.result.articles.filter((a) => a?.headline_category || a?.kicker?.name).filter((a) => a?.thumbnail?.renditions?.landscape?.["240w"]).filter((a) => !a?.content?.third_party).slice(0, number);
|
|
34
|
+
const articles = data.result.articles.filter((a) => a?.headline_category || a?.kicker?.name).filter((a) => a?.thumbnail?.renditions?.landscape?.["240w"]).filter((a) => !a?.content?.third_party).filter(articleIsNotCurrentPage).slice(0, number);
|
|
34
35
|
referrals = articles;
|
|
35
36
|
} catch {
|
|
36
37
|
console.warn("Unable to fetch referral links.");
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const getUrlFromPath = (path) => {
|
|
2
|
+
const base = 'https://www.reuters.com';
|
|
3
|
+
try {
|
|
4
|
+
return new URL(path);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(path, base);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const isCurrentPage = (urlPath) => {
|
|
16
|
+
if (typeof window === 'undefined' || typeof window.location === 'undefined') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const url = getUrlFromPath(urlPath);
|
|
20
|
+
if (!url)
|
|
21
|
+
return false;
|
|
22
|
+
return (window.location.origin === url.origin &&
|
|
23
|
+
window.location.pathname === url.pathname);
|
|
24
|
+
};
|
|
25
|
+
export const articleIsNotCurrentPage = (article) => {
|
|
26
|
+
const { redirect_url: redirectUrl, canonical_url: canonicalUrl } = article;
|
|
27
|
+
if (redirectUrl)
|
|
28
|
+
return !isCurrentPage(redirectUrl);
|
|
29
|
+
if (canonicalUrl)
|
|
30
|
+
return !isCurrentPage(canonicalUrl);
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface Referrals {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
message: string;
|
|
4
|
+
result: Result;
|
|
5
|
+
}
|
|
6
|
+
interface Result {
|
|
7
|
+
date_modified: Date;
|
|
8
|
+
pagination: Pagination;
|
|
9
|
+
fetch_type: string;
|
|
10
|
+
title: string;
|
|
11
|
+
articles: Article[];
|
|
12
|
+
}
|
|
13
|
+
export interface Article {
|
|
14
|
+
id: string;
|
|
15
|
+
canonical_url: string;
|
|
16
|
+
basic_headline: string;
|
|
17
|
+
title: string;
|
|
18
|
+
lead_art: LeadArt;
|
|
19
|
+
description: string;
|
|
20
|
+
web: string;
|
|
21
|
+
content_code: string;
|
|
22
|
+
updated_time: Date;
|
|
23
|
+
published_time: Date;
|
|
24
|
+
display_time: Date;
|
|
25
|
+
thumbnail: LeadArt;
|
|
26
|
+
primary_media_type: string;
|
|
27
|
+
source: Source;
|
|
28
|
+
redirect_url: string;
|
|
29
|
+
distributor: string;
|
|
30
|
+
authors: Author[];
|
|
31
|
+
kicker: Kicker;
|
|
32
|
+
content_elements: unknown[];
|
|
33
|
+
headline_category?: unknown;
|
|
34
|
+
content?: {
|
|
35
|
+
third_party?: unknown;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
interface Author {
|
|
39
|
+
topic_url: string;
|
|
40
|
+
thumbnail: Thumbnail;
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
first_name: string;
|
|
44
|
+
last_name: string;
|
|
45
|
+
company: string;
|
|
46
|
+
social_links: SocialLink[];
|
|
47
|
+
byline: string;
|
|
48
|
+
}
|
|
49
|
+
interface SocialLink {
|
|
50
|
+
url: string;
|
|
51
|
+
site: string;
|
|
52
|
+
}
|
|
53
|
+
interface Thumbnail {
|
|
54
|
+
url: string;
|
|
55
|
+
resizer_url: string;
|
|
56
|
+
renditions: Renditions;
|
|
57
|
+
}
|
|
58
|
+
interface Renditions {
|
|
59
|
+
square: Landscape;
|
|
60
|
+
landscape: Landscape;
|
|
61
|
+
portrait: Landscape;
|
|
62
|
+
original: Landscape;
|
|
63
|
+
}
|
|
64
|
+
interface Landscape {
|
|
65
|
+
'60w': string;
|
|
66
|
+
'120w': string;
|
|
67
|
+
'240w': string;
|
|
68
|
+
'480w': string;
|
|
69
|
+
'960w': string;
|
|
70
|
+
'1080w': string;
|
|
71
|
+
'1200w': string;
|
|
72
|
+
'1920w': string;
|
|
73
|
+
}
|
|
74
|
+
interface Kicker {
|
|
75
|
+
name: string;
|
|
76
|
+
path: string;
|
|
77
|
+
names: string[];
|
|
78
|
+
}
|
|
79
|
+
interface LeadArt {
|
|
80
|
+
type: string;
|
|
81
|
+
url: string;
|
|
82
|
+
resizer_url: string;
|
|
83
|
+
renditions: Renditions;
|
|
84
|
+
id: string;
|
|
85
|
+
caption?: string;
|
|
86
|
+
alt_text: string;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
subtitle: string;
|
|
90
|
+
updated_at: Date;
|
|
91
|
+
}
|
|
92
|
+
interface Source {
|
|
93
|
+
name: string;
|
|
94
|
+
}
|
|
95
|
+
interface Pagination {
|
|
96
|
+
size: number;
|
|
97
|
+
expected_size: number;
|
|
98
|
+
}
|
|
99
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
```yaml
|
|
2
|
+
slug: ROOT-SLUG/WILD
|
|
3
|
+
seoTitle: Page title for search
|
|
4
|
+
seoDescription: Page description for search
|
|
5
|
+
shareTitle: Page title for social media
|
|
6
|
+
shareDescription: Page description for social media
|
|
7
|
+
shareImgPath: images/reuters-graphics.jpg
|
|
8
|
+
shareImgAlt: Alt text for share image.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script>
|
|
13
|
+
import { SEO } from '@reuters-graphics/graphics-components';
|
|
14
|
+
import pkg from '$pkg';
|
|
15
|
+
import content from '$locales/en/content.json';
|
|
16
|
+
import { assets } from '$app/paths';
|
|
17
|
+
import { page } from '$app/stores';
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<SEO
|
|
21
|
+
baseUrl="{VITE_BASE_URL}"
|
|
22
|
+
pageUrl="{$page.url}"
|
|
23
|
+
seoTitle="{content.seoTitle}"
|
|
24
|
+
seoDescription="{content.seoDescription}"
|
|
25
|
+
shareTitle="{content.shareTitle}"
|
|
26
|
+
shareDescription="{content.shareDescription}"
|
|
27
|
+
shareImgPath="{`${assets}/${content.shareImgPath}`}"
|
|
28
|
+
shareImgAlt="{content.shareImgAlt}"
|
|
29
|
+
publishTime="{pkg?.reuters?.graphic?.published}"
|
|
30
|
+
updateTime="{pkg?.reuters?.graphic?.updated}"
|
|
31
|
+
authors="{pkg?.reuters?.graphic?.authors}"
|
|
32
|
+
/>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> **Note:** For _reasons_, we can't document the value of `VITE_BASE_URL` below. It's `import` + `.meta.env.BASE_URL` (concatenate all that) in the Graphics Kit and other Vite-based rigs.
|
|
36
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
First, import your ai2svelte graphics in `App.svelte` and add them to the `aiCharts` object;
|
|
2
|
+
|
|
3
|
+
```svelte
|
|
4
|
+
<!-- App.svelte -->
|
|
5
|
+
<script>
|
|
6
|
+
// Other stuff...
|
|
7
|
+
|
|
8
|
+
import AiMap1 from './ai2svelte/my-map-1.svelte';
|
|
9
|
+
import AiMap2 from './ai2svelte/my-map-2.svelte';
|
|
10
|
+
import AiMap3 from './ai2svelte/my-map-3.svelte';
|
|
11
|
+
|
|
12
|
+
const aiCharts = {
|
|
13
|
+
// Other charts...
|
|
14
|
+
AiMap1,
|
|
15
|
+
AiMap2,
|
|
16
|
+
AiMap3,
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then add the following structure to your ArchieML Doc, taking care that the names of your charts in the `aiCharts` object match the names of your step backgrounds:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
[blocks]
|
|
25
|
+
# ...
|
|
26
|
+
|
|
27
|
+
type: ai-scroller
|
|
28
|
+
id: my-map-scroller
|
|
29
|
+
width: normal
|
|
30
|
+
foregroundPosition: middle
|
|
31
|
+
stackBackground: true
|
|
32
|
+
[.steps]
|
|
33
|
+
background: AiMap1
|
|
34
|
+
text: #### Step 1
|
|
35
|
+
|
|
36
|
+
Lorem ipsum
|
|
37
|
+
:end
|
|
38
|
+
altText: A map showing the Upper West side in New York City.
|
|
39
|
+
|
|
40
|
+
Can add paragraphs of alt text if you want to break up sentences.
|
|
41
|
+
:end
|
|
42
|
+
|
|
43
|
+
background: AiMap2
|
|
44
|
+
text: #### Step 2
|
|
45
|
+
|
|
46
|
+
Lorem ipsum
|
|
47
|
+
:end
|
|
48
|
+
altText: The same map now highlights 98th Street.
|
|
49
|
+
:end
|
|
50
|
+
|
|
51
|
+
background: AiMap3
|
|
52
|
+
text: #### Step 3
|
|
53
|
+
|
|
54
|
+
Lorem ipsum
|
|
55
|
+
:end
|
|
56
|
+
altText: The same map now highlights three locations near 98th Street where something particulary important happened.
|
|
57
|
+
:end
|
|
58
|
+
[]
|
|
59
|
+
|
|
60
|
+
# ...
|
|
61
|
+
[]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```svelte
|
|
65
|
+
<!-- App.svelte -->
|
|
66
|
+
{#each content.blocks as block}
|
|
67
|
+
{#if block.type === 'text'}
|
|
68
|
+
<!-- ... -->
|
|
69
|
+
|
|
70
|
+
{:else if block.type === 'ai-scroller'}
|
|
71
|
+
<Scroller
|
|
72
|
+
id="{block.id}"
|
|
73
|
+
backgroundWidth="{block.width}"
|
|
74
|
+
foregroundPosition="{block.foregroundPosition}"
|
|
75
|
+
stackBackground="{block.stackBackground === 'true'}"
|
|
76
|
+
steps="{block.steps.map((step) => ({
|
|
77
|
+
background: aiCharts[step.background],
|
|
78
|
+
backgroundProps: assets || '/',
|
|
79
|
+
foreground: step.text,
|
|
80
|
+
altText: step.altText,
|
|
81
|
+
}))}"
|
|
82
|
+
/>
|
|
83
|
+
|
|
84
|
+
<!-- ... -->
|
|
85
|
+
{/if}
|
|
86
|
+
{/each}
|
|
87
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
```yaml
|
|
2
|
+
section: Graphics
|
|
3
|
+
sectionUrl: https://www.reuters.com/graphics/
|
|
4
|
+
hed: A beautiful page
|
|
5
|
+
authors: Samuel Granados,Dea Bankova
|
|
6
|
+
published: 2022-09-12T08:30:00.000Z
|
|
7
|
+
updated:
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```svelte
|
|
11
|
+
<!-- App.svelte -->
|
|
12
|
+
<script>
|
|
13
|
+
import { SiteHeadline } from '@reuters-graphics/graphics-components';
|
|
14
|
+
|
|
15
|
+
import content from '$locales/en/content.json';
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<SiteHeadline
|
|
19
|
+
section="{content.section}"
|
|
20
|
+
sectionUrl="{content.sectionUrl}"
|
|
21
|
+
hed="{content.hed}"
|
|
22
|
+
authors="{content.authors.split(',')}"
|
|
23
|
+
publishTime="{content.published}"
|
|
24
|
+
updateTime="{content.updated}"
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
@@ -134,7 +134,7 @@ $header: #5e81ac;
|
|
|
134
134
|
.importsnippet :global {
|
|
135
135
|
max-width: 600px;
|
|
136
136
|
position: relative;
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
p {
|
|
139
139
|
font-size: 0.8rem;
|
|
140
140
|
line-height: 1;
|
|
@@ -147,7 +147,9 @@ $header: #5e81ac;
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
pre {
|
|
150
|
-
box-shadow:
|
|
150
|
+
box-shadow:
|
|
151
|
+
0 5px 10px rgba(0, 0, 0, 0.19),
|
|
152
|
+
0 3px 3px rgba(0, 0, 0, 0.23) !important;
|
|
151
153
|
margin-top: 0 !important;
|
|
152
154
|
border-radius: 4px;
|
|
153
155
|
border: 1px solid hsla(203, 50%, 30%, 0.15);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface InlineNumberProps {
|
|
4
|
+
number: number;
|
|
5
|
+
children: React.ReactNode; // Allow children to be passed
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const InlineNumber: React.FC<InlineNumberProps> = ({ number, children }) => {
|
|
9
|
+
const containerStyle: React.CSSProperties = {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
alignItems: 'flex-start', // Align items at the top
|
|
12
|
+
gap: '0.5em',
|
|
13
|
+
marginTop: '1.5em',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const numberStyle: React.CSSProperties = {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
justifyContent: 'center',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
backgroundColor: 'rgb(0 156 253)',
|
|
21
|
+
color: 'white',
|
|
22
|
+
borderRadius: '50%',
|
|
23
|
+
width: '1.8em',
|
|
24
|
+
height: '1.8em',
|
|
25
|
+
fontSize: '1em',
|
|
26
|
+
fontWeight: 'bold',
|
|
27
|
+
lineHeight: '1',
|
|
28
|
+
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
|
|
29
|
+
flexShrink: 0, // Prevent shrinking
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const textStyle: React.CSSProperties = {
|
|
33
|
+
paddingTop: '0.25em',
|
|
34
|
+
fontSize: '1em',
|
|
35
|
+
lineHeight: '1.4em', // Adjust line height for readability
|
|
36
|
+
wordBreak: 'break-word', // Handle long words gracefully
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div style={containerStyle}>
|
|
41
|
+
<div style={numberStyle}>{number}</div>
|
|
42
|
+
<div style={textStyle}>{children}</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default InlineNumber;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface HighlightProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const Highlight: React.FC<HighlightProps> = ({ children }) => {
|
|
8
|
+
const style: React.CSSProperties = {
|
|
9
|
+
backgroundColor: '#FFF8DC', // Light yellow (Cornsilk)
|
|
10
|
+
padding: '0.15em 0.25em',
|
|
11
|
+
border: '1px solid rgb(255 239 177)',
|
|
12
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', // Subtle shadow for depth
|
|
13
|
+
fontWeight: 'bold',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return <span style={style}>{children}</span>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Highlight;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface HighlightProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const Highlight: React.FC<HighlightProps> = ({ children }) => {
|
|
8
|
+
const style: React.CSSProperties = {
|
|
9
|
+
backgroundColor: '#fff5cd',
|
|
10
|
+
padding: '0.15em 0.3em',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return <span style={style}>{children}</span>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default Highlight;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
pre {
|
|
11
|
-
box-shadow:
|
|
11
|
+
box-shadow:
|
|
12
|
+
0 10px 20px rgba(0, 0, 0, 0.19),
|
|
13
|
+
0 6px 6px rgba(0, 0, 0, 0.23) !important;
|
|
12
14
|
border: 0 !important;
|
|
13
15
|
padding: 1em 1.5em !important;
|
|
14
16
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nord syntax highlighting to match syntax highlighting in .storybook/syntax.scss
|
|
3
|
+
*/
|
|
4
|
+
export const prismNord = {
|
|
5
|
+
'code[class*="language-"]': {
|
|
6
|
+
color: '#f8f8f2',
|
|
7
|
+
background: 'none',
|
|
8
|
+
fontFamily: "\"Fira Code\", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
|
|
9
|
+
textAlign: 'left',
|
|
10
|
+
whiteSpace: 'pre',
|
|
11
|
+
wordSpacing: 'normal',
|
|
12
|
+
wordBreak: 'normal',
|
|
13
|
+
wordWrap: 'normal',
|
|
14
|
+
lineHeight: '1.5',
|
|
15
|
+
MozTabSize: '4',
|
|
16
|
+
OTabSize: '4',
|
|
17
|
+
tabSize: '4',
|
|
18
|
+
WebkitHyphens: 'none',
|
|
19
|
+
MozHyphens: 'none',
|
|
20
|
+
msHyphens: 'none',
|
|
21
|
+
hyphens: 'none',
|
|
22
|
+
},
|
|
23
|
+
'pre[class*="language-"]': {
|
|
24
|
+
color: '#f8f8f2',
|
|
25
|
+
background: '#2E3440',
|
|
26
|
+
fontFamily: "\"Fira Code\", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
|
|
27
|
+
textAlign: 'left',
|
|
28
|
+
whiteSpace: 'pre',
|
|
29
|
+
wordSpacing: 'normal',
|
|
30
|
+
wordBreak: 'normal',
|
|
31
|
+
wordWrap: 'normal',
|
|
32
|
+
lineHeight: '1.5',
|
|
33
|
+
MozTabSize: '4',
|
|
34
|
+
OTabSize: '4',
|
|
35
|
+
tabSize: '4',
|
|
36
|
+
WebkitHyphens: 'none',
|
|
37
|
+
MozHyphens: 'none',
|
|
38
|
+
msHyphens: 'none',
|
|
39
|
+
hyphens: 'none',
|
|
40
|
+
padding: '1em',
|
|
41
|
+
margin: '.5em 0',
|
|
42
|
+
overflow: 'auto',
|
|
43
|
+
borderRadius: '0.3em',
|
|
44
|
+
},
|
|
45
|
+
':not(pre) > code[class*="language-"]': {
|
|
46
|
+
background: '#2E3440',
|
|
47
|
+
padding: '.1em',
|
|
48
|
+
borderRadius: '.3em',
|
|
49
|
+
whiteSpace: 'normal',
|
|
50
|
+
},
|
|
51
|
+
comment: {
|
|
52
|
+
color: '#9199aa',
|
|
53
|
+
},
|
|
54
|
+
prolog: {
|
|
55
|
+
color: '#9199aa',
|
|
56
|
+
},
|
|
57
|
+
doctype: {
|
|
58
|
+
color: '#9199aa',
|
|
59
|
+
},
|
|
60
|
+
cdata: {
|
|
61
|
+
color: '#9199aa',
|
|
62
|
+
},
|
|
63
|
+
punctuation: {
|
|
64
|
+
color: '#81A1C1',
|
|
65
|
+
},
|
|
66
|
+
'.namespace': {
|
|
67
|
+
opacity: '.7',
|
|
68
|
+
},
|
|
69
|
+
property: {
|
|
70
|
+
color: '#81A1C1',
|
|
71
|
+
},
|
|
72
|
+
tag: {
|
|
73
|
+
color: '#81A1C1',
|
|
74
|
+
},
|
|
75
|
+
constant: {
|
|
76
|
+
color: '#81A1C1',
|
|
77
|
+
},
|
|
78
|
+
symbol: {
|
|
79
|
+
color: '#81A1C1',
|
|
80
|
+
},
|
|
81
|
+
deleted: {
|
|
82
|
+
color: '#81A1C1',
|
|
83
|
+
},
|
|
84
|
+
number: {
|
|
85
|
+
color: '#B48EAD',
|
|
86
|
+
},
|
|
87
|
+
boolean: {
|
|
88
|
+
color: '#81A1C1',
|
|
89
|
+
},
|
|
90
|
+
selector: {
|
|
91
|
+
color: '#A3BE8C',
|
|
92
|
+
},
|
|
93
|
+
'attr-name': {
|
|
94
|
+
color: '#A3BE8C',
|
|
95
|
+
},
|
|
96
|
+
string: {
|
|
97
|
+
color: '#A3BE8C',
|
|
98
|
+
},
|
|
99
|
+
char: {
|
|
100
|
+
color: '#A3BE8C',
|
|
101
|
+
},
|
|
102
|
+
builtin: {
|
|
103
|
+
color: '#A3BE8C',
|
|
104
|
+
},
|
|
105
|
+
inserted: {
|
|
106
|
+
color: '#A3BE8C',
|
|
107
|
+
},
|
|
108
|
+
operator: {
|
|
109
|
+
color: '#81A1C1',
|
|
110
|
+
},
|
|
111
|
+
entity: {
|
|
112
|
+
color: '#81A1C1',
|
|
113
|
+
cursor: 'help',
|
|
114
|
+
},
|
|
115
|
+
url: {
|
|
116
|
+
color: '#81A1C1',
|
|
117
|
+
},
|
|
118
|
+
'.language-css .token.string': {
|
|
119
|
+
color: '#81A1C1',
|
|
120
|
+
},
|
|
121
|
+
'.style .token.string': {
|
|
122
|
+
color: '#81A1C1',
|
|
123
|
+
},
|
|
124
|
+
variable: {
|
|
125
|
+
color: '#81A1C1',
|
|
126
|
+
},
|
|
127
|
+
atrule: {
|
|
128
|
+
color: '#88C0D0',
|
|
129
|
+
},
|
|
130
|
+
'attr-value': {
|
|
131
|
+
color: '#88C0D0',
|
|
132
|
+
},
|
|
133
|
+
function: {
|
|
134
|
+
color: '#88C0D0',
|
|
135
|
+
},
|
|
136
|
+
'class-name': {
|
|
137
|
+
color: '#88C0D0',
|
|
138
|
+
},
|
|
139
|
+
keyword: {
|
|
140
|
+
color: '#81A1C1',
|
|
141
|
+
},
|
|
142
|
+
regex: {
|
|
143
|
+
color: '#EBCB8B',
|
|
144
|
+
},
|
|
145
|
+
important: {
|
|
146
|
+
color: '#EBCB8B',
|
|
147
|
+
fontWeight: 'bold',
|
|
148
|
+
},
|
|
149
|
+
bold: {
|
|
150
|
+
fontWeight: 'bold',
|
|
151
|
+
},
|
|
152
|
+
italic: {
|
|
153
|
+
fontStyle: 'italic',
|
|
154
|
+
},
|
|
155
|
+
};
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
import { parameters } from '../utils/docsPage.js';
|
|
3
|
+
import Herbie from '../docs-components/Herbie/Herbie';
|
|
4
|
+
|
|
5
|
+
<Meta title="Guides/Using with ArchieML docs" parameters={{ ...parameters }} />
|
|
6
|
+
|
|
7
|
+
# Using components with ArchieML docs
|
|
8
|
+
|
|
9
|
+
Most of the snippets in these docs show how to use components by passing data into their props directly. In most cases, though, you don't want to hard-code content in your project and instead will get those values from an [ArchieML](https://archieml.org/)-formatted document, hosted in either [RNGS.io](https://rngs.io) or a Google Doc.
|
|
10
|
+
|
|
11
|
+
In most cases, it's straight forward to fill in component props from ArchieML values, but in some cases you may need to write a small bit of code to translate strings from an ArchieML doc into a different data type or to rearrange data from a doc into a specific format to match a component's props.
|
|
12
|
+
|
|
13
|
+
## Simple example
|
|
14
|
+
|
|
15
|
+
Let's look at a basic component, a `ProfileCard`, with a demo that looks like this in the docs:
|
|
16
|
+
|
|
17
|
+
```svelte
|
|
18
|
+
<script>
|
|
19
|
+
import { ProfileCard } from '@reuters-graphics/graphics-components';
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<ProfileCard
|
|
23
|
+
name="Kitty"
|
|
24
|
+
age="{2}"
|
|
25
|
+
img="https://cats.com/cat1.jpg"
|
|
26
|
+
birthday="{new Date('2020-09-25')}"
|
|
27
|
+
bio="Some notes.\n\nWith multiple paragraphs."
|
|
28
|
+
isGood="{true}"
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Notice the component's props includes strings, a date, a number and a boolean.
|
|
33
|
+
|
|
34
|
+
<Herbie number="1">
|
|
35
|
+
{`In our ArchieML doc, we might fill out a block for this component like ...`}
|
|
36
|
+
</Herbie>
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
[blocks]
|
|
40
|
+
# ...
|
|
41
|
+
|
|
42
|
+
type: profile-card
|
|
43
|
+
name: Tom
|
|
44
|
+
age: 10
|
|
45
|
+
picture: images/tom-the-cat.jpg
|
|
46
|
+
birthday: 2020-09-25
|
|
47
|
+
bio: A very frisky feline.
|
|
48
|
+
|
|
49
|
+
... and an avid mouser!
|
|
50
|
+
:end
|
|
51
|
+
isGood: true
|
|
52
|
+
|
|
53
|
+
# ...
|
|
54
|
+
[]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It's often a good idea to give the properties in your ArchieML doc the same names as the props on the component you're using. That's what we've done above, but you can also name them something else. For example, we went with `picture` instead of `img`.
|
|
58
|
+
|
|
59
|
+
> If the ArchieML syntax above looks foreign to you, check out the Help docs on any story in [RNGS.io](https://rngs.io) or read the official [ArchieML docs](https://archieml.org/#demo) to get familiar with the basics.
|
|
60
|
+
|
|
61
|
+
<Herbie number="2">
|
|
62
|
+
{`When we download the doc, our ArchieML will turn into JSON data like ...`}
|
|
63
|
+
</Herbie>
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"blocks": [
|
|
68
|
+
{
|
|
69
|
+
"type": "profile-card",
|
|
70
|
+
"name": "Tom",
|
|
71
|
+
"age": "10",
|
|
72
|
+
"picture": "images/tom-the-cat.jpg",
|
|
73
|
+
"birthday": "2020-09-25",
|
|
74
|
+
"bio": "A very frisy feline.\n\n... and an avid mouser!",
|
|
75
|
+
"isGood": "true"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Notice all the values in the data are **strings**. More on that soon.
|
|
82
|
+
|
|
83
|
+
<Herbie number="3">
|
|
84
|
+
{`Now we can use that data to feed our component the information it needs for its props:`}
|
|
85
|
+
</Herbie>
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script>
|
|
89
|
+
// These are usually already imported for you
|
|
90
|
+
import { assets } from '$app/paths';
|
|
91
|
+
// Your ArchieML doc
|
|
92
|
+
import content from '$locales/en/content.json';
|
|
93
|
+
|
|
94
|
+
// Your component from graphics-components
|
|
95
|
+
import { ProfileCard } from '@reuters-graphics/graphics-components';
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
{#each content.blocks as block}
|
|
99
|
+
{#if block.type === 'text'}
|
|
100
|
+
<!-- ... -->
|
|
101
|
+
{:else if block.type === 'profile-card'}
|
|
102
|
+
<ProfileCard
|
|
103
|
+
name="{block.name}"
|
|
104
|
+
age="{parseInt(block.age)}"
|
|
105
|
+
img="{`${assets}/${block.picture}`}"
|
|
106
|
+
birthday="{new Date(block.birthday)}"
|
|
107
|
+
bio="{block.bio}"
|
|
108
|
+
isGood="{block.isGood === 'true'}"
|
|
109
|
+
/>
|
|
110
|
+
<!-- ... -->
|
|
111
|
+
{/if}
|
|
112
|
+
{/each}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### But let's break that last bit down ...
|
|
116
|
+
|
|
117
|
+
This is a loop that goes over each block in the `blocks: []` array in the JSON data we downloaded from our ArchieML doc.
|
|
118
|
+
|
|
119
|
+
```svelte
|
|
120
|
+
{#each content.blocks as block}
|
|
121
|
+
<!-- ... -->
|
|
122
|
+
{/each}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
While we're looping over each individual block, we check its `type` so we know which component to match it with. For example, `block.type === 'text'` will signal we want to use our [BodyText](./?path=/docs/components-text-elements-bodytext--docs) component in this case.
|
|
126
|
+
|
|
127
|
+
```svelte
|
|
128
|
+
{#if block.type === 'text'}
|
|
129
|
+
<!-- ... -->
|
|
130
|
+
{:else if block.type === 'profile-card'}
|
|
131
|
+
<!-- ... -->
|
|
132
|
+
{/if}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Once we've identified we have the right block for our component, we need to convert several of the data values from strings to the appropriate data type for each prop.
|
|
136
|
+
|
|
137
|
+
```svelte
|
|
138
|
+
<ProfileCard
|
|
139
|
+
name="{block.name}"
|
|
140
|
+
age="{parseInt(block.age)}"
|
|
141
|
+
img="{`${assets}/${block.picture}`}"
|
|
142
|
+
birthday="{new Date(block.birthday)}"
|
|
143
|
+
bio="{block.bio}"
|
|
144
|
+
isGood="{block.isGood === 'true'}"
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For example, we're converting Tom's age into a number with JavaScript's [parseInt](https://www.w3schools.com/jsref/jsref_parseint.asp) function ...
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
{parseInt(block.age)}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
... and we're parsing his birthday into a JavaScript Date ...
|
|
155
|
+
|
|
156
|
+
```svelte
|
|
157
|
+
{new Date(block.birthday)}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
... and we're checking if he's a good cat by converting the string into a `true`/`false` boolean:
|
|
161
|
+
|
|
162
|
+
```svelte
|
|
163
|
+
{block.isGood === 'true'}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Especially note** how we're using the `assets` module we talked about in ["Using with the Graphics Kit"](./?path=/docs/guides-using-with-the-graphics-kit--docs) to turn the _relative_ path to Tom's profile pic in our ArchieML doc into an _absolute_ path that will have the full `https://reuters.com...` bit attached.
|
|
167
|
+
|
|
168
|
+
```svelte
|
|
169
|
+
{`${assets}/${block.picture}`}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Sum it all up 🏁
|
|
173
|
+
|
|
174
|
+
Tying your ArchieML doc to a component is as easy as:
|
|
175
|
+
|
|
176
|
+
<Herbie number={1}>Read what props your component needs in these docs.</Herbie>
|
|
177
|
+
|
|
178
|
+
<Herbie number={2}>Write your ArchieML block to match those props.</Herbie>
|
|
179
|
+
|
|
180
|
+
<Herbie number={3}>
|
|
181
|
+
{`Convert the string values in your ArchieML JSON into the data types the
|
|
182
|
+
component's props expect and pass them into your component!`}
|
|
183
|
+
</Herbie>
|
|
184
|
+
|
|
185
|
+
## A slightly more complex example
|
|
186
|
+
|
|
187
|
+
The simple example above is a variation of the majority of the components in these docs. Sometimes, though, a component's props get a little more complex, especially when they require an _array_ of objects with different data.
|
|
188
|
+
|
|
189
|
+
Let's look at another example component:
|
|
190
|
+
|
|
191
|
+
```svelte
|
|
192
|
+
<Timeline
|
|
193
|
+
title="A brief history of BitCoin"
|
|
194
|
+
dates="{[
|
|
195
|
+
{
|
|
196
|
+
date: new Date('1992-01-01'),
|
|
197
|
+
subhed:
|
|
198
|
+
'Cynthia Dwork and Moni Naor come with an idea for "cryptocurrency"',
|
|
199
|
+
img: `${assets}/images/dwork-naor.jpeg`,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
date: new Date('2008-08-18'),
|
|
203
|
+
subhed: 'Domain name for bitcoin.org registered',
|
|
204
|
+
link: 'https://bitcoin.org',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
date: new Date('2013-07-22'),
|
|
208
|
+
subhed: 'The Winklevoss twins buy in',
|
|
209
|
+
img: `${assets}/images/winkle-boys.jpeg`,
|
|
210
|
+
},
|
|
211
|
+
]}"
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Notice our `Timeline` component's `dates` prop takes an array of objects, each with required `date` and `subhed` properties and what appears to be _optional_ `img` or `link` properties.
|
|
216
|
+
|
|
217
|
+
To match that structure in our ArchieML doc, we might write it like ...
|
|
218
|
+
|
|
219
|
+
```yaml
|
|
220
|
+
[blocks]
|
|
221
|
+
# ...
|
|
222
|
+
|
|
223
|
+
type: timeline
|
|
224
|
+
title: A brief history of Bitcoin
|
|
225
|
+
[.dates]
|
|
226
|
+
date: 1992-01-01
|
|
227
|
+
subhed: Cynthia Dwork and Moni Naor ...
|
|
228
|
+
img: images/dwork-naor.jpeg
|
|
229
|
+
|
|
230
|
+
date: 2008-08-18
|
|
231
|
+
subhed: Domain name for bitcoin.org ...
|
|
232
|
+
link: https://bitcoin.org
|
|
233
|
+
|
|
234
|
+
date: 2013-07-22
|
|
235
|
+
subhed: The Winklevoss twins ...
|
|
236
|
+
img: images/winkle-boys.jpeg
|
|
237
|
+
[]
|
|
238
|
+
|
|
239
|
+
# ...
|
|
240
|
+
[]
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Especially notice** the `[.dates] ... []` bit. The `.` on `.dates` creates a _nested_ array on this block.
|
|
244
|
+
|
|
245
|
+
That leaves us with JSON data that looks like ...
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"blocks": [
|
|
250
|
+
{
|
|
251
|
+
"type": "timeline",
|
|
252
|
+
"title": "A brief history of Bitcoin",
|
|
253
|
+
"dates": [
|
|
254
|
+
{
|
|
255
|
+
"date": "1992-01-01",
|
|
256
|
+
"subhed": "Cynthia Dwork and Moni Naor ...",
|
|
257
|
+
"img": "images/dwork-naor.jpeg"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"date": "2008-08-18",
|
|
261
|
+
"subhed": "Domain name for bitcoin.org ...",
|
|
262
|
+
"link": "https://bitcoin.org"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"date": "2013-07-22",
|
|
266
|
+
"subhed": "The Winklevoss twins ...",
|
|
267
|
+
"img": "images/winkle-boys.jpeg"
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Now our data looks OK and all the properties on each date object that go into the Timeline component's `dates` prop appear to match. It may look like we can pass our data straight into our component like ...
|
|
276
|
+
|
|
277
|
+
```svelte
|
|
278
|
+
<!-- ... -->
|
|
279
|
+
{:else if block.type === 'timeline'}
|
|
280
|
+
<Timeline
|
|
281
|
+
title="{block.title}"
|
|
282
|
+
dates="{block.dates}""
|
|
283
|
+
/>
|
|
284
|
+
{/else}
|
|
285
|
+
<!-- ... -->
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
... but we have a couple problems and they all have to do with data types.
|
|
289
|
+
|
|
290
|
+
According to the component's docs, our `date` property's value should be a JavaScript Date and our `img` needs to be an absolute path.
|
|
291
|
+
|
|
292
|
+
But this is a problem we can solve by looping over our JSON data and making a new array where all the values are of the right type for our props.
|
|
293
|
+
|
|
294
|
+
```svelte
|
|
295
|
+
<script>
|
|
296
|
+
// ...
|
|
297
|
+
|
|
298
|
+
const timelineBlock = content.blocks.find((block) => block.type === 'timeline');
|
|
299
|
+
|
|
300
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
301
|
+
const date = new Date(d.date);
|
|
302
|
+
const subhed = d.subhed;
|
|
303
|
+
let img;
|
|
304
|
+
let link;
|
|
305
|
+
|
|
306
|
+
if (d.img) {
|
|
307
|
+
img = `${assets}/${d.img}`;
|
|
308
|
+
}
|
|
309
|
+
if (d.link) {
|
|
310
|
+
link = d.link;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
date,
|
|
315
|
+
subhed,
|
|
316
|
+
img,
|
|
317
|
+
link,
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
</script>
|
|
321
|
+
|
|
322
|
+
<!-- ... -->
|
|
323
|
+
|
|
324
|
+
{:else if block.type === 'timeline'}
|
|
325
|
+
<Timeline
|
|
326
|
+
title="{block.title}"
|
|
327
|
+
dates="{timelineDates}""
|
|
328
|
+
/>
|
|
329
|
+
{/else}
|
|
330
|
+
|
|
331
|
+
<!-- ... -->
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### OK, let's break down that new chunk of code ...
|
|
335
|
+
|
|
336
|
+
First, notice I'm writing this code inside my `script` tags so I can easily just write it with JavaScript.
|
|
337
|
+
|
|
338
|
+
```svelte
|
|
339
|
+
<script>
|
|
340
|
+
// Normal JavaScript is OK here ...
|
|
341
|
+
</script>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Now the first thing I do is get ahold of _just_ my timeline block, leaving behind all the other blocks on my page, using a simple [find](https://www.w3schools.com/jsref/jsref_find.asp) function.
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
// Gets JUST my timeline block from the array of all the blocks that make up my page
|
|
348
|
+
const timelineBlock = content.blocks.find((block) => block.type === 'timeline');
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
With my timeline block in hand, I can loop over all my dates and create a _new_ array with all the values correctly parsed using a basic [map](https://www.w3schools.com/jsref/jsref_map.asp) function.
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
355
|
+
// "d" is each date object in the ArchieML JSON
|
|
356
|
+
// ...
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Inside my `map` I can convert the required properties into new variables matching my component's prop names. In this case, I need to convert the data, but the subhed is just a string ...
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
364
|
+
const date = new Date(d.date);
|
|
365
|
+
const subhed = d.subhed;
|
|
366
|
+
// ...
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
For the optional ones, I'll do a tiny `if` to check if the date has them before adding them to a variable:
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
374
|
+
// ...
|
|
375
|
+
|
|
376
|
+
let img; // Start as an empty variable ...
|
|
377
|
+
let link;
|
|
378
|
+
|
|
379
|
+
// If our date has an image, we'll make it an absolute URL
|
|
380
|
+
if (d.img) {
|
|
381
|
+
img = `${assets}/${d.img}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// If it has a link, we'll just pass that on, as is
|
|
385
|
+
if (d.link) {
|
|
386
|
+
link = d.link;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ...
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
All that done, I can create my new object by returning it from my `map` ...
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
397
|
+
// ...
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
date,
|
|
401
|
+
subhed,
|
|
402
|
+
img,
|
|
403
|
+
link,
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Finally**, I have a new array of dates all sorted for my timeline and I can pass them direcly in to the component's `dates` prop:
|
|
409
|
+
|
|
410
|
+
```svelte
|
|
411
|
+
<script>
|
|
412
|
+
const timelineDates = timelineBlock.dates.map((d) => {
|
|
413
|
+
// ...
|
|
414
|
+
});
|
|
415
|
+
</script>
|
|
416
|
+
|
|
417
|
+
{:else if block.type === 'timeline'}
|
|
418
|
+
<Timeline
|
|
419
|
+
title="{block.title}"
|
|
420
|
+
dates="{timelineDates}""
|
|
421
|
+
/>
|
|
422
|
+
{/else}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
> This is just _one way_ to prepare your data for your component in JavaScript. In many cases, generative AI tools like ChatGPT can also help you write the small bit of code you need. Just provide it a sample of the ArchieML JSON data and describe the format you need to convert it to for your component.
|
|
426
|
+
|
|
427
|
+
### Sum it all up 🏁
|
|
428
|
+
|
|
429
|
+
If your component has more complex props like arrays of objects ...
|
|
430
|
+
|
|
431
|
+
<Herbie
|
|
432
|
+
number={1}
|
|
433
|
+
>{`Write your ArchieML doc with nested arrays to match your component's props.`}</Herbie>
|
|
434
|
+
|
|
435
|
+
<Herbie
|
|
436
|
+
number={2}
|
|
437
|
+
>{`Format your data in JavaScript using simple \`find\` and \`map\` functions.`}</Herbie>
|
|
438
|
+
|
|
439
|
+
<Herbie
|
|
440
|
+
number={3}
|
|
441
|
+
>{`Pass the new formatted array into your components' props instead of the original ArchieML data.`}</Herbie>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
import { parameters } from '../utils/docsPage.js';
|
|
3
|
+
import Highlight from '../docs-components/Highlight/Highlight';
|
|
4
|
+
|
|
5
|
+
<Meta title="Guides/Bare minimum Svelte" parameters={{ ...parameters }} />
|
|
6
|
+
|
|
7
|
+
# Introduction to Svelte components
|
|
8
|
+
|
|
9
|
+
Svelte is a modern JavaScript framework for building parts of HTML pages. Components are the building blocks of Svelte pages, allowing you to create reusable and interactive parts of your page.
|
|
10
|
+
|
|
11
|
+
Here's **the bare minimum** you need to know to start using graphics components:
|
|
12
|
+
|
|
13
|
+
## What's a component?
|
|
14
|
+
|
|
15
|
+
A Svelte <Highlight>component</Highlight> is just a `.svelte` file.
|
|
16
|
+
|
|
17
|
+
A component is usually composed of several parts: JavaScript for managing data, HTML to represent the elements it creates on the page and CSS to style those elements.
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<!-- JavaScript -->
|
|
21
|
+
<script>
|
|
22
|
+
export let imgSrc = 'https://reuters.com/image.jpg';
|
|
23
|
+
export let altText = 'A cat';
|
|
24
|
+
export let caption = 'Mousing all day is hard work ...';
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- HTML -->
|
|
28
|
+
<figure>
|
|
29
|
+
<img src="{imgSrc}" alt="{altText}" />
|
|
30
|
+
<figcaption>{caption}</figcaption>
|
|
31
|
+
</figure>
|
|
32
|
+
|
|
33
|
+
<!-- CSS -->
|
|
34
|
+
<style>
|
|
35
|
+
figure {
|
|
36
|
+
max-width: 600px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
img {
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Importing and using components
|
|
46
|
+
|
|
47
|
+
To use a component, you first need to import it. For example, below is a component called `Button.svelte`:
|
|
48
|
+
|
|
49
|
+
```svelte
|
|
50
|
+
<script>
|
|
51
|
+
export let text = 'Click me';
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<!-- Button.svelte -->
|
|
55
|
+
<button>{text}</button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You can import and use this component in another file:
|
|
59
|
+
|
|
60
|
+
```svelte
|
|
61
|
+
<!-- App.svelte -->
|
|
62
|
+
<script>
|
|
63
|
+
import Button from './Button.svelte';
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<main>
|
|
67
|
+
<h1>Welcome to Svelte</h1>
|
|
68
|
+
<Button text="Press here" />
|
|
69
|
+
</main>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Here, the `Button` component is imported and used in the `App` component.
|
|
73
|
+
|
|
74
|
+
The components in this project are pre-made so you can install, import and use them directly in your page without needing to write them yourself or have a copy of each component in your project.
|
|
75
|
+
|
|
76
|
+
For example, a basic page might look like this:
|
|
77
|
+
|
|
78
|
+
```svelte
|
|
79
|
+
<script>
|
|
80
|
+
// Importing our pre-made components from this project
|
|
81
|
+
import {
|
|
82
|
+
BodyText,
|
|
83
|
+
SiteHeader,
|
|
84
|
+
SiteFooter,
|
|
85
|
+
Headline,
|
|
86
|
+
} from '@reuters-graphics/graphics-components';
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<SiteHeader />
|
|
90
|
+
|
|
91
|
+
<Headline hed="My new story" dek="The beginning of a beautiful page" />
|
|
92
|
+
<BodyText text="Lorem ipsum ..." />
|
|
93
|
+
|
|
94
|
+
<SiteFooter />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Passing data to components with props
|
|
98
|
+
|
|
99
|
+
All graphics components are written to work across different stories. That means they expect to be given the content they'll add (or "render") on the page by you.
|
|
100
|
+
|
|
101
|
+
We pass the data components use to render their part of a page using <Highlight>props</Highlight>.
|
|
102
|
+
|
|
103
|
+
In the `Button.svelte` example above, the `text` prop provides a way to pass the text that will be rendered in the button like this:
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<Button text="Click me!" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
<button>Click me!</button>
|
|
110
|
+
|
|
111
|
+
## Reading component props
|
|
112
|
+
|
|
113
|
+
All graphics components are documented with a code snippet showing how to import them and what props that component has with the kind of data each expects.
|
|
114
|
+
|
|
115
|
+
For example, the default FeaturePhoto example looks like:
|
|
116
|
+
|
|
117
|
+
```svelte
|
|
118
|
+
<script>
|
|
119
|
+
import { FeaturePhoto } from '@reuters-graphics/graphics-components';
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<FeaturePhoto
|
|
123
|
+
src="https://reuters.com/images/myImage.jpg"
|
|
124
|
+
altText="Some alt text"
|
|
125
|
+
caption="A caption"
|
|
126
|
+
width="normal"
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The component's props are `src`, `altTtext`, `caption` and `width` and each shows an example of the type of data that should go into that prop. For example, the `src` prop expects a full URL to the image it will render.
|
|
131
|
+
|
|
132
|
+
## Next steps
|
|
133
|
+
|
|
134
|
+
To learn more about Svelte, you're not short of great tutorials. There are dozens of YouTube videos, online courses and articles to introduce you to more advanced features of the framework. Watch out for our own training courses on the Graphics Team to learn more about Svelte or JavaScript. But in a pinch, each desk has at least one developer who can help you through any problems as you're working with Svelte.
|
|
135
|
+
|
|
136
|
+
All are also welcome to join the "⚙️ Graphics Dev Group" channel in Teams, where we occasionally chat about code tips or tricks.
|
|
137
|
+
|
|
138
|
+
> **NOTE:** As of now, we are using version 4 Svelte syntax. We will soon upgrade to version 5, but for now, we recommend looking for older tutorials online and not the official guides for the latest version.
|