@reuters-graphics/graphics-components 3.2.1 → 3.3.0

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.
@@ -0,0 +1,101 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** Title of the blog post */
4
+ title: string;
5
+ /**
6
+ * A sluggable title of the blog post.
7
+ *
8
+ * **Important:** Do not change this title after publishing the post. Changes will break
9
+ * published links to the post.
10
+ */
11
+ slugTitle: string;
12
+ /**
13
+ * Base path prepended to the copied URL, e.g. "/graphics", usually gotten from base store in SvelteKit.
14
+ */
15
+ base: string;
16
+ /** Array of author names, which will be slugified to create links to Reuters author pages */
17
+ authors: string[];
18
+ /** Publish time as a datetime string. */
19
+ publishTime: string;
20
+ /** Update time as a datetime string. */
21
+ updateTime?: string;
22
+ /** Add an id to target post headline with custom CSS. */
23
+ id?: string;
24
+ /** Add extra classes to target post headline with custom CSS. */
25
+ cls?: string;
26
+ /**
27
+ * If the post is the last on the page, remove the dividing rule used to separate posts.
28
+ */
29
+ isLastPost?: boolean;
30
+ children?: import('svelte').Snippet;
31
+ }
32
+
33
+ let {
34
+ title = 'Reuters Graphics blog post',
35
+ slugTitle = 'Reuters Graphics blog post',
36
+ base = '',
37
+ authors = [],
38
+ publishTime = '',
39
+ updateTime = '',
40
+ id = '',
41
+ cls = '',
42
+ isLastPost = false,
43
+ children,
44
+ }: Props = $props();
45
+
46
+ import PostHeadline from './PostHeadline.svelte';
47
+ import { slugify } from '../../utils';
48
+ import { getShortDate } from './utils';
49
+
50
+ let shortPubDate = $derived(getShortDate(publishTime));
51
+ </script>
52
+
53
+ <article id={slugify(slugTitle)} class="post-anchor">
54
+ <PostHeadline
55
+ hed={title}
56
+ sluggableHed={slugTitle}
57
+ {base}
58
+ {authors}
59
+ {publishTime}
60
+ {updateTime}
61
+ {id}
62
+ {cls}
63
+ />
64
+ <a href="{base}/{shortPubDate}/{slugify(slugTitle)}/" hidden>
65
+ {title}
66
+ </a>
67
+ {@render children?.()}
68
+ </article>
69
+
70
+ {#if !isLastPost}
71
+ <div class="divider"></div>
72
+ {/if}
73
+
74
+ <style>/* Generated from
75
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
76
+ */
77
+ /* Generated from
78
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
79
+ */
80
+ /* Scales by 1.125 */
81
+ .divider {
82
+ height: 1px;
83
+ max-width: 594px;
84
+ width: calc(100% - 40px);
85
+ margin: 4rem auto;
86
+ border-top: solid 1px var(--tr-muted-grey);
87
+ }
88
+
89
+ .post-anchor {
90
+ scroll-margin-top: 125px;
91
+ }
92
+ @media (min-width: 768px) {
93
+ .post-anchor {
94
+ scroll-margin-top: 150px;
95
+ }
96
+ }
97
+ @media (min-width: 1200px) {
98
+ .post-anchor {
99
+ scroll-margin-top: 175px;
100
+ }
101
+ }</style>
@@ -0,0 +1,33 @@
1
+ interface Props {
2
+ /** Title of the blog post */
3
+ title: string;
4
+ /**
5
+ * A sluggable title of the blog post.
6
+ *
7
+ * **Important:** Do not change this title after publishing the post. Changes will break
8
+ * published links to the post.
9
+ */
10
+ slugTitle: string;
11
+ /**
12
+ * Base path prepended to the copied URL, e.g. "/graphics", usually gotten from base store in SvelteKit.
13
+ */
14
+ base: string;
15
+ /** Array of author names, which will be slugified to create links to Reuters author pages */
16
+ authors: string[];
17
+ /** Publish time as a datetime string. */
18
+ publishTime: string;
19
+ /** Update time as a datetime string. */
20
+ updateTime?: string;
21
+ /** Add an id to target post headline with custom CSS. */
22
+ id?: string;
23
+ /** Add extra classes to target post headline with custom CSS. */
24
+ cls?: string;
25
+ /**
26
+ * If the post is the last on the page, remove the dividing rule used to separate posts.
27
+ */
28
+ isLastPost?: boolean;
29
+ children?: import('svelte').Snippet;
30
+ }
31
+ declare const BlogPost: import("svelte").Component<Props, {}, "">;
32
+ type BlogPost = ReturnType<typeof BlogPost>;
33
+ export default BlogPost;
@@ -0,0 +1,131 @@
1
+ <script lang="ts">
2
+ import Fa from 'svelte-fa';
3
+ import { slugify } from '../../utils';
4
+ import { getShortDate } from './utils';
5
+ import { faLink } from '@fortawesome/free-solid-svg-icons';
6
+
7
+ interface Props {
8
+ /** The published date of the post. */
9
+ publishedDate?: string;
10
+ /** The heading of the post. */
11
+ hed?: string;
12
+ /** Base path prepended to the copied URL, e.g. "/graphics". */
13
+ base: string;
14
+ /** The ARIA label for the copy link button. */
15
+ ariaLabel?: string;
16
+ /** The message to display before copying the link. */
17
+ copyMessageBefore?: string;
18
+ /** The message to display after copying the link. */
19
+ copyMessageAfter?: string;
20
+ }
21
+
22
+ let {
23
+ publishedDate = '',
24
+ hed = '',
25
+ base = '',
26
+ ariaLabel = 'Copy link to this post',
27
+ copyMessageBefore = 'Copy link',
28
+ copyMessageAfter = 'Copied',
29
+ }: Props = $props();
30
+
31
+ let hovering = $state(false);
32
+ let clicked = $state(false);
33
+
34
+ let publishDate = $derived(getShortDate(publishedDate));
35
+ </script>
36
+
37
+ <div class="link-container pl-0.5">
38
+ <div class="mask"></div>
39
+ <button
40
+ class:clicked
41
+ aria-label={ariaLabel}
42
+ role="link"
43
+ onclick={(e) => {
44
+ e.preventDefault();
45
+ clicked = true;
46
+ navigator.clipboard.writeText(
47
+ `${window.location.origin}${base}/${publishDate}/${slugify(hed)}/`
48
+ );
49
+ setTimeout(() => {
50
+ clicked = false;
51
+ }, 2000);
52
+ }}
53
+ onmouseenter={() => {
54
+ hovering = true;
55
+ }}
56
+ onmouseleave={() => {
57
+ hovering = false;
58
+ }}
59
+ >
60
+ <Fa icon={faLink} />
61
+ </button>
62
+
63
+ {#if !clicked}
64
+ <div class="message" class:active={hovering}>{copyMessageBefore}</div>
65
+ {:else}
66
+ <div class="message" class:active={clicked}>{copyMessageAfter}</div>
67
+ {/if}
68
+ </div>
69
+
70
+ <style>/* Generated from
71
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
72
+ */
73
+ /* Generated from
74
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
75
+ */
76
+ /* Scales by 1.125 */
77
+ .link-container {
78
+ display: inline-flex;
79
+ justify-items: center;
80
+ align-items: center;
81
+ overflow-y: hidden;
82
+ position: relative;
83
+ }
84
+ .link-container .message {
85
+ background-color: var(--theme-colour-text-primary);
86
+ color: var(--theme-colour-background);
87
+ font-size: var(--theme-font-size-xxs);
88
+ font-weight: 500;
89
+ padding-inline-start: clamp(0.88rem, 0.83rem + 0.21vw, 1rem);
90
+ padding-inline-end: clamp(0.56rem, 0.52rem + 0.21vw, 0.69rem);
91
+ line-height: 1.7;
92
+ display: inline-flex;
93
+ border-top-right-radius: 20px;
94
+ border-bottom-right-radius: 20px;
95
+ transform: translateX(-100px);
96
+ transition: all 0.15s ease;
97
+ opacity: 0;
98
+ }
99
+ .link-container .message.active {
100
+ transform: translateX(-10px);
101
+ opacity: 1;
102
+ }
103
+
104
+ .mask {
105
+ background-color: var(--theme-colour-background);
106
+ display: inline-block;
107
+ height: 30px;
108
+ width: 20px;
109
+ position: absolute;
110
+ left: 0;
111
+ top: 0;
112
+ z-index: 1;
113
+ }
114
+
115
+ button {
116
+ z-index: 1;
117
+ background-color: var(--theme-colour-background);
118
+ border-radius: 50%;
119
+ font-size: var(--theme-font-size-sm);
120
+ aspect-ratio: 1;
121
+ display: inline-flex;
122
+ justify-items: center;
123
+ align-items: center;
124
+ border: 1px solid var(--theme-colour-background);
125
+ color: var(--tr-light-grey);
126
+ cursor: pointer;
127
+ }
128
+ button:hover, button.clicked {
129
+ color: var(--tr-dark-grey);
130
+ border: 1px solid var(--tr-dark-grey);
131
+ }</style>
@@ -0,0 +1,17 @@
1
+ interface Props {
2
+ /** The published date of the post. */
3
+ publishedDate?: string;
4
+ /** The heading of the post. */
5
+ hed?: string;
6
+ /** Base path prepended to the copied URL, e.g. "/graphics". */
7
+ base: string;
8
+ /** The ARIA label for the copy link button. */
9
+ ariaLabel?: string;
10
+ /** The message to display before copying the link. */
11
+ copyMessageBefore?: string;
12
+ /** The message to display after copying the link. */
13
+ copyMessageAfter?: string;
14
+ }
15
+ declare const CopyLink: import("svelte").Component<Props, {}, "">;
16
+ type CopyLink = ReturnType<typeof CopyLink>;
17
+ export default CopyLink;
@@ -0,0 +1,184 @@
1
+ <script lang="ts">
2
+ import { apdate } from 'journalize';
3
+ import CopyLink from './CopyLink.svelte';
4
+ import { slugify } from '../../utils';
5
+ import Kinesis from '../KinesisLogo/KinesisLogo.svelte';
6
+ import Block from '../Block/Block.svelte';
7
+ import Byline from '../Byline/Byline.svelte';
8
+
9
+ interface Props {
10
+ /** Headline of the blog post */
11
+ hed: string;
12
+ /**
13
+ * A sluggable headline of the blog post.
14
+ *
15
+ * **Important:** Do not change this headline after publishing the post. Changes will break
16
+ * published links to the post.
17
+ */
18
+ sluggableHed: string;
19
+ /** Base path prepended to the copied URL, e.g. "/graphics". */
20
+ base: string;
21
+ /** Array of author names, which will be slugified to create links to Reuters author pages */
22
+ authors: string[];
23
+ /** Publish time as a datetime string. */
24
+ publishTime: string;
25
+ /** Update time as a datetime string. */
26
+ updateTime?: string;
27
+ /** Add an id to target with custom CSS. */
28
+ id?: string;
29
+ /** Add extra classes to target with custom CSS. */
30
+ cls?: string;
31
+ }
32
+
33
+ let {
34
+ hed = 'Reuters Graphics blog post',
35
+ sluggableHed = 'Reuters Graphics blog post',
36
+ base = '',
37
+ authors = [],
38
+ publishTime = '',
39
+ updateTime = '',
40
+ id = '',
41
+ cls = '',
42
+ }: Props = $props();
43
+
44
+ const isValidDate = (datetime: string) => {
45
+ if (!datetime) return false;
46
+ if (!Date.parse(datetime)) return false;
47
+ return true;
48
+ };
49
+
50
+ const formatTime = (datetime: string) =>
51
+ new Date(datetime).toLocaleTimeString([], {
52
+ hour: 'numeric',
53
+ minute: '2-digit',
54
+ timeZoneName: 'short',
55
+ });
56
+
57
+ const areSameDay = (first: Date, second: Date) =>
58
+ first.getFullYear() === second.getFullYear() &&
59
+ first.getMonth() === second.getMonth() &&
60
+ first.getDate() === second.getDate();
61
+
62
+ let hedMain = $derived(hed.split(' ').slice(0, -1).join(' '));
63
+ let hedWidow = $derived(hed.split(' ').slice(-1).join(' '));
64
+ </script>
65
+
66
+ <Block {id} class="headline-container post-headline fmt-7 {cls}" width="normal">
67
+ <header class="headline">
68
+ <div class="kinesis">
69
+ <Kinesis width="30px" colour="#fff" />
70
+ </div>
71
+ <div class="dateline-container">
72
+ {#if isValidDate(publishTime)}
73
+ <div class="published-time font-bold whitespace-nowrap inline-block">
74
+ <time class="uppercase" datetime={publishTime}>
75
+ {#if isValidDate(updateTime)}
76
+ {apdate(new Date(publishTime))}
77
+ {:else}
78
+ {apdate(new Date(publishTime))}&nbsp;&nbsp;{formatTime(
79
+ publishTime
80
+ )}
81
+ {/if}
82
+ </time>
83
+ </div>
84
+ {/if}
85
+ {#if isValidDate(publishTime) && isValidDate(updateTime)}
86
+ <div class="updated-time font-bold whitespace-nowrap block">
87
+ <span>Updated</span>
88
+ <time class="uppercase" datetime={updateTime}>
89
+ {#if areSameDay(new Date(publishTime), new Date(updateTime))}
90
+ {formatTime(updateTime)}
91
+ {:else}
92
+ {apdate(new Date(updateTime))}&nbsp;&nbsp;{formatTime(updateTime)}
93
+ {/if}
94
+ </time>
95
+ </div>
96
+ {/if}
97
+ </div>
98
+ <div class="title">
99
+ {#if hed}
100
+ <h2>
101
+ <a href="#{slugify(sluggableHed)}">
102
+ {hedMain}
103
+ <span
104
+ >{hedWidow}
105
+ <CopyLink
106
+ hed={sluggableHed}
107
+ publishedDate={publishTime}
108
+ {base}
109
+ /></span
110
+ >
111
+ </a>
112
+ </h2>
113
+ {/if}
114
+ </div>
115
+ <Byline {authors} />
116
+ </header>
117
+ </Block>
118
+
119
+ <style>@charset "UTF-8";
120
+ /* Generated from
121
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
122
+ */
123
+ /* Generated from
124
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
125
+ */
126
+ /* Scales by 1.125 */
127
+ .headline {
128
+ position: relative;
129
+ }
130
+ .headline .kinesis {
131
+ position: absolute;
132
+ top: -12px;
133
+ left: -50px;
134
+ background-color: #d64000;
135
+ border-radius: 50%;
136
+ width: 40px;
137
+ height: 40px;
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ }
142
+ @media (max-width: 860px) {
143
+ .headline .kinesis {
144
+ display: none;
145
+ }
146
+ }
147
+
148
+ .dateline-container {
149
+ font-family: var(--theme-font-family-note);
150
+ color: var(--theme-colour-text-secondary);
151
+ font-size: var(--theme-font-size-xs);
152
+ line-height: 1.3;
153
+ font-weight: 400;
154
+ margin-block-start: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
155
+ margin-block-end: clamp(0.56rem, 0.52rem + 0.21vw, 0.69rem);
156
+ margin-block-start: 0;
157
+ margin-block-end: 0;
158
+ text-align: left;
159
+ color: var(--theme-colour-accent);
160
+ }
161
+ @media (min-width: 510px) {
162
+ .dateline-container .updated-time {
163
+ display: inline-block;
164
+ }
165
+ .dateline-container .updated-time:before {
166
+ content: "·";
167
+ margin: 0 5px 0 3px;
168
+ }
169
+ }
170
+
171
+ h2 {
172
+ margin-block-start: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
173
+ margin-block-end: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
174
+ font-weight: 900;
175
+ }
176
+ h2 a {
177
+ color: inherit;
178
+ text-decoration: none;
179
+ }
180
+ h2 span {
181
+ white-space: nowrap;
182
+ display: inline-flex;
183
+ align-items: baseline;
184
+ }</style>
@@ -0,0 +1,26 @@
1
+ interface Props {
2
+ /** Headline of the blog post */
3
+ hed: string;
4
+ /**
5
+ * A sluggable headline of the blog post.
6
+ *
7
+ * **Important:** Do not change this headline after publishing the post. Changes will break
8
+ * published links to the post.
9
+ */
10
+ sluggableHed: string;
11
+ /** Base path prepended to the copied URL, e.g. "/graphics". */
12
+ base: string;
13
+ /** Array of author names, which will be slugified to create links to Reuters author pages */
14
+ authors: string[];
15
+ /** Publish time as a datetime string. */
16
+ publishTime: string;
17
+ /** Update time as a datetime string. */
18
+ updateTime?: string;
19
+ /** Add an id to target with custom CSS. */
20
+ id?: string;
21
+ /** Add extra classes to target with custom CSS. */
22
+ cls?: string;
23
+ }
24
+ declare const PostHeadline: import("svelte").Component<Props, {}, "">;
25
+ type PostHeadline = ReturnType<typeof PostHeadline>;
26
+ export default PostHeadline;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Converts a date string to a short date format.
3
+ * @param d - The date string to be converted.
4
+ * @returns The short date format string.
5
+ */
6
+ export declare const getShortDate: (d: string) => string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Converts a date string to a short date format.
3
+ * @param d - The date string to be converted.
4
+ * @returns The short date format string.
5
+ */
6
+ export const getShortDate = (d) => new Date(d).toISOString().split('T')[0];