PhotoPack

This commit is contained in:
Jon McClure 2022-08-22 00:19:01 +01:00
parent 9324f2e1ee
commit c812788dda
11 changed files with 393 additions and 35 deletions

View file

@ -64,22 +64,25 @@ a.sbdocs-a {
.sbdocs {
@include font-display;
&:not(.sbdocs-preview) {
code {
font-size: 90%;
margin-left: 2px;
margin-right: 2px;
background-color: #efefef;
padding: 2px 4px;
}
img {
display: block;
margin-top: 1rem;
margin-bottom: 2rem;
}
}
}
code {
font-size: 90%;
margin-left: 2px;
margin-right: 2px;
background-color: #efefef;
padding: 2px 4px;
}
img {
display: block;
margin-top: 1rem;
margin-bottom: 2rem;
}
div.reset-article {
width: calc(100% + 30px);

View file

@ -34,7 +34,7 @@
</Template>
<Story
name="Basic"
name="Default"
args="{{
width: 'normal',
src: SharkImg,

View file

@ -22,7 +22,7 @@
/** Width of the component within the text well. */
export let width: ContainerWidth = 'normal';
import Block from '../../Block/Block.svelte';
import Block from '../Block/Block.svelte';
</script>
<Block {width} cls="photo">

View file

@ -204,4 +204,4 @@
"./scss/typography/_variables.scss": "./dist/scss/typography/_variables.scss",
".": "./dist/index.js"
}
}
}

View file

