@reuters-graphics/graphics-components 3.5.0 → 3.7.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,347 @@
1
+ <!-- @component `ShareBar` shows a row of share buttons (X, Facebook, LinkedIn, Email, Copy link) and an optional "Purchase Licensing Rights" button, echoing the share toolbar on Reuters.com stories. [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-page-furniture-sharebar--docs) -->
2
+ <script lang="ts">
3
+ import { onDestroy } from 'svelte';
4
+ import Block from '../Block/Block.svelte';
5
+
6
+ interface Props {
7
+ /**
8
+ * Canonical URL to share. When omitted, the live `window.location.href` is
9
+ * read at click time so a shared link reproduces the reader's current view.
10
+ */
11
+ url?: string;
12
+ /** Headline used as the share text and email subject. */
13
+ headline?: string;
14
+ /** Destination for the "Purchase Licensing Rights" button. */
15
+ licensingUrl?: string;
16
+ /** Hide the "Purchase Licensing Rights" button when false. */
17
+ showLicensing?: boolean;
18
+ /** ID on the containing block. */
19
+ id?: string;
20
+ /** Extra classes on the containing block. */
21
+ class?: string;
22
+ }
23
+
24
+ let {
25
+ url,
26
+ headline = '',
27
+ licensingUrl = 'https://www.reutersagency.com/en/licensereuterscontent/?utm_medium=rcom-article-media&utm_campaign=rcom-rcp-lead',
28
+ showLicensing = true,
29
+ id = '',
30
+ class: cls = 'fmy-8',
31
+ }: Props = $props();
32
+
33
+ let copied = $state(false);
34
+ let timer: ReturnType<typeof setTimeout> | null = null;
35
+
36
+ /** Resolve the URL to share, falling back to the live page URL. */
37
+ function shareTarget(): string {
38
+ return url ?? (typeof window !== 'undefined' ? window.location.href : '');
39
+ }
40
+
41
+ /** Open a centered popup for a social share dialog. */
42
+ function openPopup(shareUrl: string) {
43
+ if (typeof window === 'undefined') return;
44
+ window.open(shareUrl, '_blank', 'noopener,noreferrer,width=600,height=600');
45
+ }
46
+
47
+ function shareOnX() {
48
+ const u = encodeURIComponent(shareTarget());
49
+ const text = encodeURIComponent(headline);
50
+ openPopup(`https://twitter.com/intent/tweet?url=${u}&text=${text}`);
51
+ }
52
+
53
+ function shareOnFacebook() {
54
+ const u = encodeURIComponent(shareTarget());
55
+ openPopup(`https://www.facebook.com/sharer/sharer.php?u=${u}`);
56
+ }
57
+
58
+ function shareOnLinkedIn() {
59
+ const u = encodeURIComponent(shareTarget());
60
+ openPopup(`https://www.linkedin.com/sharing/share-offsite/?url=${u}`);
61
+ }
62
+
63
+ function shareViaEmail() {
64
+ if (typeof window === 'undefined') return;
65
+ const subject = encodeURIComponent(headline);
66
+ const body = encodeURIComponent(shareTarget());
67
+ window.location.href = `mailto:?subject=${subject}&body=${body}`;
68
+ }
69
+
70
+ /**
71
+ * Legacy clipboard fallback for contexts where the async Clipboard API is
72
+ * unavailable or blocked — most notably inside a cross-origin iframe (how
73
+ * Reuters embeds graphics), where `navigator.clipboard.writeText` rejects
74
+ * without `clipboard-write` permission. Uses a hidden textarea +
75
+ * `document.execCommand('copy')`, which acts on the document's own selection
76
+ * during the click gesture.
77
+ */
78
+ function legacyCopy(value: string): boolean {
79
+ if (typeof document === 'undefined') return false;
80
+ const textarea = document.createElement('textarea');
81
+ textarea.value = value;
82
+ textarea.setAttribute('readonly', '');
83
+ textarea.style.position = 'fixed';
84
+ textarea.style.top = '-9999px';
85
+ textarea.style.opacity = '0';
86
+ document.body.appendChild(textarea);
87
+ const selection = document.getSelection();
88
+ const previousRange =
89
+ selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
90
+ textarea.select();
91
+ let ok = false;
92
+ try {
93
+ ok = document.execCommand('copy');
94
+ } catch {
95
+ ok = false;
96
+ }
97
+ document.body.removeChild(textarea);
98
+ if (previousRange && selection) {
99
+ selection.removeAllRanges();
100
+ selection.addRange(previousRange);
101
+ }
102
+ return ok;
103
+ }
104
+
105
+ function showCopied() {
106
+ copied = true;
107
+ if (timer) clearTimeout(timer);
108
+ timer = setTimeout(() => {
109
+ copied = false;
110
+ }, 2000);
111
+ }
112
+
113
+ async function copyLink() {
114
+ const value = shareTarget();
115
+ if (!value) return;
116
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
117
+ try {
118
+ await navigator.clipboard.writeText(value);
119
+ showCopied();
120
+ return;
121
+ } catch {
122
+ // Fall through to the legacy path below.
123
+ }
124
+ }
125
+ if (legacyCopy(value)) showCopied();
126
+ }
127
+
128
+ onDestroy(() => {
129
+ if (timer) clearTimeout(timer);
130
+ });
131
+ </script>
132
+
133
+ <Block {id} class="share-bar-block {cls}" width="normal">
134
+ <div class="share-bar" data-testid="ShareBar" dir="ltr">
135
+ <div class="share-buttons" role="group" aria-label="Share this article">
136
+ <button
137
+ type="button"
138
+ class="icon-button"
139
+ aria-label="Share article on X"
140
+ data-testid="share-x"
141
+ onclick={shareOnX}
142
+ >
143
+ <svg viewBox="0 0 30 30" aria-hidden="true" focusable="false">
144
+ <path
145
+ d="M17.923 14.387 25.569 5.5h-1.812l-6.64 7.717L11.817 5.5H5.7l8.018 11.67L5.7 26.49h1.812l7.01-8.15 5.6 8.15h6.116l-8.316-12.102Zm-2.482 2.885-.812-1.162-6.464-9.246h2.783l5.216 7.462.813 1.162 6.78 9.7h-2.782l-5.534-7.915Z"
146
+ />
147
+ </svg>
148
+ </button>
149
+
150
+ <button
151
+ type="button"
152
+ class="icon-button"
153
+ aria-label="Share article on Facebook"
154
+ data-testid="share-facebook"
155
+ onclick={shareOnFacebook}
156
+ >
157
+ <svg viewBox="6 6 20 20" aria-hidden="true" focusable="false">
158
+ <path
159
+ d="M16 6.04001C10.5 6.04001 6 10.53 6 16.06C6 21.06 9.66 25.21 14.44 25.96V18.96H11.9V16.06H14.44V13.85C14.44 11.34 15.93 9.96001 18.22 9.96001C19.31 9.96001 20.45 10.15 20.45 10.15V12.62H19.19C17.95 12.62 17.56 13.39 17.56 14.18V16.06H20.34L19.89 18.96H17.56V25.96C19.9164 25.5879 22.0622 24.3855 23.6099 22.5701C25.1576 20.7546 26.0054 18.4457 26 16.06C26 10.53 21.5 6.04001 16 6.04001Z"
160
+ />
161
+ </svg>
162
+ </button>
163
+
164
+ <button
165
+ type="button"
166
+ class="icon-button"
167
+ aria-label="Share article on LinkedIn"
168
+ data-testid="share-linkedin"
169
+ onclick={shareOnLinkedIn}
170
+ >
171
+ <svg viewBox="0 0 21 21" aria-hidden="true" focusable="false">
172
+ <path
173
+ d="M19.031 0c1.034 0 1.888.807 1.964 1.822L21 1.97V19.03a1.975 1.975 0 0 1-1.822 1.964L19.03 21H1.97a1.975 1.975 0 0 1-1.964-1.822L0 19.03V1.97C0 .935.807.08 1.822.005L1.97 0H19.03ZM6.3 7.875H3.15v10.063H6.3V7.874Zm7.875-.175c-1.575 0-2.538.788-2.975 1.575v-1.4H8.225v10.063h3.15V12.95c0-1.313.175-2.537 1.838-2.537 1.575 0 1.575 1.487 1.575 2.624v4.9h3.15v-5.425c0-2.712-.613-4.812-3.763-4.812ZM4.637 2.8c-1.05 0-1.837.875-1.837 1.838 0 1.05.875 1.837 1.838 1.837 1.05 0 1.837-.787 1.837-1.837 0-1.05-.875-1.838-1.837-1.838Z"
174
+ />
175
+ </svg>
176
+ </button>
177
+
178
+ <button
179
+ type="button"
180
+ class="icon-button"
181
+ aria-label="Email article"
182
+ data-testid="share-email"
183
+ onclick={shareViaEmail}
184
+ >
185
+ <svg viewBox="0 0 21 18" aria-hidden="true" focusable="false">
186
+ <path
187
+ d="M20.125 9c.449 0 .819.347.87.795L21 9.9v5.4c0 1.438-1.093 2.613-2.47 2.695l-.155.005H2.625c-1.398 0-2.54-1.124-2.62-2.541L0 15.3V9.9c0-.497.392-.9.875-.9.449 0 .819.347.87.795l.005.105v5.4c0 .462.338.842.773.894l.102.006h15.75a.884.884 0 0 0 .87-.795l.005-.105V9.9c0-.497.392-.9.875-.9Zm-1.75-9C19.825 0 21 1.209 21 2.7v2.7c0 .33-.175.632-.456.79l-9.625 5.4a.854.854 0 0 1-.838 0L.456 6.19A.904.904 0 0 1 0 5.4V2.7C0 1.209 1.175 0 2.625 0h15.75Zm0 1.8H2.625a.888.888 0 0 0-.875.9v2.166l8.75 4.909 8.75-4.91V2.7a.892.892 0 0 0-.773-.894l-.102-.006Z"
188
+ />
189
+ </svg>
190
+ </button>
191
+
192
+ <span class="icon-button-wrap">
193
+ <button
194
+ type="button"
195
+ class="icon-button"
196
+ aria-label="Copy link"
197
+ data-testid="share-link"
198
+ onclick={copyLink}
199
+ >
200
+ <svg viewBox="0 0 16 16" aria-hidden="true" focusable="false">
201
+ <path
202
+ d="M14.47 1.458a4.163 4.163 0 0 0-5.901 0l-1.28 1.209a.688.688 0 0 0 0 .995c.284.285.71.285.995 0l1.21-1.209a2.878 2.878 0 0 1 3.982 0c1.138 1.067 1.137 2.916.07 3.983l-2.133 2.133-.284.284c-1.28.925-3.058.711-3.982-.569-.214-.284-.711-.355-.996-.142-.285.213-.356.71-.142.996a4.242 4.242 0 0 0 3.413 1.706c.853 0 1.777-.284 2.56-.853.142-.142.284-.284.497-.427l2.134-2.133c1.636-1.636 1.564-4.41-.143-5.973Z"
203
+ />
204
+ <path
205
+ d="m7.644 12.338-1.209 1.209a2.878 2.878 0 0 1-3.982 0c-1.138-1.067-1.138-2.916-.071-3.983l2.133-2.133.284-.284c.64-.427 1.351-.64 2.134-.57.782.143 1.422.498 1.849 1.138.213.284.71.356.996.143.284-.214.355-.711.142-.996-.71-.924-1.707-1.493-2.773-1.636-1.067-.213-2.205.071-3.13.783l-.426.426L1.458 8.57c-1.636 1.706-1.565 4.409.07 6.044.854.782 1.92 1.209 2.987 1.209 1.067 0 2.134-.427 2.987-1.21l1.209-1.208a.688.688 0 0 0 0-.996c-.284-.285-.782-.356-1.067-.07Z"
206
+ />
207
+ </svg>
208
+ </button>
209
+ {#if copied}
210
+ <span class="tooltip" aria-hidden="true">Link copied</span>
211
+ {/if}
212
+ <span class="visually-hidden" role="status" aria-live="polite">
213
+ {copied ? 'Copied to clipboard' : ''}
214
+ </span>
215
+ </span>
216
+ </div>
217
+
218
+ {#if showLicensing}
219
+ <a
220
+ class="licensing"
221
+ href={licensingUrl}
222
+ target="_blank"
223
+ rel="noopener noreferrer"
224
+ dir="ltr"
225
+ data-testid="licensing-link"
226
+ >
227
+ Purchase Licensing Rights
228
+ </a>
229
+ {/if}
230
+ </div>
231
+ </Block>
232
+
233
+ <style>.share-bar {
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: space-between;
237
+ flex-wrap: wrap;
238
+ gap: 0.75rem;
239
+ }
240
+
241
+ .share-buttons {
242
+ display: inline-flex;
243
+ align-items: center;
244
+ gap: 0.5rem;
245
+ flex-wrap: wrap;
246
+ }
247
+
248
+ .icon-button-wrap {
249
+ position: relative;
250
+ display: inline-flex;
251
+ }
252
+
253
+ .icon-button {
254
+ width: 2.5rem;
255
+ height: 2.5rem;
256
+ display: inline-flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ padding: 0;
260
+ border: 1px solid var(--theme-colour-brand-rules, #d3d3d3);
261
+ border-radius: 4px;
262
+ background: var(--theme-colour-background, #fff);
263
+ color: var(--theme-colour-text-secondary, #666);
264
+ cursor: pointer;
265
+ transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
266
+ }
267
+
268
+ .icon-button:hover,
269
+ .icon-button:focus-visible {
270
+ color: var(--theme-colour-text-primary, #121212);
271
+ border-color: var(--theme-colour-text-secondary, #666);
272
+ background: rgba(0, 0, 0, 0.03);
273
+ }
274
+
275
+ .icon-button svg {
276
+ display: block;
277
+ width: 1.125rem;
278
+ height: 1.125rem;
279
+ fill: currentColor;
280
+ }
281
+
282
+ .tooltip {
283
+ position: absolute;
284
+ bottom: calc(100% + 0.375rem);
285
+ left: 50%;
286
+ transform: translateX(-50%);
287
+ padding: 0.25rem 0.5rem;
288
+ border-radius: 4px;
289
+ background: var(--theme-colour-text-primary, #121212);
290
+ color: var(--theme-colour-background, #fff);
291
+ font-size: 0.75rem;
292
+ line-height: 1;
293
+ white-space: nowrap;
294
+ pointer-events: none;
295
+ z-index: 1;
296
+ }
297
+
298
+ .tooltip::after {
299
+ content: "";
300
+ position: absolute;
301
+ top: 100%;
302
+ left: 50%;
303
+ transform: translateX(-50%);
304
+ border: 4px solid transparent;
305
+ border-top-color: var(--theme-colour-text-primary, #121212);
306
+ }
307
+
308
+ .licensing {
309
+ display: inline-flex;
310
+ align-items: center;
311
+ height: 2.5rem;
312
+ padding: 0 1rem;
313
+ border: 1px solid var(--theme-colour-brand-rules, #d3d3d3);
314
+ border-radius: 4px;
315
+ background: var(--theme-colour-background, #fff);
316
+ color: var(--theme-colour-text-secondary, #666);
317
+ font-size: 0.875rem;
318
+ line-height: 1;
319
+ white-space: nowrap;
320
+ text-decoration: none;
321
+ transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
322
+ }
323
+
324
+ .licensing:hover,
325
+ .licensing:focus-visible {
326
+ color: var(--theme-colour-text-primary, #121212);
327
+ border-color: var(--theme-colour-text-secondary, #666);
328
+ background: rgba(0, 0, 0, 0.03);
329
+ }
330
+
331
+ .visually-hidden {
332
+ position: absolute;
333
+ width: 1px;
334
+ height: 1px;
335
+ padding: 0;
336
+ margin: -1px;
337
+ overflow: hidden;
338
+ clip: rect(0, 0, 0, 0);
339
+ white-space: nowrap;
340
+ border: 0;
341
+ }
342
+
343
+ @media (max-width: 767px) {
344
+ .share-bar {
345
+ align-items: flex-start;
346
+ }
347
+ }</style>
@@ -0,0 +1,21 @@
1
+ interface Props {
2
+ /**
3
+ * Canonical URL to share. When omitted, the live `window.location.href` is
4
+ * read at click time so a shared link reproduces the reader's current view.
5
+ */
6
+ url?: string;
7
+ /** Headline used as the share text and email subject. */
8
+ headline?: string;
9
+ /** Destination for the "Purchase Licensing Rights" button. */
10
+ licensingUrl?: string;
11
+ /** Hide the "Purchase Licensing Rights" button when false. */
12
+ showLicensing?: boolean;
13
+ /** ID on the containing block. */
14
+ id?: string;
15
+ /** Extra classes on the containing block. */
16
+ class?: string;
17
+ }
18
+ /** `ShareBar` shows a row of share buttons (X, Facebook, LinkedIn, Email, Copy link) and an optional "Purchase Licensing Rights" button, echoing the share toolbar on Reuters.com stories. [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-page-furniture-sharebar--docs) */
19
+ declare const ShareBar: import("svelte").Component<Props, {}, "">;
20
+ type ShareBar = ReturnType<typeof ShareBar>;
21
+ export default ShareBar;
package/dist/index.d.ts CHANGED
@@ -7,6 +7,9 @@ export { default as BlogPost } from './components/BlogPost/BlogPost.svelte';
7
7
  export { default as BlogTOC } from './components/BlogTOC/BlogTOC.svelte';
8
8
  export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
9
9
  export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte';
10
+ export { default as Bio } from './components/BioBox/Bio.svelte';
11
+ export { default as BioBox } from './components/BioBox/BioBox.svelte';
12
+ export { default as SocialLinks } from './components/BioBox/SocialLinks.svelte';
10
13
  export { default as Block } from './components/Block/Block.svelte';
11
14
  export { default as ClockWall } from './components/ClockWall/ClockWall.svelte';
12
15
  export { default as BodyText } from './components/BodyText/BodyText.svelte';
@@ -37,11 +40,13 @@ export { default as PhotoPack } from './components/PhotoPack/PhotoPack.svelte';
37
40
  export { default as PymChild } from './components/PymChild/PymChild.svelte';
38
41
  export { pym } from './components/PymChild/state.svelte';
39
42
  export { default as ReferralBlock } from './components/ReferralBlock/ReferralBlock.svelte';
43
+ export { default as Referral } from './components/ReferralBlock/Referral.svelte';
40
44
  export { default as ReutersGraphicsLogo } from './components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte';
41
45
  export { default as ReutersLogo } from './components/ReutersLogo/ReutersLogo.svelte';
42
46
  export { default as Scroller } from './components/Scroller/Scroller.svelte';
43
47
  export { default as SearchInput } from './components/SearchInput/SearchInput.svelte';
44
48
  export { default as SEO } from './components/SEO/SEO.svelte';
49
+ export { default as ShareBar } from './components/ShareBar/ShareBar.svelte';
45
50
  export { default as SimpleTimeline } from './components/SimpleTimeline/SimpleTimeline.svelte';
46
51
  export { default as SiteFooter } from './components/SiteFooter/SiteFooter.svelte';
47
52
  export { default as SiteHeader } from './components/SiteHeader/SiteHeader.svelte';
@@ -59,5 +64,8 @@ export { default as ToolsHeader } from './components/ToolsHeader/ToolsHeader.sve
59
64
  export { default as Video } from './components/Video/Video.svelte';
60
65
  export { default as Visible } from './components/Visible/Visible.svelte';
61
66
  export type { ContainerWidth, HeadlineSize, ScrollerVideoInstance, } from './components/@types/global';
67
+ export type { ReferralItem } from './components/ReferralBlock/types';
68
+ export type { LinkTarget } from './components/ReferralBlock/types';
69
+ export type { Author, SocialLink, SocialPlatform, } from './components/BioBox/types';
62
70
  export type { GeocodeOptions, GeocodeFeature, GeocodeFeatureType, } from './components/Geocoder/geocode';
63
71
  export type { LegendMode, LegendFormatter, LegendItem, LegendStop, LegendTick, LegendMidpoint, LegendSymbolItem, LegendNoData, } from './components/Legend/Legend.svelte';
package/dist/index.js CHANGED
@@ -10,6 +10,9 @@ export { default as BlogPost } from './components/BlogPost/BlogPost.svelte';
10
10
  export { default as BlogTOC } from './components/BlogTOC/BlogTOC.svelte';
11
11
  export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
12
12
  export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte';
13
+ export { default as Bio } from './components/BioBox/Bio.svelte';
14
+ export { default as BioBox } from './components/BioBox/BioBox.svelte';
15
+ export { default as SocialLinks } from './components/BioBox/SocialLinks.svelte';
13
16
  export { default as Block } from './components/Block/Block.svelte';
14
17
  export { default as ClockWall } from './components/ClockWall/ClockWall.svelte';
15
18
  export { default as BodyText } from './components/BodyText/BodyText.svelte';
@@ -40,11 +43,13 @@ export { default as PhotoPack } from './components/PhotoPack/PhotoPack.svelte';
40
43
  export { default as PymChild } from './components/PymChild/PymChild.svelte';
41
44
  export { pym } from './components/PymChild/state.svelte';
42
45
  export { default as ReferralBlock } from './components/ReferralBlock/ReferralBlock.svelte';
46
+ export { default as Referral } from './components/ReferralBlock/Referral.svelte';
43
47
  export { default as ReutersGraphicsLogo } from './components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte';
44
48
  export { default as ReutersLogo } from './components/ReutersLogo/ReutersLogo.svelte';
45
49
  export { default as Scroller } from './components/Scroller/Scroller.svelte';
46
50
  export { default as SearchInput } from './components/SearchInput/SearchInput.svelte';
47
51
  export { default as SEO } from './components/SEO/SEO.svelte';
52
+ export { default as ShareBar } from './components/ShareBar/ShareBar.svelte';
48
53
  export { default as SimpleTimeline } from './components/SimpleTimeline/SimpleTimeline.svelte';
49
54
  export { default as SiteFooter } from './components/SiteFooter/SiteFooter.svelte';
50
55
  export { default as SiteHeader } from './components/SiteHeader/SiteHeader.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reuters-graphics/graphics-components",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "homepage": "https://reuters-graphics.github.io/graphics-components",