@reuters-graphics/graphics-components 3.0.19 → 3.0.21

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.
@@ -1,5 +1,3 @@
1
- // Reuters Google Tag ID
2
- const GOOGLE_TAG_ID = 'G-WBSR7WLTGD';
3
1
  export default () => {
4
2
  try {
5
3
  window.dataLayer = window.dataLayer || [];
@@ -10,10 +8,6 @@ export default () => {
10
8
  window.dataLayer.push(arguments);
11
9
  };
12
10
  window.gtag('js', new Date());
13
- // config event registers a pageview by default
14
- window.gtag('config', GOOGLE_TAG_ID, {
15
- send_page_view: false,
16
- });
17
11
  registerPageview();
18
12
  }
19
13
  }
@@ -8,9 +8,22 @@ import * as PhotoPackStories from './PhotoPack.stories.svelte';
8
8
 
9
9
  The `PhotoPack` component makes simple photo grids with custom layouts at various breakpoints.
10
10
 
11
- `images` are defined with their src, alt text, captions and an optional `maxHeight`, which ensures that the images are no taller than that height in any layout.
11
+ `images` are defined with their src, alt text, captions and an optional `maxHeight`, which ensures that an image is no taller than that height in any layout.
12
12
 
13
- `layouts` describe how images will be laid out at different breakpoints. The default layout is one photo per row, stacked vertically -- i.e. mobile layout. You can customise the layouts and group images into `rows` above a certain `breakpoint` by specifying the number of images that should go in that row. For example:
13
+ ```javascript
14
+ const images = [
15
+ {
16
+ src: 'https://...',
17
+ altText: 'Alt text',
18
+ caption: 'Lorem ipsum. REUTERS/Photog',
19
+ // Optional max-height of images across all layouts
20
+ maxHeight: 800,
21
+ },
22
+ // ...
23
+ ];
24
+ ```
25
+
26
+ `layouts` optionally define how images are laid out at different breakpoints. You can customise the layouts and group images into `rows` above a certain `breakpoint` by specifying the number of images that should go in that row. For example:
14
27
 
15
28
  ```javascript
16
29
  const layouts = [
@@ -23,6 +36,8 @@ const layouts = [
23
36
 
24
37
  ... tells the component that when the `PhotoPack` container is 450 pixels or wider, it should group the 4 images in 3 rows: 1 in the first, 2 in the second and 1 in the last.
25
38
 
39
+ If you don't specify any layouts, the component will use a default responsive layout based on the number of images in your pack.
40
+
26
41
  You can define as many layouts for as many images as you like.
27
42
 
28
43
  ```svelte