@ -98,6 +98,7 @@
img {
width: 100%;
margin: 0;
}
.placeholder {

View file

@ -67,6 +67,7 @@
import AriaHidden from './AriaHidden.svelte';
import TextBlock from './TextBlock.svelte';
import Block from '../Block/Block.svelte';
import PaddingReset from '../PaddingReset/index.svelte';
import { marked } from 'marked';
</script>
@ -80,17 +81,21 @@
>
<div>
{#if $$slots.title}
<TextBlock width="{textWidth}">
<!-- Custom title content -->
<slot name="title" />
</TextBlock>
<PaddingReset width={width}>
<TextBlock width="{textWidth}">
<!-- Custom title content -->
<slot name="title" />
</TextBlock>
</PaddingReset>
{:else if title}
<TextBlock width="{textWidth}">
<h3>{title}</h3>
{#if description}
{@html marked(description)}
{/if}
</TextBlock>
<PaddingReset width={width}>
<TextBlock width="{textWidth}">
<h3>{title}</h3>
{#if description}
{@html marked(description)}
{/if}
</TextBlock>
</PaddingReset>
{/if}
<AriaHidden hidden="{!!$$slots.aria || !!ariaDescription}">
<!-- Graphic content -->
@ -107,16 +112,20 @@
</div>
{/if}
{#if $$slots.notes}
<TextBlock width="{textWidth}">
<!-- Custom notes content -->
<slot name="notes" />
</TextBlock>
<PaddingReset width={width}>
<TextBlock width="{textWidth}">
<!-- Custom notes content -->
<slot name="notes" />
</TextBlock>
</PaddingReset>
{:else if notes}
<TextBlock width="{textWidth}">
<aside>
{@html marked(notes)}
</aside>
</TextBlock>
<PaddingReset width={width}>
<TextBlock width="{textWidth}">
<aside>
{@html marked(notes)}
</aside>
</TextBlock>
</PaddingReset>
{/if}
</div>
</Block>

View file

@ -0,0 +1,18 @@
<script>
export let width = 'normal';
</script>
{#if width === 'fluid'}
<div>
<slot></slot>
</div>
{:else}
<slot></slot>
{/if}
<style>
div {
width: 100%;
padding: 0 15px;
}
</style>

View file

@ -0,0 +1,116 @@
<script>
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
// @ts-ignore
import componentDocs from './stories/docs/component.md?raw';
// @ts-ignore
import moreDocs from './stories/docs/more.md?raw';
import PhotoPack from './PhotoPack.svelte';
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
const meta = {
title: 'Components/PhotoPack',
component: PhotoPack,
...withComponentDocs(componentDocs),
argTypes: {
width: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
captionWidth: {
control: 'select',
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
},
},
};
const defaultImages = [
{
row: 1,
src: 'https://via.placeholder.com/1024x768.jpg',
altText: 'alt text',
caption: 'A residential building destroyed by shelling in the settlement of Borodyanka in the Kyiv region, Ukraine March 3, 2022. Picture taken with a drone. REUTERS/Maksim Levin',
maxHeight: 400,
},
{
row: 2,
src: 'https://via.placeholder.com/1640x1180.jpg',
altText: 'alt text',
caption: 'Surveillance footage shows a missile hitting a residential building in Kyiv, Ukraine, February 26, 2022, in this still image taken from a video obtained by REUTERS',
},
{
row: 2,
src: 'https://via.placeholder.com/1200x900.jpg',
altText: 'alt text',
caption: 'People walk past the remains of a missile at a bus terminal in Kyiv, Ukraine March 4, 2022. REUTERS/Valentyn Ogirenko',
},
{
row: 2,
src: 'https://via.placeholder.com/1024x768.jpg',
altText: 'alt text',
caption: 'People walk past the remains of a missile at a bus terminal. REUTERS/Valentyn Ogirenko',
},
];
const groupedImages = [
{
row: 1,
src: 'https://via.placeholder.com/1024x768.jpg',
altText: 'alt text',
caption: 'A residential building destroyed by shelling in the settlement of Borodyanka in the Kyiv region, Ukraine March 3, 2022. Picture taken with a drone. REUTERS/Maksim Levin',
maxHeight: 400,
},
{
row: 2,
group: 1,
maxHeight: 300,
src: 'https://via.placeholder.com/1640x1180.jpg',
altText: 'alt text',
caption: 'Surveillance footage shows a missile hitting a residential building in Kyiv, Ukraine, February 26, 2022, in this still image taken from a video obtained by REUTERS',
},
{
row: 2,
group: 2,
maxHeight: 300,
src: 'https://via.placeholder.com/1200x900.jpg',
altText: 'alt text',
caption: 'People walk past the remains of a missile at a bus terminal in Kyiv, Ukraine March 4, 2022. REUTERS/Valentyn Ogirenko',
},
{
row: 2,
group: 2,
maxHeight: 300,
src: 'https://via.placeholder.com/1024x768.jpg',
altText: 'alt text',
caption: 'People walk past the remains of a missile at a bus terminal. REUTERS/Valentyn Ogirenko',
},
];
</script>
<Meta {...meta} />
<Template let:args>
<PhotoPack {...args} />
</Template>
<Story
name="Default"
args="{{
width: 'wide',
images: defaultImages,
breakRows: 750,
}}"
/>
<Story
name="More settings"
args="{{
width: 'wide',
images: groupedImages,
breakRows: 750,
breakGroups: 600,
}}"
{...withStoryDocs(moreDocs)}
/>

View file

@ -0,0 +1,198 @@
<script lang="ts">
interface Image {
src: string;
altText: string;
caption?: string;
row?: number | string;
group?: number | string;
maxHeight?: number | string;
}
/**
* A set of images
* @required
*/
export let images: Image[] = [];
// Coerce string values to numbers, where needed
$: imgs = images.map((img) => ({
...img,
row: !img.row ? 1 : typeof img.row === 'string' ? parseInt(img.row) || 1 : img.row,
group: !img.group ? 1 : typeof img.group === 'string' ? parseInt(img.group) || 1 : img.group,
maxHeight: !img.maxHeight ? null : img.maxHeight === 'string' ? parseFloat(img.maxHeight) || null : img.maxHeight,
}));
/**
* Container width below which to break rows.
* @type {number}
*/
export let breakRows: number = 900;
/**
* Container width below which to break groups, if groups specified in images.
* (Should be smaller than `breakRows`.)
* @type {number}
*/
export let breakGroups: number = breakRows;
/**
* Gap between images.
* @type {number}
*/
export let gap = 10;
const random4 = () =>
Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
/**
* Add an ID to target with SCSS. Should be unique from all other elements.
* @type {string}
*/
export let id: string = 'photopack-' + random4() + random4();
/**
* Add a class to target with SCSS.
* @type {string}
*/
export let cls: string = '';
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the component within the text well. */
export let width: ContainerWidth = 'normal';
/**
* Set a different width for captions within the text well, for example,
* "normal" to keep captions inline with the rest of the text well.
* Can't ever be wider than `width`.
* @type {string}
*/
export let captionWidth: ContainerWidth = 'normal';
import Block from '../Block/Block.svelte';
import PaddingReset from '../PaddingReset/index.svelte';
import { groupBy } from 'lodash-es';
import { marked } from 'marked';
const group = (groupArray, key = 'row') => {
const groupObj = groupBy(groupArray, d => d[key]);
const groupKeys = Object.keys(groupObj).sort();
return groupKeys.map(k => key === 'row' ? group(groupObj[k], 'group') : groupObj[k]);
};
$: rows = group(imgs);
let containerWidth;
$: rowsBroken = (containerWidth || Infinity) <= breakRows;
$: groupsBroken = (containerWidth || Infinity) <= (breakGroups < breakRows ? breakGroups : breakRows);
</script>
<Block {width} {id} cls="photopack {cls}">
<div class="photopack-container" bind:clientWidth="{containerWidth}">
{#each rows as row, ri}
<div
class="photopack-row"
style:gap="0 {gap}px"
style:margin-bottom={gap + 'px'}
class:break={rowsBroken}
>
{#each row as group, gi}
<div
class="photopack-group"
style:gap="0 {gap}px"
style:margin-bottom={gap + 'px'}
class:break={groupsBroken}
>
{#each group as img, i}
<figure
style="--gap: {gap}px;"
aria-labelledby="{id}-figure-{ri}-{gi}-{i}"
>
<img
src="{img.src}"
alt="{img.altText}"
style:max-height="{img.maxHeight ? img.maxHeight + 'px' : ''}"
/>
</figure>
{/each}
</div>
{/each}
</div>
{/each}
</div>
<PaddingReset width={width}>
<Block width={captionWidth}>
<div class='captions-container'>
{#each rows as row, ri}
{#each row as group, gi}
{#each group as img, i}
{#if img.caption}
<div id="{id}-figure-{ri}-{gi}-{i}" class='caption'>
{@html marked(img.caption)}
</div>
{/if}
{/each}
{/each}
{/each}
</div>
</Block>
</PaddingReset>
</Block>
<style lang="scss">
@import "../../scss/fonts/variables";
@import "../../scss/colours/thematic/tr";
div.photopack-container {
display: block;
width: 100%;
margin-bottom: 10px;
div.photopack-row {
display: flex;
justify-content: space-between;
&.break {
display: block;
div.photopack-group {
display: flex;
justify-content: space-between;
&.break {
display: block;
figure {
display: block;
max-height: unset;
margin-bottom: var(--gap);
}
}
}
}
div.photopack-group {
display: contents;
}
}
figure {
flex: 1;
margin: 0;
padding: 0;
img {
margin: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
div.captions-container {
div.caption {
margin: 0 0 0.5rem;
&:last-of-type {
margin-bottom: 0;
}
:global(p) {
font-size: 0.85rem;
line-height: 1.15rem;
font-family: var(--theme-font-family-note, $font-family-display);
color: var(--theme-colour-text-secondary, $tr-medium-grey);
margin: 0;
}
}
}
</style>

View file

@ -0,0 +1,12 @@
> **Coming soon!**
---
```svelte
<script>
import { PhotoPack } from '@reuters-graphics/graphics-svelte-components';
</script>
<PhotoPack />
```

View file

@ -0,0 +1 @@
You can set sub groups and maxHeights...