PhotoPack
This commit is contained in:
parent
9324f2e1ee
commit
c812788dda
11 changed files with 393 additions and 35 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
</Template>
|
||||
|
||||
<Story
|
||||
name="Basic"
|
||||
name="Default"
|
||||
args="{{
|
||||
width: 'normal',
|
||||
src: SharkImg,
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -204,4 +204,4 @@
|
|||
"./scss/typography/_variables.scss": "./dist/scss/typography/_variables.scss",
|
||||
".": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
|
||||
img {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
18
src/components/PaddingReset/index.svelte
Normal file
18
src/components/PaddingReset/index.svelte
Normal 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>
|
||||
116
src/components/PhotoPack/PhotoPack.stories.svelte
Normal file
116
src/components/PhotoPack/PhotoPack.stories.svelte
Normal 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)}
|
||||
/>
|
||||
198
src/components/PhotoPack/PhotoPack.svelte
Normal file
198
src/components/PhotoPack/PhotoPack.svelte
Normal 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>
|
||||
12
src/components/PhotoPack/stories/docs/component.md
Normal file
12
src/components/PhotoPack/stories/docs/component.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
> **Coming soon!**
|
||||
|
||||
---
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { PhotoPack } from '@reuters-graphics/graphics-svelte-components';
|
||||
</script>
|
||||
|
||||
|
||||
<PhotoPack />
|
||||
```
|
||||
1
src/components/PhotoPack/stories/docs/more.md
Normal file
1
src/components/PhotoPack/stories/docs/more.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
You can set sub groups and maxHeights...
|
||||
Loading…
Reference in a new issue