@@ -123,3 +138,40 @@ gap: 10 # Optional; must be a number.
123
138
  ```
124
139
 
125
140
  <Canvas of={PhotoPackStories.ArchieML} />
141
+
142
+ ## Smart default layouts
143
+
144
+ If you don't specify the `layouts` prop, `PhotoPack` will automatically generate responsive layouts based on the number of images and the container width.
145
+
146
+ **How it works:**
147
+
148
+ - **Desktop** (1024px+): Number of images per row depends on container width:
149
+ - `normal`: max 2 per row
150
+ - `wide` / `wider`: max 3 per row
151
+ - `widest` / `fluid`: max 4 per row
152
+ - **Tablet** (768px+): Always max 2 per row
153
+ - **Mobile** (below 768px): 1 per row
154
+
155
+ The smart defaults use a **bottom-heavy distribution**, meaning earlier rows have fewer images (making them larger and more prominent), while later rows have more images.
156
+
157
+ **Examples:**
158
+
159
+ - 5 images, `wide` container, desktop: `[2, 3]` (2 in first row, 3 in second)
160
+ - 7 images, `widest` container, desktop: `[3, 4]` (3 in first row, 4 in second)
161
+ - 4 images, any container, desktop: `[2, 2]` (evenly distributed)
162
+
163
+ ```svelte
164
+ <script>
165
+ import { PhotoPack } from '@reuters-graphics/graphics-components';
166
+
167
+ const images = [
168
+ { src: `${assets}/image1.jpg`, altText: 'Photo 1', caption: 'Caption 1' },
169
+ { src: `${assets}/image2.jpg`, altText: 'Photo 2', caption: 'Caption 2' },
170
+ { src: `${assets}/image3.jpg`, altText: 'Photo 3', caption: 'Caption 3' },
171
+ { src: `${assets}/image4.jpg`, altText: 'Photo 4', caption: 'Caption 4' },
172
+ ];
173
+ </script>
174
+
175
+ <!-- No layouts prop = smart defaults! -->
176
+ <PhotoPack {images} width="wide" />
177
+ ```
@@ -19,6 +19,12 @@
19
19
  </script>
20
20
 
21
21
  <script lang="ts">
22
+ import type { ComponentProps } from 'svelte';
23
+
24
+ type SmartDefaultsArgs = Omit<ComponentProps<typeof PhotoPack>, 'images'> & {
25
+ imageCount: number;
26
+ };
27
+
22
28
  const defaultImages = [
23
29
  {
24
30
  src: 'https://graphics.thomsonreuters.com/cdn/django-tools/media/graphics-gallery/galleries/world-cup-2022/spain-germany-11-27/2022-11-27T194630Z_544493697_UP1E.jpeg',
@@ -51,7 +57,7 @@
51
57
  { breakpoint: 750, rows: [1, 3] },
52
58
  ];
53
59
 
54
- const archieMLImages = [
60
+ const allImages = [
55
61
  {
56
62
  src: 'https://graphics.thomsonreuters.com/cdn/django-tools/media/graphics-gallery/galleries/world-cup-2022/spain-germany-11-27/2022-11-27T194630Z_544493697_UP1E.jpeg',
57
63
  caption:
@@ -88,7 +94,7 @@
88
94
  width: 'wide' as const,
89
95
  textWidth: 'normal' as const,
90
96
  gap: Number('15'),
91
- images: archieMLImages,
97
+ images: allImages.slice(0, 5),
92
98
  layouts: [
93
99
  { breakpoint: 750, rows: [2, 3] },
94
100
  { breakpoint: 450, rows: [1, 2, 2] },
@@ -106,3 +112,29 @@
106
112
  }}
107
113
  />
108
114
  <Story name="ArchieML" args={archieMLBlock} />
115
+
116
+ <Story
117
+ name="Smart layouts"
118
+ args={{
119
+ width: 'wide',
120
+ textWidth: 'normal',
121
+ // @ts-expect-error - imageCount is a custom arg for this story's template
122
+ imageCount: 4,
123
+ }}
124
+ argTypes={{
125
+ // @ts-expect-error - imageCount is a custom arg for this story's template
126
+ imageCount: {
127
+ control: { type: 'range', min: 2, max: 5, step: 1 },
128
+ description:
129
+ 'Number of images to display (demonstrates smart default layouts)',
130
+ },
131
+ }}
132
+ >
133
+ {#snippet children(args)}
134
+ {@const { imageCount, ...photoPackProps } = args as SmartDefaultsArgs}
135
+ <PhotoPack
136
+ {...photoPackProps}
137
+ images={allImages.slice(0, imageCount || 4)}
138
+ />
139
+ {/snippet}
140
+ </Story>
@@ -6,7 +6,7 @@
6
6
 
7
7
  // Utils
8
8
  import { random4 } from '../../utils';
9
- import { groupRows } from './utils';
9
+ import { groupRows, generateDefaultLayouts } from './utils';
10
10
 
11
11
  // Types
12
12
  export interface Image {
@@ -33,7 +33,7 @@
33
33
  /** Add an ID to target with SCSS. Should be unique from all other elements. */
34
34
  id?: string;
35
35
  /** Add a class to target with SCSS. */
36
- class: string;
36
+ class?: string;
37
37
  /** Width of the component within the text well: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid' */
38
38
  width: ContainerWidth;
39
39
  /** Set a different width for captions within the text well. For example, "normal" to keep captions inline with the rest of the text well.
@@ -60,10 +60,14 @@
60
60
  *
61
61
  * @NOTE - We can't use `sort` directly on the array because it mutates the original array; we can't update a state inside a derived expression: https://svelte.dev/docs/svelte/runtime-errors#Client-errors-state_unsafe_mutation
62
62
  *
63
- * So, we need to use `toSorted` instead.
63
+ * We avoid `toSorted` because it's not supported on older iPhones. Instead, we create a shallow copy using the spread operator and then sort that copy.
64
+ *
65
+ * If no layouts are provided, we generate smart defaults based on the container width and number of images.
64
66
  */
65
67
  let sortedLayouts = $derived(
66
- layouts?.toSorted((a, b) => (a.breakpoint < b.breakpoint ? 1 : -1))
68
+ layouts ?
69
+ [...layouts].sort((a, b) => (a.breakpoint < b.breakpoint ? 1 : -1))
70
+ : generateDefaultLayouts(images.length, width)
67
71
  );
68
72
 
69
73
  let layout = $derived(
@@ -19,7 +19,7 @@ interface Props {
19
19
  /** Add an ID to target with SCSS. Should be unique from all other elements. */
20
20
  id?: string;
21
21
  /** Add a class to target with SCSS. */
22
- class: string;
22
+ class?: string;
23
23
  /** Width of the component within the text well: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid' */
24
24
  width: ContainerWidth;
25
25
  /** Set a different width for captions within the text well. For example, "normal" to keep captions inline with the rest of the text well.
@@ -1,2 +1,30 @@
1
1
  import type { Image, Layout } from './PhotoPack.svelte';
2
+ export declare const DESKTOP_BREAKPOINT = 1024;
3
+ export declare const TABLET_BREAKPOINT = 768;
4
+ /**
5
+ * Generates a smart layout for a given number of images with bottom-heavy distribution.
6
+ * Avoids single-image rows by redistributing when necessary.
7
+ *
8
+ * @param imageCount - Total number of images
9
+ * @param maxPerRow - Maximum images per row
10
+ * @param breakpoint - Breakpoint threshold for this layout
11
+ * @returns Layout object with rows array
12
+ */
13
+ export declare const generateSmartLayout: (imageCount: number, maxPerRow: number, breakpoint: number) => Layout;
14
+ type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
15
+ /**
16
+ * Generates smart default layouts for desktop and tablet breakpoints.
17
+ * Mobile (below TABLET_BREAKPOINT) automatically shows 1 image per row.
18
+ *
19
+ * Max images per row by container width:
20
+ * - normal: 2
21
+ * - wide/wider: 3
22
+ * - widest/fluid: 4
23
+ *
24
+ * @param imageCount - Total number of images
25
+ * @param width - Container width setting
26
+ * @returns Array of 2 layouts [desktop, tablet]
27
+ */
28
+ export declare const generateDefaultLayouts: (imageCount: number, width: ContainerWidth) => Layout[];
2
29
  export declare const groupRows: (images: Image[], layout?: Layout) => Image[][];
30
+ export {};
@@ -1,3 +1,63 @@
1
+ // Breakpoint constants for smart default layouts
2
+ export const DESKTOP_BREAKPOINT = 1024;
3
+ export const TABLET_BREAKPOINT = 768;
4
+ /**
5
+ * Generates a smart layout for a given number of images with bottom-heavy distribution.
6
+ * Avoids single-image rows by redistributing when necessary.
7
+ *
8
+ * @param imageCount - Total number of images
9
+ * @param maxPerRow - Maximum images per row
10
+ * @param breakpoint - Breakpoint threshold for this layout
11
+ * @returns Layout object with rows array
12
+ */
13
+ export const generateSmartLayout = (imageCount, maxPerRow, breakpoint) => {
14
+ // Handle edge cases
15
+ if (imageCount === 0)
16
+ return { breakpoint, rows: [] };
17
+ if (imageCount === 1)
18
+ return { breakpoint, rows: [1] };
19
+ const fullRows = Math.floor(imageCount / maxPerRow);
20
+ const remainder = imageCount % maxPerRow;
21
+ let rows = [];
22
+ if (remainder === 0) {
23
+ // Perfect division: all rows have maxPerRow
24
+ rows = Array(fullRows).fill(maxPerRow);
25
+ }
26
+ else {
27
+ // Bottom-heavy: smaller row at top, larger rows below
28
+ // This makes early images larger (fewer per row = bigger display size)
29
+ rows = [remainder, ...Array(fullRows).fill(maxPerRow)];
30
+ }
31
+ return {
32
+ breakpoint,
33
+ rows,
34
+ };
35
+ };
36
+ /**
37
+ * Generates smart default layouts for desktop and tablet breakpoints.
38
+ * Mobile (below TABLET_BREAKPOINT) automatically shows 1 image per row.
39
+ *
40
+ * Max images per row by container width:
41
+ * - normal: 2
42
+ * - wide/wider: 3
43
+ * - widest/fluid: 4
44
+ *
45
+ * @param imageCount - Total number of images
46
+ * @param width - Container width setting
47
+ * @returns Array of 2 layouts [desktop, tablet]
48
+ */
49
+ export const generateDefaultLayouts = (imageCount, width) => {
50
+ // Map container width to max images per row for desktop
51
+ const desktopMaxPerRow = width === 'normal' ? 2
52
+ : width === 'widest' || width === 'fluid' ? 4
53
+ : 3;
54
+ // Tablet always uses max 2 per row
55
+ const tabletMaxPerRow = 2;
56
+ return [
57
+ generateSmartLayout(imageCount, desktopMaxPerRow, DESKTOP_BREAKPOINT),
58
+ generateSmartLayout(imageCount, tabletMaxPerRow, TABLET_BREAKPOINT),
59
+ ];
60
+ };
1
61
  export const groupRows = (images, layout) => {
2
62
  // Default layout, one img per row
3
63
  if (!layout)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reuters-graphics/graphics-components",
3
- "version": "3.0.19",
3
+ "version": "3.0.21",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "homepage": "https://reuters-graphics.github.io/graphics-components",