set up photo pack
This commit is contained in:
parent
894ff8442d
commit
e9dff42de1
101 changed files with 848 additions and 892 deletions
|
|
@ -28,7 +28,7 @@
|
|||
import SharkImg from './stories/shark.jpg';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<YourComponent {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -36,9 +36,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
src: SharkImg,
|
||||
altText: "Duh dum! It's a shark!!",
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@
|
|||
|
||||
<Block {width} {id} class="photo {cls}">
|
||||
<div
|
||||
style:background-image="{`url(${src})`}"
|
||||
style:height="{`${height}px`}"
|
||||
style:background-image={`url(${src})`}
|
||||
style:height={`${height}px`}
|
||||
></div>
|
||||
<p class="visually-hidden">{altText}</p>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ An action you can use to easily set [CSS variables](https://developer.mozilla.or
|
|||
</script>
|
||||
|
||||
<!-- Attach it to a parent element with the action -->
|
||||
<div use:cssVariables="{variables}">
|
||||
<div use:cssVariables={variables}>
|
||||
<p>My text...</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ An action you can use to easily to check when a DOM element's dimensions change
|
|||
let elementWidth = 0;
|
||||
</script>
|
||||
|
||||
<div use:resizeObserver="{(element) => (elementWidth = element.clientWidth)}">
|
||||
<div use:resizeObserver={(element) => (elementWidth = element.clientWidth)}>
|
||||
My width is: {elementWidth}
|
||||
</div>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div data-freestar-ad="{dataFreestarAd || null}" id="{adId}"></div>
|
||||
<div data-freestar-ad={dataFreestarAd || null} id={adId}></div>
|
||||
|
||||
<style>
|
||||
:global(div.freestar-adslot:has(.unfulfilled-ad)) {
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ You may add **up to three** inline ads per page, but must set the `n` prop on mu
|
|||
|
||||
```svelte
|
||||
<!-- First inline ad on the page -->
|
||||
<InlineAd n="{1}" />
|
||||
<InlineAd n={1} />
|
||||
<!-- ... second ... -->
|
||||
<InlineAd n="{2}" />
|
||||
<InlineAd n={2} />
|
||||
<!-- ... third and final. -->
|
||||
<InlineAd n="{3}" />
|
||||
<InlineAd n={3} />
|
||||
```
|
||||
|
||||
<Canvas of={InlineAdStories.Demo} />
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth="{windowWidth}" />
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
<div
|
||||
class="freestar-adslot leaderboard__sticky {cls}"
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
let adType = $derived(getAdType(placementName));
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth="{windowWidth}" />
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
{#if windowWidth}
|
||||
{#key placementName}
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ You can set custom column widths by passing an object to the `columnWidths` prop
|
|||
```svelte
|
||||
<!-- Set custom column widths -->
|
||||
<Article
|
||||
columnWidths="{{
|
||||
columnWidths={{
|
||||
narrower: 310,
|
||||
narrow: 450,
|
||||
normal: 550,
|
||||
wide: 675,
|
||||
wider: 1400,
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<!-- Custom column widths get passed down to the `Block` component -->
|
||||
<Block width="narrower" />
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
import Article from '../Article/Article.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
|
|
@ -51,9 +51,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="Custom layouts" {...withStoryDocs(customLayoutsDocs)}>
|
||||
|
|
@ -77,42 +77,41 @@
|
|||
<Article id="block-demo-article">
|
||||
<div class="article-boundaries">
|
||||
<div class="label">Article</div>
|
||||
<Block width="narrower" snap="{true}" class="block-snap-widths-demo"
|
||||
<Block width="narrower" snap={true} class="block-snap-widths-demo"
|
||||
>narrower</Block
|
||||
>
|
||||
<Block width="narrow" snap="{true}" class="block-snap-widths-demo"
|
||||
<Block width="narrow" snap={true} class="block-snap-widths-demo"
|
||||
>narrow</Block
|
||||
>
|
||||
<Block width="normal" snap="{true}" class="block-snap-widths-demo"
|
||||
<Block width="normal" snap={true} class="block-snap-widths-demo"
|
||||
>normal</Block
|
||||
>
|
||||
<Block width="wide" snap="{true}" class="block-snap-widths-demo"
|
||||
>wide</Block
|
||||
<Block width="wide" snap={true} class="block-snap-widths-demo">wide</Block
|
||||
>
|
||||
<Block width="wider" snap="{true}" class="block-snap-widths-demo"
|
||||
<Block width="wider" snap={true} class="block-snap-widths-demo"
|
||||
>wider</Block
|
||||
>
|
||||
<Block width="narrower" snap="{true}" class="block-snap-widths-demo even"
|
||||
<Block width="narrower" snap={true} class="block-snap-widths-demo even"
|
||||
>narrower</Block
|
||||
>
|
||||
<Block width="narrow" snap="{true}" class="block-snap-widths-demo even"
|
||||
<Block width="narrow" snap={true} class="block-snap-widths-demo even"
|
||||
>narrow</Block
|
||||
>
|
||||
<Block
|
||||
width="normal"
|
||||
snap="{true}"
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-narrow"
|
||||
>normal.skip-narrow</Block
|
||||
>
|
||||
<Block
|
||||
width="wide"
|
||||
snap="{true}"
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-normal skip-narrow"
|
||||
>wide.skip-normal.skip-narrow</Block
|
||||
>
|
||||
<Block
|
||||
width="wider"
|
||||
snap="{true}"
|
||||
snap={true}
|
||||
class="block-snap-widths-demo even skip-wide">wider.skip-wide</Block
|
||||
>
|
||||
</div>
|
||||
|
|
@ -145,9 +144,9 @@
|
|||
background: rgb(211, 132, 123);
|
||||
}
|
||||
:global(
|
||||
#block-demo-article .label,
|
||||
#block-demo-article div.article-block.block-snap-widths-demo
|
||||
) {
|
||||
#block-demo-article .label,
|
||||
#block-demo-article div.article-block.block-snap-widths-demo
|
||||
) {
|
||||
padding-left: 3px;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@
|
|||
<div
|
||||
{id}
|
||||
class="article-block fmx-auto {width} {cls}"
|
||||
class:snap="{snap && width !== 'fluid' && width !== 'widest'}"
|
||||
class:snap={snap && width !== 'fluid' && width !== 'widest'}
|
||||
{role}
|
||||
aria-label="{ariaLabel}"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<!-- block content -->
|
||||
<slot />
|
||||
|
|
|
|||
|
|
@ -17,18 +17,18 @@
|
|||
|
||||
<Story
|
||||
name="Demo"
|
||||
args="{{
|
||||
args={{
|
||||
src: 'https://reuters.com/graphics/USA-ABORTION/lgpdwggnwvo/media-embed.html',
|
||||
id: 'abortion-rights-map',
|
||||
ariaLabel: 'map',
|
||||
frameTitle: 'Global abortion access',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="With chatter"
|
||||
tags="{['!autodocs']}"
|
||||
args="{{
|
||||
tags={['!autodocs']}
|
||||
args={{
|
||||
frameTitle: 'Global abortion access',
|
||||
ariaLabel: 'map',
|
||||
id: 'abortion-rights-map',
|
||||
|
|
@ -37,5 +37,5 @@
|
|||
description: 'A map of worldwide access to abortion.',
|
||||
notes:
|
||||
'Note: Different indicators and additional restrictions, including different gestational limits, apply in some countries. Refer to source for full classification. Current as of May 4, 2022.\n\nSource: Center for Reproductive Rights',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<DocumentCloud {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -30,9 +30,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
slug: '3259984-Trump-Intelligence-Allegations',
|
||||
altText: 'These Reports Allege Trump Has Deep Ties To Russia',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<iframe
|
||||
class="h-screen"
|
||||
src="https://embed.documentcloud.org/documents/{slug}/?embed=1&responsive=1&title=1"
|
||||
title="{altText}"
|
||||
title={altText}
|
||||
width="700"
|
||||
height="540"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
|
|
@ -15,7 +15,6 @@
|
|||
// withStoryDocs,
|
||||
// } from '$lib/docs/utils/withParams.js';
|
||||
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Multimedia/FeaturePhoto',
|
||||
component: FeaturePhoto,
|
||||
|
|
@ -39,34 +38,33 @@
|
|||
import sharkSrc from './stories/shark.jpg';
|
||||
</script>
|
||||
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
src: sharkSrc,
|
||||
altText: 'A shark!',
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="ArchieML"
|
||||
args="{{
|
||||
args={{
|
||||
src: sharkSrc,
|
||||
altText: 'A shark!',
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
<!-- {...withStoryDocs(archieMLDocs)} -->
|
||||
|
||||
<Story
|
||||
name="Missing altText"
|
||||
args="{{
|
||||
args={{
|
||||
src: sharkSrc,
|
||||
width: 'normal',
|
||||
caption: 'Carcharodon carcharias - REUTERS',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
<!-- {...withStoryDocs(missingAltTextDocs)} -->
|
||||
|
|
|
|||
|
|
@ -93,18 +93,18 @@
|
|||
|
||||
<Block {width} class="photo fmy-6 {cls}" {id}>
|
||||
<figure
|
||||
bind:this="{container}"
|
||||
bind:this={container}
|
||||
aria-label="media"
|
||||
class="w-full flex flex-col relative"
|
||||
>
|
||||
{#if !lazy || (intersectable && intersecting)}
|
||||
<img class="w-full my-0" {src} alt="{altText}" />
|
||||
<img class="w-full my-0" {src} alt={altText} />
|
||||
{:else}
|
||||
<div class="placeholder w-full" style="{`height: ${height}px;`}"></div>
|
||||
<div class="placeholder w-full" style={`height: ${height}px;`}></div>
|
||||
{/if}
|
||||
{#if caption}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<Block width="{textWidth}" class="notes w-full fmy-0">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<Block width={textWidth} class="notes w-full fmy-0">
|
||||
<figcaption>
|
||||
{caption}
|
||||
</figcaption>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Framer {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -25,9 +25,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
embeds: [
|
||||
'https://graphics.reuters.com/USA-CONGRESS/FUNDRAISING/zjvqkawjlvx/embeds/en/embed/?zzz',
|
||||
],
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -62,25 +62,20 @@
|
|||
|
||||
<div id="typeahead-container">
|
||||
<div class="embed-link">
|
||||
<a
|
||||
rel="external"
|
||||
target="_blank"
|
||||
href="{activeEmbed}"
|
||||
title="{activeEmbed}"
|
||||
>
|
||||
Live link <Fa icon="{faLink}" />
|
||||
<a rel="external" target="_blank" href={activeEmbed} title={activeEmbed}>
|
||||
Live link <Fa icon={faLink} />
|
||||
</a>
|
||||
</div>
|
||||
<Typeahead
|
||||
label="Select an embed"
|
||||
value="{embedTitles[embeds.indexOf(activeEmbed)] ||
|
||||
value={embedTitles[embeds.indexOf(activeEmbed)] ||
|
||||
embedTitles[activeEmbedIndex] ||
|
||||
embedTitles[0]}"
|
||||
extract="{(d) => embedTitles[d.index]}"
|
||||
data="{embeds.map((embed, index) => ({ index, embed }))}"
|
||||
placeholder="{'Search'}"
|
||||
showDropdownOnFocus="{true}"
|
||||
on:select="{({ detail }) => {
|
||||
embedTitles[0]}
|
||||
extract={(d) => embedTitles[d.index]}
|
||||
data={embeds.map((embed, index) => ({ index, embed }))}
|
||||
placeholder={'Search'}
|
||||
showDropdownOnFocus={true}
|
||||
on:select={({ detail }) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(
|
||||
'framer-active-embed',
|
||||
|
|
@ -89,7 +84,7 @@
|
|||
}
|
||||
activeEmbed = detail.original.embed;
|
||||
activeEmbedIndex = detail.original.index;
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -101,7 +96,7 @@
|
|||
|
||||
<div id="home-link">
|
||||
<a rel="external" href="./../">
|
||||
<Fa icon="{faDesktop}" />
|
||||
<Fa icon={faDesktop} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -95,48 +95,48 @@
|
|||
</script>
|
||||
|
||||
<svelte:window
|
||||
onmousemove="{move}"
|
||||
onmouseup="{end}"
|
||||
onkeydown="{handleKeyDown}"
|
||||
bind:innerWidth="{windowInnerWidth}"
|
||||
onmousemove={move}
|
||||
onmouseup={end}
|
||||
onkeydown={handleKeyDown}
|
||||
bind:innerWidth={windowInnerWidth}
|
||||
/>
|
||||
|
||||
<div id="resizer">
|
||||
<div class="slider">
|
||||
<div class="label" style="{`opacity: ${sliding || isFocused ? 1 : 0};`}">
|
||||
<div class="label" style={`opacity: ${sliding || isFocused ? 1 : 0};`}>
|
||||
{pixelLabel || $width}px
|
||||
</div>
|
||||
<button
|
||||
class="icon left"
|
||||
disabled="{$width === minWidth}"
|
||||
onclick="{decrement}"
|
||||
onfocus="{onFocus}"
|
||||
onmouseover="{onFocus}"
|
||||
onmouseleave="{onBlur}"
|
||||
disabled={$width === minWidth}
|
||||
onclick={decrement}
|
||||
onfocus={onFocus}
|
||||
onmouseover={onFocus}
|
||||
onmouseleave={onBlur}
|
||||
>
|
||||
<Fa icon="{faMobileAlt}" fw />
|
||||
<Fa icon={faMobileAlt} fw />
|
||||
</button>
|
||||
<div class="slider-container" bind:this="{container}">
|
||||
<div class="slider-container" bind:this={container}>
|
||||
<div class="track"></div>
|
||||
<div
|
||||
class="handle"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
style="left: calc({offset * 100}% - 5px);"
|
||||
onmousedown="{start}"
|
||||
onfocus="{onFocus}"
|
||||
onblur="{onBlur}"
|
||||
onmousedown={start}
|
||||
onfocus={onFocus}
|
||||
onblur={onBlur}
|
||||
></div>
|
||||
</div>
|
||||
<button
|
||||
class="icon right"
|
||||
disabled="{$width === maxWidth}"
|
||||
onclick="{increment}"
|
||||
onfocus="{onFocus}"
|
||||
onmouseover="{onFocus}"
|
||||
onmouseleave="{onBlur}"
|
||||
disabled={$width === maxWidth}
|
||||
onclick={increment}
|
||||
onfocus={onFocus}
|
||||
onmouseover={onFocus}
|
||||
onmouseleave={onBlur}
|
||||
>
|
||||
<Fa icon="{faDesktop}" fw />
|
||||
<Fa icon={faDesktop} fw />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -158,55 +158,55 @@
|
|||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:click="{({ target }) => {
|
||||
on:click={({ target }) => {
|
||||
if (!hideDropdown && !comboboxRef?.contains(target)) {
|
||||
close();
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
data-svelte-typeahead
|
||||
bind:this="{comboboxRef}"
|
||||
bind:this={comboboxRef}
|
||||
role="combobox"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="{id}-listbox"
|
||||
class:dropdown="{results.length > 0}"
|
||||
class:dropdown={results.length > 0}
|
||||
aria-controls="{id}-listbox"
|
||||
aria-expanded="{showResults ||
|
||||
(isFocused && value.length > 0 && results.length === 0)}"
|
||||
aria-expanded={showResults ||
|
||||
(isFocused && value.length > 0 && results.length === 0)}
|
||||
id="{id}-typeahead"
|
||||
>
|
||||
<Search
|
||||
{id}
|
||||
removeFormAriaAttributes="{true}"
|
||||
removeFormAriaAttributes={true}
|
||||
{...$$restProps}
|
||||
bind:ref="{searchRef}"
|
||||
bind:ref={searchRef}
|
||||
aria-autocomplete="list"
|
||||
aria-controls="{id}-listbox"
|
||||
aria-labelledby="{id}-label"
|
||||
aria-activedescendant="{(
|
||||
aria-activedescendant={(
|
||||
selectedIndex >= 0 && !hideDropdown && results.length > 0
|
||||
) ?
|
||||
`${id}-result-${selectedIndex}`
|
||||
: null}"
|
||||
: null}
|
||||
bind:value
|
||||
on:type
|
||||
on:input
|
||||
on:change
|
||||
on:focus
|
||||
on:focus="{() => {
|
||||
on:focus={() => {
|
||||
open();
|
||||
if (showDropdownOnFocus) {
|
||||
showResults = true;
|
||||
isFocused = true;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
on:clear
|
||||
on:clear="{open}"
|
||||
on:clear={open}
|
||||
on:blur
|
||||
on:keydown
|
||||
on:keydown="{(e) => {
|
||||
on:keydown={(e) => {
|
||||
if (results.length === 0) return;
|
||||
|
||||
switch (e.key) {
|
||||
|
|
@ -228,10 +228,10 @@
|
|||
close();
|
||||
break;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
<ul
|
||||
class:svelte-typeahead-list="{true}"
|
||||
class:svelte-typeahead-list={true}
|
||||
role="listbox"
|
||||
aria-labelledby="{id}-label"
|
||||
id="{id}-listbox"
|
||||
|
|
@ -241,24 +241,24 @@
|
|||
<li
|
||||
role="option"
|
||||
id="{id}-result-{index}"
|
||||
class:selected="{selectedIndex === index}"
|
||||
class:disabled="{result.disabled}"
|
||||
aria-selected="{selectedIndex === index}"
|
||||
on:click="{() => {
|
||||
class:selected={selectedIndex === index}
|
||||
class:disabled={result.disabled}
|
||||
aria-selected={selectedIndex === index}
|
||||
on:click={() => {
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
select();
|
||||
}}"
|
||||
on:keyup="{(e) => {
|
||||
}}
|
||||
on:keyup={(e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
select();
|
||||
}}"
|
||||
on:mouseenter="{() => {
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
if (result.disabled) return;
|
||||
selectedIndex = index;
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<slot {result} {index} {value}>
|
||||
{@html result.string}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts">
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Whether to wrap the graphic with an aria hidden tag.
|
||||
*/
|
||||
* Whether to wrap the graphic with an aria hidden tag.
|
||||
*/
|
||||
hidden?: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@
|
|||
import PlaceholderImg from './stories/placeholder.png';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<GraphicBlock {...args}>
|
||||
<div class="demo-graphic">
|
||||
<img src="{PlaceholderImg}" alt="placeholder" />
|
||||
<img src={PlaceholderImg} alt="placeholder" />
|
||||
</div>
|
||||
</GraphicBlock>
|
||||
{/snippet}
|
||||
|
|
@ -52,14 +52,14 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
title: 'Bacon ipsum dolor amet t-bone',
|
||||
description:
|
||||
'Pork loin t-bone jowl prosciutto, short loin flank kevin tri-tip cupim pig pork. Meatloaf tri-tip frankfurter short ribs, cupim brisket bresaola chislic tail jerky burgdoggen pancetta.',
|
||||
notes:
|
||||
'Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="ArchieML" {...withStoryDocs(archieMLDocs)}>
|
||||
|
|
@ -76,18 +76,18 @@
|
|||
<Story name="Custom text" {...withStoryDocs(customTextDocs)}>
|
||||
<GraphicBlock width="normal">
|
||||
{#snippet title()}
|
||||
<div >
|
||||
<div>
|
||||
<h5>My smaller title</h5>
|
||||
</div>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
<div class="demo-graphic">
|
||||
<img src="{PlaceholderImg}" alt="placeholder" />
|
||||
<img src={PlaceholderImg} alt="placeholder" />
|
||||
</div>
|
||||
{#snippet notes()}
|
||||
<aside >
|
||||
<aside>
|
||||
<p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p>
|
||||
</aside>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</GraphicBlock>
|
||||
</Story>
|
||||
|
||||
|
|
|
|||
|
|
@ -76,23 +76,23 @@
|
|||
|
||||
<Block {id} {snap} {role} {width} {ariaLabel} class="graphic fmy-6 {cls}">
|
||||
{#if $$slots.title}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<!-- Custom title content -->
|
||||
<slot name="title" />
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if title}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<h3>{title}</h3>
|
||||
{#if description}
|
||||
<Markdown source="{description}" />
|
||||
<Markdown source={description} />
|
||||
{/if}
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
<AriaHidden hidden="{!!$$slots.aria || !!ariaDescription}">
|
||||
<AriaHidden hidden={!!$$slots.aria || !!ariaDescription}>
|
||||
<!-- Graphic content -->
|
||||
<slot />
|
||||
</AriaHidden>
|
||||
|
|
@ -102,22 +102,22 @@
|
|||
<!-- Custom ARIA markup -->
|
||||
<slot name="aria" />
|
||||
{:else}
|
||||
<Markdown source="{ariaDescription}" />
|
||||
<Markdown source={ariaDescription} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.notes}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<!-- Custom notes content -->
|
||||
<slot name="notes" />
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
{:else if notes}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<TextBlock width="{textWidth}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<TextBlock width={textWidth}>
|
||||
<aside>
|
||||
<Markdown source="{notes}" />
|
||||
<Markdown source={notes} />
|
||||
</aside>
|
||||
</TextBlock>
|
||||
</PaddingReset>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { ContainerWidth } from '../@types/global';
|
||||
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
interface Props {
|
||||
/** Width of the component within the text well. */
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2021-09-29 12:37 -->
|
||||
|
||||
<div id="g-_ai-chart-box" bind:clientWidth="{width}">
|
||||
<div id="g-_ai-chart-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-_ai-chart-xs" class="g-artboard" style="">
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
id="g-_ai-chart-xs-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
id="g-_ai-chart-sm-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -292,7 +292,7 @@
|
|||
id="g-_ai-chart-md-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
import Map from './stories/graphic.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Headline {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -52,49 +52,49 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
section: 'World News',
|
||||
hed: 'Reuters Graphics interactive',
|
||||
hedSize: 'normal',
|
||||
dek: '',
|
||||
authors: [],
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="With dek" {...withStoryDocs(withDekDocs)}>
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
/>
|
||||
</Story>
|
||||
|
||||
<Story name="With byline" {...withStoryDocs(withBylineDocs)}>
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Dea Bankova', 'Aditi Bhandari']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
authors={['Dea Bankova', 'Aditi Bhandari']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
/>
|
||||
</Story>
|
||||
|
||||
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
|
||||
<Headline width="wide">
|
||||
{#snippet hed()}
|
||||
<h1 class="custom-hed" >
|
||||
<h1 class="custom-hed">
|
||||
<span class="small block text-base">The secret to</span>
|
||||
“The Nutcracker's”
|
||||
<span class="small block text-base fpt-1">success</span>
|
||||
</h1>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
{#snippet dek()}
|
||||
<p class="custom-dek !fmt-3" >
|
||||
<p class="custom-dek !fmt-3">
|
||||
How “The Nutcracker” ballet became an<span
|
||||
class="font-medium mx-1 px-1.5 py-1">American holday staple</span
|
||||
>and a financial pillar of ballet companies across the country
|
||||
</p>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</Headline>
|
||||
<style lang="scss">
|
||||
.custom-hed {
|
||||
|
|
@ -109,21 +109,20 @@
|
|||
</Story>
|
||||
|
||||
<Story name="With crown image" {...withStoryDocs(withCrownImgDocs)}>
|
||||
<Headline class="!fmt-3" publishTime="{new Date('2020-01-01').toISOString()}">
|
||||
<Headline class="!fmt-3" publishTime={new Date('2020-01-01').toISOString()}>
|
||||
<!-- Add a crown -->
|
||||
{#snippet crown()}
|
||||
<img
|
||||
|
||||
src="{crownImgSrc}"
|
||||
<img
|
||||
src={crownImgSrc}
|
||||
width="100"
|
||||
class="mx-auto mb-0"
|
||||
alt="Illustration of Europe"
|
||||
/>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
<!-- Override the hed with a named slot -->
|
||||
{#snippet hed()}
|
||||
<h1 class="!font-serif !tracking-wide">Europa</h1>
|
||||
{/snippet}
|
||||
<h1 class="!font-serif !tracking-wide">Europa</h1>
|
||||
{/snippet}
|
||||
</Headline>
|
||||
</Story>
|
||||
|
||||
|
|
@ -131,17 +130,17 @@
|
|||
<Headline
|
||||
width="wider"
|
||||
class="!fmt-1"
|
||||
hed="{'Unfriendly skies'}"
|
||||
dek="{'How Russia’s invasion of Ukraine is redrawing air routes'}"
|
||||
section="{'Ukraine Crisis'}"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2022-03-04').toISOString()}"
|
||||
hed={'Unfriendly skies'}
|
||||
dek={'How Russia’s invasion of Ukraine is redrawing air routes'}
|
||||
section={'Ukraine Crisis'}
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2022-03-04').toISOString()}
|
||||
>
|
||||
<!-- Add a crown graphic -->
|
||||
{#snippet crown()}
|
||||
<div >
|
||||
<div>
|
||||
<Map />
|
||||
</div>
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</Headline>
|
||||
</Story>
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@
|
|||
<!-- Headline named slot -->
|
||||
<slot name="hed" />
|
||||
{:else}
|
||||
<h1 class="{hedClass}">
|
||||
<Markdown source="{hed}" parseInline />
|
||||
<h1 class={hedClass}>
|
||||
<Markdown source={hed} parseInline />
|
||||
</h1>
|
||||
{/if}
|
||||
{#if $$slots.dek}
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
</div>
|
||||
{:else if dek}
|
||||
<div class="dek fmx-auto fmb-6">
|
||||
<Markdown source="{dek}" />
|
||||
<Markdown source={dek} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
let width = $state(null);
|
||||
</script>
|
||||
|
||||
<div id="g-graphic-box" bind:clientWidth="{width}">
|
||||
<div id="g-graphic-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-graphic-xs" class="g-artboard" style="">
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<div
|
||||
id="g-graphic-xs-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-3"
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
<div
|
||||
id="g-graphic-sm-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
<div
|
||||
id="g-graphic-md-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
|
||||
<div
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
<div
|
||||
id="g-graphic-lg-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -485,7 +485,7 @@
|
|||
<div
|
||||
id="g-graphic-xl-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
|
|||
|
|
@ -75,14 +75,14 @@
|
|||
|
||||
<Story
|
||||
name="With backdrop photo"
|
||||
args="{{
|
||||
args={{
|
||||
section: 'World News',
|
||||
hed: 'Reuters Graphics Interactive',
|
||||
dek: 'The beginning of a beautiful page',
|
||||
authors: ['Simon Scarr', 'Vijdan Mohammad Kawoosa'],
|
||||
publishTime: new Date('2022-03-04').toISOString(),
|
||||
img: polarImgSrc,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="With transparent header" {...withStoryDocs(transparentHeaderDocs)}>
|
||||
|
|
@ -95,9 +95,9 @@
|
|||
section="World News"
|
||||
hed="Reuters Graphics Interactive"
|
||||
dek="The beginning of a beautiful page"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2022-03-04').toISOString()}"
|
||||
img="{polarImgSrc}"
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2022-03-04').toISOString()}
|
||||
img={polarImgSrc}
|
||||
/>
|
||||
</div>
|
||||
</Story>
|
||||
|
|
@ -108,12 +108,12 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Earthquake devastates Afghanistan'}"
|
||||
hedSize="{'big'}"
|
||||
hed={'Earthquake devastates Afghanistan'}
|
||||
hedSize={'big'}
|
||||
hedWidth="wide"
|
||||
class="custom-hero mb-0"
|
||||
dek=""
|
||||
authors="{[
|
||||
authors={[
|
||||
'Anand Katakam',
|
||||
'Vijdan Mohammad Kawoosa',
|
||||
'Adolfo Arranz',
|
||||
|
|
@ -123,8 +123,8 @@
|
|||
'Jitesh Chowdhury',
|
||||
'Manas Sharma',
|
||||
'Aditi Bhandari',
|
||||
]}"
|
||||
publishTime="{new Date('2022-06-24').toISOString()}"
|
||||
]}
|
||||
publishTime={new Date('2022-06-24').toISOString()}
|
||||
>
|
||||
<div slot="background">
|
||||
<GraphicBlock
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
notes=""
|
||||
ariaDescription="Earthquake impact map"
|
||||
>
|
||||
<svelte:component this="{QuakeMap}" />
|
||||
<svelte:component this={QuakeMap} />
|
||||
</GraphicBlock>
|
||||
</div>
|
||||
</HeroHeadline>
|
||||
|
|
@ -186,16 +186,16 @@
|
|||
hed="The conflict in Ethiopia"
|
||||
hedSize="bigger"
|
||||
hedWidth="wide"
|
||||
authors="{['Aditi Bhandari ', 'David Lewis']}"
|
||||
publishTime="{new Date('2020-12-18').toISOString()}"
|
||||
authors={['Aditi Bhandari ', 'David Lewis']}
|
||||
publishTime={new Date('2020-12-18').toISOString()}
|
||||
>
|
||||
<div slot="background">
|
||||
<Video
|
||||
width="widest"
|
||||
class="my-0"
|
||||
showControls="{false}"
|
||||
showControls={false}
|
||||
preloadVideo="auto"
|
||||
playVideoWhenInView="{false}"
|
||||
playVideoWhenInView={false}
|
||||
src="https://vm.reuters.tv/9c72e/titlef2ac(425954_R21MP41500).mp4"
|
||||
poster="https://www.reuters.com/resizer/vexYmtEuXKmfnsCbfS6jSMVbHms=/1080x0/filters:quality(80)/cloudfront-us-east-2.images.arcpublishing.com/reuters/VKJHKJEENVO4DASDND3VLHPV5Y.jpg"
|
||||
notes="Drone footage from the Village 8 refugee camp in Sudan."
|
||||
|
|
@ -227,13 +227,13 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Buried under the bricks'}"
|
||||
hed={'Buried under the bricks'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How mud-brick housing made the Morocco earthquake so deadly'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Mariano Zafra']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How mud-brick housing made the Morocco earthquake so deadly'}
|
||||
section={'Global news'}
|
||||
authors={['Mariano Zafra']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<FeaturePhoto
|
||||
|
|
@ -253,13 +253,13 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'The plunge from 29,000 feet'}"
|
||||
hed={'The plunge from 29,000 feet'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}
|
||||
section={'Global news'}
|
||||
authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<GraphicBlock
|
||||
|
|
@ -270,7 +270,7 @@
|
|||
notes="Source: Satellite image from Google, Maxar Technologies, CNES/Airbus, Landsat/Copernicus"
|
||||
ariaDescription="Aerial map showing trajectory of crash"
|
||||
>
|
||||
<svelte:component this="{CrashMap}" />
|
||||
<svelte:component this={CrashMap} />
|
||||
</GraphicBlock>
|
||||
</div>
|
||||
</HeroHeadline>
|
||||
|
|
@ -282,21 +282,21 @@
|
|||
</Block>
|
||||
|
||||
<HeroHeadline
|
||||
hed="{'Devastation in Derna'}"
|
||||
hed={'Devastation in Derna'}
|
||||
hedWidth="wide"
|
||||
class="mb-0"
|
||||
dek="{'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Simon Scarr']}"
|
||||
publishTime="{new Date('2020-01-01').toISOString()}"
|
||||
dek={'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}
|
||||
section={'Global news'}
|
||||
authors={['Simon Scarr']}
|
||||
publishTime={new Date('2020-01-01').toISOString()}
|
||||
>
|
||||
<div slot="inline">
|
||||
<Video
|
||||
width="widest"
|
||||
class="my-0"
|
||||
showControls="{false}"
|
||||
showControls={false}
|
||||
preloadVideo="auto"
|
||||
playVideoWhenInView="{false}"
|
||||
playVideoWhenInView={false}
|
||||
src="https://www.reuters.com/graphics/LIBYA-STORM/EXPLAINER/klvyzqebzpg/cdn/video/drone.mp4"
|
||||
notes="Drone shots of Derna, Libya. September 14, 2023. REUTERS"
|
||||
ariaDescription="alttext fot video"
|
||||
|
|
@ -308,14 +308,14 @@
|
|||
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
|
||||
<HeroHeadline
|
||||
class="custom-hed"
|
||||
authors="{[
|
||||
authors={[
|
||||
'Prasanta Kumar Dutta',
|
||||
'Dea Bankova',
|
||||
'Aditi Bhandari',
|
||||
'Anurag Rao',
|
||||
]}"
|
||||
publishTime="{new Date('2023-05-11').toISOString()}"
|
||||
img="{eurovisImgSrc}"
|
||||
]}
|
||||
publishTime={new Date('2023-05-11').toISOString()}
|
||||
img={eurovisImgSrc}
|
||||
>
|
||||
<h1 slot="hed">
|
||||
<div class="body-note">A visual guide to</div>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
{#if $$slots.hed}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
{:else}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -144,11 +144,11 @@
|
|||
<!-- Inline hero -->
|
||||
{#if $$slots.inline}
|
||||
<Block width="fluid" class="hero-headline inline-hero">
|
||||
<PaddingReset containerIsFluid="{true}">
|
||||
<PaddingReset containerIsFluid={true}>
|
||||
{#if $$slots.hed}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
{:else}
|
||||
<Headline
|
||||
class="{cls} !text-{hedAlign}"
|
||||
width="{hedWidth}"
|
||||
width={hedWidth}
|
||||
{section}
|
||||
{hedSize}
|
||||
{hed}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2022-03-29 17:01 -->
|
||||
|
||||
<div id="g-CRASH_1-box" bind:clientWidth="{width}">
|
||||
<div id="g-CRASH_1-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-CRASH_1-xs" class="g-artboard" style="">
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
id="g-CRASH_1-xs-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
id="g-CRASH_1-sm-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
id="g-CRASH_1-md-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
id="g-CRASH_1-lg-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -255,7 +255,7 @@
|
|||
id="g-CRASH_1-xl-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
id="g-CRASH_1-xl_copy-img"
|
||||
class="g-aiImg"
|
||||
alt=""
|
||||
style="{`background-image: url(${chartXxl});`}"
|
||||
style={`background-image: url(${chartXxl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai5-1"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import chartXl from '././quake-map-top-xl.jpeg';
|
||||
</script>
|
||||
|
||||
<div id="g-quake-map-top-box" bind:clientWidth="{width}">
|
||||
<div id="g-quake-map-top-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: xs -->
|
||||
{#if width && width >= 0 && width < 510}
|
||||
<div id="g-quake-map-top-xs" class="g-artboard" style="">
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-xs-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXs});`}"
|
||||
style={`background-image: url(${chartXs});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai0-1"
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-sm-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartSm});`}"
|
||||
style={`background-image: url(${chartSm});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai1-1"
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-md-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartMd});`}"
|
||||
style={`background-image: url(${chartMd});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai2-1"
|
||||
|
|
@ -271,7 +271,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-lg-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartLg});`}"
|
||||
style={`background-image: url(${chartLg});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai3-1"
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
<div
|
||||
id="g-quake-map-top-xl-img"
|
||||
class="g-aiImg"
|
||||
style="{`background-image: url(${chartXl});`}"
|
||||
style={`background-image: url(${chartXl});`}
|
||||
></div>
|
||||
<div
|
||||
id="g-ai4-1"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
const photos = photosJson.map((p) => ({ ...p, altText: p.caption }));
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<PhotoCarousel {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -37,18 +37,18 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'wider',
|
||||
photos,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Custom credits and captions"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'wider',
|
||||
photos,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(customDocs)}
|
||||
>
|
||||
<PhotoCarousel
|
||||
|
|
@ -58,11 +58,11 @@
|
|||
}}
|
||||
>
|
||||
{#snippet credit({ credit })}
|
||||
<p class="custom-credit" >{credit}</p>
|
||||
{/snippet}
|
||||
<p class="custom-credit">{credit}</p>
|
||||
{/snippet}
|
||||
{#snippet caption({ caption })}
|
||||
<p class="custom-caption" >{caption}</p>
|
||||
{/snippet}
|
||||
<p class="custom-caption">{caption}</p>
|
||||
{/snippet}
|
||||
</PhotoCarousel>
|
||||
</Story>
|
||||
|
||||
|
|
|
|||
|
|
@ -111,17 +111,17 @@
|
|||
</script>
|
||||
|
||||
<Block {width} {id} class="photo-carousel fmy-6 {cls}">
|
||||
<div class="carousel-container" bind:clientWidth="{containerWidth}">
|
||||
<div class="carousel-container" bind:clientWidth={containerWidth}>
|
||||
<Splide
|
||||
hasTrack="{false}"
|
||||
options="{{
|
||||
hasTrack={false}
|
||||
options={{
|
||||
height: carouselHeight,
|
||||
fixedHeight: carouselHeight,
|
||||
lazyLoad: 'nearby',
|
||||
preloadPages: preloadImages,
|
||||
}}"
|
||||
aria-label="{carouselAriaLabel}"
|
||||
on:move="{handleActiveChange}"
|
||||
}}
|
||||
aria-label={carouselAriaLabel}
|
||||
on:move={handleActiveChange}
|
||||
>
|
||||
<div class="image-container">
|
||||
<SplideTrack>
|
||||
|
|
@ -134,20 +134,19 @@
|
|||
>
|
||||
<img
|
||||
class="w-full h-full fmy-0"
|
||||
data-splide-lazy="{photo.src}"
|
||||
alt="{photo.altText}"
|
||||
style:object-fit="{photo.objectFit ||
|
||||
defaultImageObjectFit}"
|
||||
style:object-position="{photo.objectPosition ||
|
||||
defaultImageObjectPosition}"
|
||||
data-splide-lazy={photo.src}
|
||||
alt={photo.altText}
|
||||
style:object-fit={photo.objectFit || defaultImageObjectFit}
|
||||
style:object-position={photo.objectPosition ||
|
||||
defaultImageObjectPosition}
|
||||
/>
|
||||
{#if $$slots.credit}
|
||||
<slot name="credit" credit="{photo.credit}" />
|
||||
<slot name="credit" credit={photo.credit} />
|
||||
{:else}
|
||||
<span
|
||||
class="credit absolute fmb-1 fml-1 leading-tighter font-note text-xxs"
|
||||
class:contain-fit="{photo.objectFit === 'contain' ||
|
||||
defaultImageObjectFit === 'contain'}"
|
||||
class:contain-fit={photo.objectFit === 'contain' ||
|
||||
defaultImageObjectFit === 'contain'}
|
||||
>{photo.credit}</span
|
||||
>
|
||||
{/if}
|
||||
|
|
@ -158,18 +157,18 @@
|
|||
</SplideTrack>
|
||||
|
||||
{#if photos[activeImageIndex].caption}
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<Block width="{textWidth}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<Block width={textWidth}>
|
||||
{#if $$slots.caption}
|
||||
<slot
|
||||
name="caption"
|
||||
caption="{photos[activeImageIndex].caption}"
|
||||
caption={photos[activeImageIndex].caption}
|
||||
/>
|
||||
{:else}
|
||||
{#key activeImageIndex}
|
||||
<p
|
||||
class="caption body-caption text-center"
|
||||
in:fly|local="{{ x: 20, duration: 175 }}"
|
||||
in:fly|local={{ x: 20, duration: 175 }}
|
||||
>
|
||||
{photos[activeImageIndex].caption}
|
||||
</p>
|
||||
|
|
@ -185,10 +184,10 @@
|
|||
|
||||
<div class="splide__arrows fp-1">
|
||||
<button class="splide__arrow splide__arrow--prev">
|
||||
<Fa icon="{faChevronLeft}" fw />
|
||||
<Fa icon={faChevronLeft} fw />
|
||||
</button>
|
||||
<button class="splide__arrow splide__arrow--next">
|
||||
<Fa icon="{faChevronRight}" fw />
|
||||
<Fa icon={faChevronRight} fw />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
127
src/components/PhotoPack/PhotoPack.mdx
Normal file
127
src/components/PhotoPack/PhotoPack.mdx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as PhotoPackStories from './PhotoPack.stories.svelte';
|
||||
|
||||
<Meta of={PhotoPackStories} />
|
||||
|
||||
# PhotoPack
|
||||
|
||||
The `PhotoPack` component makes simple photo grids with custom layouts at various breakpoints.
|
||||
|
||||
`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.
|
||||
|
||||
`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:
|
||||
|
||||
```javascript
|
||||
const layouts = [
|
||||
{
|
||||
breakpoint: 450,
|
||||
rows: [1, 2, 1],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
... 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.
|
||||
|
||||
You can define as many layouts for as many images as you like.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { PhotoPack } from '@reuters-graphics/graphics-components';
|
||||
|
||||
/** Array of photo metadata */
|
||||
const images = [
|
||||
{
|
||||
src: 'https://...',
|
||||
altText: 'Alt text',
|
||||
caption: 'Lorem ipsum. REUTERS/Photog',
|
||||
// Optional max-height of images across all layouts
|
||||
maxHeight: 800,
|
||||
},
|
||||
// ...
|
||||
];
|
||||
|
||||
/** Set the number of photos in each row at various breakpoints */
|
||||
const layouts = [
|
||||
{
|
||||
breakpoint: 450, // Applies to containers wider than 450px
|
||||
rows: [1, 2, 1], // Number of photos in each row
|
||||
},
|
||||
// Another layout for containers wider than 750px
|
||||
{ breakpoint: 750, rows: [1, 3] },
|
||||
];
|
||||
</script>
|
||||
|
||||
<PhotoPack {images} {layouts} />
|
||||
```
|
||||
|
||||
<Canvas of={PhotoPackStories.Demo} />
|
||||
|
||||
## ArchieML
|
||||
|
||||
```yaml
|
||||
[blocks]
|
||||
# ...
|
||||
|
||||
type: photo-pack
|
||||
id: my-photo-pack
|
||||
class: mb-2
|
||||
width: wide
|
||||
textWidth: normal
|
||||
gap: 10
|
||||
[.images]
|
||||
src: images/my-img-1.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-2.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-3.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-4.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-5.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
[]
|
||||
|
||||
# ...
|
||||
[]
|
||||
```
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'text'}
|
||||
<!-- ... -->
|
||||
{:else if block.type === 'photo-pack'}
|
||||
<PhotoPack
|
||||
id={block.id}
|
||||
class={block.class}
|
||||
width={block.width}
|
||||
textWidth={block.textWidth}
|
||||
images={block.images.map((img) => ({
|
||||
src: `${assets}/${img.src}`,
|
||||
altText: img.altText,
|
||||
caption: img.caption,
|
||||
}))}
|
||||
layouts={[
|
||||
{ breakpoint: 750, rows: [2, 3] },
|
||||
{ breakpoint: 450, rows: [1, 2, 2] },
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- ... -->
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
## Misisng alt text
|
||||
|
||||
If any of your images is missing `altText` a small warning will overlay the photo.
|
||||
|
|
@ -1,19 +1,10 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import archieMLDocs from './stories/docs/archieML.md?raw';
|
||||
// @ts-ignore raw
|
||||
import missingAltTextDocs from './stories/docs/missingAltText.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import PhotoPack from './PhotoPack.svelte';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Multimedia/PhotoPack',
|
||||
component: PhotoPack,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
|
|
@ -24,37 +15,34 @@
|
|||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
const defaultImages = [
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
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',
|
||||
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,
|
||||
"Spain's Sergio Busquets and Aymeric Laporte react before a Germany goal is disallowed following a VAR review.",
|
||||
altText: 'alt text',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1640x1180.jpg',
|
||||
altText: 'alt text',
|
||||
src: 'https://graphics.thomsonreuters.com/cdn/django-tools/media/graphics-gallery/galleries/world-cup-2022/spain-germany-11-27/2022-11-27T194619Z_2007900040_UP1.jpeg',
|
||||
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',
|
||||
"Spain's Sergio Busquets fouls Germany's Jamal Musiala before being shown yellow card.",
|
||||
altText: 'alt text',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1200x900.jpg',
|
||||
altText: 'alt text',
|
||||
src: 'https://graphics.thomsonreuters.com/cdn/django-tools/media/graphics-gallery/galleries/world-cup-2022/spain-germany-11-27/2022-11-27T194619Z_635809122_UP1E.jpeg',
|
||||
caption:
|
||||
'People walk past the remains of a missile at a bus terminal in Kyiv, Ukraine March 4, 2022. REUTERS/Valentyn Ogirenko',
|
||||
"Spain's Sergio Busquets is shown a yellow card by referee Danny Desmond Makkelie.",
|
||||
altText: 'alt text',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
src: 'https://graphics.thomsonreuters.com/cdn/django-tools/media/graphics-gallery/galleries/world-cup-2022/spain-germany-11-27/2022-11-27T191015Z_1293757566_UP1.jpeg',
|
||||
caption:
|
||||
'People walk past the remains of a missile at a bus terminal. REUTERS/Valentyn Ogirenko',
|
||||
"Spain's Sergio Busquets in action with Germany's Thomas Muller.",
|
||||
altText: 'alt text',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -70,34 +58,7 @@
|
|||
width: 'wide',
|
||||
textWidth: 'normal',
|
||||
gap: '15',
|
||||
images: [
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
caption: 'Lorem ipsum. Reuters/Photog',
|
||||
maxHeight: 600,
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
caption: 'Lorem ipsum. Reuters/Photog',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
caption: 'Lorem ipsum. Reuters/Photog',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
caption: 'Lorem ipsum. Reuters/Photog',
|
||||
},
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
caption: 'Lorem ipsum. Reuters/Photog',
|
||||
},
|
||||
],
|
||||
images: defaultImages,
|
||||
layouts: [
|
||||
{ breakpoint: 750, rows: [2, 3] },
|
||||
{ breakpoint: 450, rows: [1, 2, 2] },
|
||||
|
|
@ -106,8 +67,8 @@
|
|||
|
||||
const altTextImages = [
|
||||
{
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
altText: 'alt text',
|
||||
src: 'https://via.placeholder.com/1024x768.jpg',
|
||||
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,
|
||||
|
|
@ -122,37 +83,23 @@
|
|||
const altTextLayouts = [{ breakpoint: 450, rows: [2] }];
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ ...args })}
|
||||
<PhotoPack {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
name="Demo"
|
||||
args={{
|
||||
width: 'wide',
|
||||
textWidth: 'normal',
|
||||
images: defaultImages,
|
||||
layouts: defaultLayouts,
|
||||
}}"
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="ArchieML"
|
||||
{...withStoryDocs(archieMLDocs)}
|
||||
args="{archieMLBlock}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Missing altText"
|
||||
args="{{
|
||||
exportName="MissingAltText"
|
||||
args={{
|
||||
width: 'wide',
|
||||
textWidth: 'normal',
|
||||
images: altTextImages,
|
||||
layouts: altTextLayouts,
|
||||
}}"
|
||||
{...withStoryDocs(missingAltTextDocs)}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,102 +1,92 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `PhotoPack` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-multimedia-photopack--docs) -->
|
||||
<script lang="ts">
|
||||
interface Image {
|
||||
import Block from '../Block/Block.svelte';
|
||||
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
|
||||
// Utils
|
||||
import { random4 } from '../../utils';
|
||||
import { groupRows } from './utils';
|
||||
// import { groupRows } from './utils';
|
||||
|
||||
// Types
|
||||
export interface Image {
|
||||
src: string;
|
||||
altText: string;
|
||||
caption?: string;
|
||||
maxHeight?: number;
|
||||
}
|
||||
/**
|
||||
* Array of image objects
|
||||
* @required
|
||||
*/
|
||||
export let images: Image[] = [];
|
||||
|
||||
interface Layout {
|
||||
export interface Layout {
|
||||
breakpoint: number;
|
||||
rows: number[];
|
||||
}
|
||||
/**
|
||||
* Array of layout objects
|
||||
* @required
|
||||
*/
|
||||
export let layouts: Layout[] = [];
|
||||
/**
|
||||
* Gap between images.
|
||||
* @type {number}
|
||||
*/
|
||||
export let gap = 15;
|
||||
|
||||
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}
|
||||
*/
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
|
||||
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
|
||||
|
||||
/** Width of the component within the text well. */
|
||||
export let width: ContainerWidth = 'normal';
|
||||
interface Props {
|
||||
/** Array of image objects */
|
||||
images: Image[];
|
||||
/** Array of layout objects */
|
||||
layouts?: Layout[];
|
||||
/** Gap between images. */
|
||||
gap?: number;
|
||||
/** Add an ID to target with SCSS. Should be unique from all other elements. */
|
||||
id?: string;
|
||||
/** Add a class to target with SCSS. */
|
||||
class: string;
|
||||
/** Width of the component within the text well: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid' */
|
||||
width: ContainerWidth;
|
||||
/** 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`: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid' */
|
||||
textWidth: ContainerWidth;
|
||||
}
|
||||
|
||||
let {
|
||||
images,
|
||||
layouts,
|
||||
gap = 15,
|
||||
id = 'photopack-' + random4() + random4(),
|
||||
class: cls = '',
|
||||
width = 'normal',
|
||||
textWidth = 'normal',
|
||||
}: Props = $props();
|
||||
|
||||
let containerWidth = $state(0); // or undefined?
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*
|
||||
* Sort layouts by descending breakpoints.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* So, we need to use `toSorted` instead.
|
||||
*/
|
||||
export let textWidth: ContainerWidth = 'normal';
|
||||
|
||||
import Block from '../Block/Block.svelte';
|
||||
import PaddingReset from '../PaddingReset/PaddingReset.svelte';
|
||||
import Markdown from '../Markdown/Markdown.svelte';
|
||||
|
||||
let containerWidth: number;
|
||||
|
||||
const groupRows = (images: Image[], layout?: Layout) => {
|
||||
// Default layout, one img per row
|
||||
if (!layout) return images.map((img) => [img]);
|
||||
// Otherwise, chunk into rows according to layout scheme
|
||||
let i = 0;
|
||||
const rows = [];
|
||||
for (const rowLength of layout.rows) {
|
||||
const row = [];
|
||||
for (const imgI of [...Array(rowLength).keys()]) {
|
||||
row.push(images[imgI + i]);
|
||||
}
|
||||
rows.push(row);
|
||||
i += rowLength;
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
// Sort so breakpoints always descend
|
||||
$: layouts.sort((a, b) => (a.breakpoint < b.breakpoint ? 1 : -1));
|
||||
$: layout = layouts.find(
|
||||
(l) =>
|
||||
// Must have valid rows schema, i.e., adds to the total number of images
|
||||
l.rows.reduce((a, b) => a + b, 0) === images.length &&
|
||||
// Breakpoint is higher than container width
|
||||
(containerWidth || 0) >= l.breakpoint
|
||||
let sortedLayouts = $derived(
|
||||
layouts?.toSorted((a, b) => (a.breakpoint < b.breakpoint ? 1 : -1))
|
||||
);
|
||||
$: rows = groupRows(images, layout);
|
||||
|
||||
let layout = $derived(
|
||||
sortedLayouts?.find(
|
||||
(l) =>
|
||||
// Must have valid rows schema, i.e., adds to the total number of images
|
||||
l.rows.reduce((a, b) => a + b, 0) === images.length &&
|
||||
// Breakpoint is higher than container width
|
||||
(containerWidth || 0) >= l.breakpoint
|
||||
)
|
||||
);
|
||||
|
||||
let rows = $derived(groupRows(images, layout));
|
||||
</script>
|
||||
|
||||
<Block {width} {id} class="photopack fmy-6 {cls}">
|
||||
<div class="photopack-container w-full" bind:clientWidth="{containerWidth}">
|
||||
<div class="photopack-container w-full" bind:clientWidth={containerWidth}>
|
||||
{#each rows as row, ri}
|
||||
<div
|
||||
class="photopack-row flex justify-between"
|
||||
style:gap="0 {gap}px"
|
||||
style:margin-bottom="{ri < rows.length - 1 ? gap + 'px' : ''}"
|
||||
style:margin-bottom={ri < rows.length - 1 ? gap + 'px' : ''}
|
||||
>
|
||||
{#each row as img, i}
|
||||
<figure
|
||||
|
|
@ -105,9 +95,9 @@
|
|||
>
|
||||
<img
|
||||
class="m-0 w-full h-full object-cover"
|
||||
src="{img.src}"
|
||||
alt="{img.altText}"
|
||||
style:max-height="{img.maxHeight ? img.maxHeight + 'px' : ''}"
|
||||
src={img.src}
|
||||
alt={img.altText}
|
||||
style:max-height={img.maxHeight ? img.maxHeight + 'px' : ''}
|
||||
/>
|
||||
{#if !img.altText}
|
||||
<div class="alt-warning absolute text-xxs py-1 px-2">altText</div>
|
||||
|
|
@ -117,14 +107,14 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<PaddingReset containerIsFluid="{width === 'fluid'}">
|
||||
<PaddingReset containerIsFluid={width === 'fluid'}>
|
||||
<div class="notes contents">
|
||||
<Block width="{textWidth}" class="photopack-captions-container">
|
||||
<Block width={textWidth} class="photopack-captions-container">
|
||||
{#each rows as row, ri}
|
||||
{#each row as img, i}
|
||||
{#if img.caption}
|
||||
<div id="{id}-figure-{ri}-{i}" class="caption">
|
||||
<Markdown source="{img.caption}" />
|
||||
<Markdown source={img.caption} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
```yaml
|
||||
[blocks]
|
||||
# ...
|
||||
|
||||
type: photo-pack
|
||||
id: my-photo-pack
|
||||
class: mb-2
|
||||
width: wide
|
||||
textWidth: normal
|
||||
gap: 10
|
||||
[.images]
|
||||
src: images/my-img-1.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-2.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-3.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-4.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
|
||||
src: images/my-img-5.jpg
|
||||
altText: Alt text
|
||||
caption: Lorem ipsum. REUTERS/Photog
|
||||
[]
|
||||
|
||||
# ...
|
||||
[]
|
||||
```
|
||||
|
||||
```svelte
|
||||
<!-- App.svelte -->
|
||||
{#each content.blocks as block}
|
||||
{#if block.type === 'text'}
|
||||
<!-- ... -->
|
||||
|
||||
{:else if block.type === 'photo-pack'}
|
||||
<PhotoPack
|
||||
id={block.id}
|
||||
class={block.class}
|
||||
width={block.width}
|
||||
textWidth={block.textWidth}
|
||||
images={block.images.map((img) => ({
|
||||
src: `${assets}/${img.src}`,
|
||||
altText: img.altText,
|
||||
caption: img.caption,
|
||||
}))}
|
||||
layouts={[
|
||||
{ breakpoint: 750, rows: [2, 3] },
|
||||
{ breakpoint: 450, rows: [1, 2, 2] },
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- ... -->
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
The `PhotoPack` component makes simple photo grids with custom layouts at whatever breakpoint you need.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { PhotoPack } from '@reuters-graphics/graphics-components';
|
||||
|
||||
const images = [
|
||||
{
|
||||
src: 'https://...',
|
||||
altText: 'Alt text',
|
||||
caption: 'Lorem ipsum. REUTERS/Photog',
|
||||
// Optional max-height of image across all layouts
|
||||
maxHeight: 800,
|
||||
},
|
||||
// ...
|
||||
];
|
||||
|
||||
const layouts = [
|
||||
{
|
||||
// Breakpoint above which this layout applies
|
||||
breakpoint: 450,
|
||||
// Number of photos in each row of this layout, adding up to the total number of images
|
||||
rows: [1, 2, 1],
|
||||
},
|
||||
{ breakpoint: 750, rows: [1, 3] },
|
||||
];
|
||||
</script>
|
||||
|
||||
<PhotoPack images="{images}" layouts="{layouts}" />
|
||||
```
|
||||
|
||||
`images` are defined with their src, alt text, captions and an optional `maxHeight`, which makes sure the image is no taller than that height in pixels in any layout.
|
||||
|
||||
`layouts` describe how those images will be laid out in rows at different breakpoints. The default layout (mobile-first) is each photo on its own row, stacked vertically, but you can group photos into `rows` above a `breakpoint` by specifying the number of photos that should go in that row. For example:
|
||||
|
||||
```javascript
|
||||
const layouts = [{
|
||||
breakpoint: 450,
|
||||
rows: [1,2,1],
|
||||
}];
|
||||
```
|
||||
|
||||
... tells the component that when the `PhotoPack` container is 450 pixels or wider, it should group the 4 photos in 3 rows, 1 in the first, 2 in the second and 1 in the last.
|
||||
|
||||
You can define as many layouts for as many images as you like.
|
||||
|
|
@ -1 +0,0 @@
|
|||
If any of your images is missing `altText` a small warning will overlay the photo.
|
||||
18
src/components/PhotoPack/utils.ts
Normal file
18
src/components/PhotoPack/utils.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type { Image, Layout } from './PhotoPack.svelte';
|
||||
|
||||
export const groupRows = (images: Image[], layout?: Layout) => {
|
||||
// Default layout, one img per row
|
||||
if (!layout) return images.map((img) => [img]);
|
||||
// Otherwise, chunk into rows according to layout scheme
|
||||
let i = 0;
|
||||
const rows = [];
|
||||
for (const rowLength of layout.rows) {
|
||||
const row = [];
|
||||
for (const imgI of [...Array(rowLength).keys()]) {
|
||||
row.push(images[imgI + i]);
|
||||
}
|
||||
rows.push(row);
|
||||
i += rowLength;
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<PymChild {...args} />
|
||||
<div>Nothing to see here. 😎</div>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<ReferralBlock {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -49,21 +49,21 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
section: '/lifestyle/sports/',
|
||||
number: 4,
|
||||
class: 'fmy-0',
|
||||
heading: 'More World Cup coverage',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="By collection"
|
||||
args="{{
|
||||
args={{
|
||||
collection: 'x-trump',
|
||||
number: 6,
|
||||
class: 'fmy-8',
|
||||
heading: 'The latest Trump coverage',
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(collectionDocs)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -96,33 +96,33 @@
|
|||
<Block {width} {id} class="referrals-block {cls}">
|
||||
<div
|
||||
class="block-container"
|
||||
class:stacked="{clientWidth && clientWidth < 750}"
|
||||
class:stacked={clientWidth && clientWidth < 750}
|
||||
bind:clientWidth
|
||||
>
|
||||
{#if heading}
|
||||
<div
|
||||
class="heading h4 font-bold"
|
||||
class:stacked="{clientWidth && clientWidth < 750}"
|
||||
class:stacked={clientWidth && clientWidth < 750}
|
||||
>
|
||||
{heading}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="referral-container inline-flex flex-wrap w-full justify-between"
|
||||
class:stacked="{clientWidth && clientWidth < 750}"
|
||||
class:xs="{clientWidth && clientWidth < 450}"
|
||||
class:stacked={clientWidth && clientWidth < 750}
|
||||
class:xs={clientWidth && clientWidth < 450}
|
||||
>
|
||||
{#each referrals as referral}
|
||||
<div class="referral">
|
||||
<a
|
||||
href="https://www.reuters.com{referral.canonical_url}"
|
||||
target="{linkTarget}"
|
||||
rel="{linkTarget === '_blank' ? 'noreferrer' : null}"
|
||||
target={linkTarget}
|
||||
rel={linkTarget === '_blank' ? 'noreferrer' : null}
|
||||
>
|
||||
<div class="referral-pack flex justify-around my-0 mx-auto">
|
||||
<div
|
||||
class="headline"
|
||||
class:xs="{clientWidth && clientWidth < 450}"
|
||||
class:xs={clientWidth && clientWidth < 450}
|
||||
>
|
||||
<div
|
||||
class="kicker m-0 body-caption leading-tighter"
|
||||
|
|
@ -145,14 +145,14 @@
|
|||
</div>
|
||||
<div
|
||||
class="image-container block m-0 overflow-hidden relative"
|
||||
class:xs="{clientWidth && clientWidth < 450}"
|
||||
class:xs={clientWidth && clientWidth < 450}
|
||||
>
|
||||
<img
|
||||
class="block object-cover m-0 w-full"
|
||||
data-chromatic="ignore"
|
||||
src="{referral.thumbnail.renditions.landscape['240w']}"
|
||||
alt="{referral.thumbnail.caption ||
|
||||
referral.thumbnail.alt_text}"
|
||||
src={referral.thumbnail.renditions.landscape['240w']}
|
||||
alt={referral.thumbnail.caption ||
|
||||
referral.thumbnail.alt_text}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
{#if preload === 0 || (i >= (stackBackground ? 0 : index - preload) && i <= index + preload)}
|
||||
<div
|
||||
class="step-background step-{i + 1} w-full absolute"
|
||||
class:visible="{stackBackground ? i <= index : i === index}"
|
||||
class:invisible="{stackBackground ? i > index : i !== index}"
|
||||
class:visible={stackBackground ? i <= index : i === index}
|
||||
class:invisible={stackBackground ? i > index : i !== index}
|
||||
>
|
||||
<step.background {...step.backgroundProps || {}}></step.background>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
let { step, backgroundWidth, index }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Block width="{backgroundWidth}" class="background-container step-{index + 1}">
|
||||
<Block width={backgroundWidth} class="background-container step-{index + 1}">
|
||||
<div class="embedded-background step-{index + 1}" aria-hidden="true">
|
||||
<step.background {...step.backgroundProps || {}}></step.background>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,18 +17,18 @@
|
|||
|
||||
{#if typeof step.altText === 'string'}
|
||||
<div class="background-alt-text visually-hidden">
|
||||
<Markdown source="{step.altText}" />
|
||||
<Markdown source={step.altText} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if typeof step.foreground === 'string'}
|
||||
<Block class="body-text step-{index + 1}">
|
||||
<div class="embedded-foreground step-{index + 1}">
|
||||
<Markdown source="{step.foreground}" />
|
||||
<Markdown source={step.foreground} />
|
||||
</div>
|
||||
|
||||
{#if typeof step.altText === 'string'}
|
||||
<div class="background-alt-text visually-hidden">
|
||||
<Markdown source="{step.altText}" />
|
||||
<Markdown source={step.altText} />
|
||||
</div>
|
||||
{/if}
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@
|
|||
<div class="empty-step-foreground"></div>
|
||||
{#if typeof step.altText === 'string'}
|
||||
<div class="background-alt-text visually-hidden">
|
||||
<Markdown source="{step.altText}" />
|
||||
<Markdown source={step.altText} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="step-foreground w-full">
|
||||
{#if typeof step.foreground === 'string'}
|
||||
<Markdown source="{step.foreground}" />
|
||||
<Markdown source={step.foreground} />
|
||||
{:else}
|
||||
<step.foreground {...step.foregroundProps || {}}></step.foreground>
|
||||
{/if}
|
||||
</div>
|
||||
{#if typeof step.altText === 'string'}
|
||||
<div class="background-alt-text visually-hidden">
|
||||
<Markdown source="{step.altText}" />
|
||||
<Markdown source={step.altText} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Scroller {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
steps: [
|
||||
{
|
||||
background: BasicStep,
|
||||
|
|
@ -112,14 +112,14 @@
|
|||
backgroundWidth: 'fluid',
|
||||
embeddedLayout: 'fb',
|
||||
embedded: false,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="ArchieML" args="{docBlock}" {...withStoryDocs(archieMLDocs)} />
|
||||
<Story name="ArchieML" args={docBlock} {...withStoryDocs(archieMLDocs)} />
|
||||
|
||||
<Story
|
||||
name="Foreground components"
|
||||
args="{{
|
||||
args={{
|
||||
steps: [
|
||||
{
|
||||
background: BasicStep,
|
||||
|
|
@ -142,13 +142,13 @@
|
|||
backgroundWidth: 'fluid',
|
||||
embeddedLayout: 'fb',
|
||||
embedded: false,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(interactiveDocs)}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Ai2svelte"
|
||||
args="{{
|
||||
args={{
|
||||
steps: [
|
||||
{
|
||||
background: AiMap1,
|
||||
|
|
@ -175,6 +175,6 @@
|
|||
backgroundWidth: 'fluid',
|
||||
embeddedLayout: 'fb',
|
||||
embedded: false,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(ai2svelteDocs)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -123,13 +123,13 @@
|
|||
<div
|
||||
slot="background"
|
||||
class="background min-h-screen relative p-0 flex justify-center"
|
||||
class:right="{foregroundPosition === 'left opposite'}"
|
||||
class:left="{foregroundPosition === 'right opposite'}"
|
||||
class:right={foregroundPosition === 'left opposite'}
|
||||
class:left={foregroundPosition === 'right opposite'}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="scroller-graphic-well w-full">
|
||||
<Block
|
||||
width="{backgroundWidth}"
|
||||
width={backgroundWidth}
|
||||
class="background-container step-{index +
|
||||
1} my-0 min-h-screen flex justify-center items-center relative"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2021-09-30 14:21 -->
|
||||
|
||||
<div id="g-step-1-box" bind:clientWidth="{width}">
|
||||
<div id="g-step-1-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: XL -->
|
||||
{#if width && width >= 1200}
|
||||
<div id="g-step-1-xl" class="g-artboard" style="">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2021-09-30 14:20 -->
|
||||
|
||||
<div id="g-step-2-box" bind:clientWidth="{width}">
|
||||
<div id="g-step-2-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: XL -->
|
||||
{#if width && width >= 1200}
|
||||
<div id="g-step-2-xl" class="g-artboard" style="">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<!-- Generated by ai2html v0.100.0 - 2021-09-30 14:28 -->
|
||||
|
||||
<div id="g-step-3-box" bind:clientWidth="{width}">
|
||||
<div id="g-step-3-box" bind:clientWidth={width}>
|
||||
<!-- Artboard: XL -->
|
||||
{#if width && width >= 1200}
|
||||
<div id="g-step-3-xl" class="g-artboard" style="">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<p>The count is {count}</p>
|
||||
|
||||
<button
|
||||
onclick="{() => {
|
||||
onclick={() => {
|
||||
count += 1;
|
||||
}}">Click Me</button
|
||||
}}>Click Me</button
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
let { colour = 'lightblue' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="step" style="{`background: ${colour};`}"></div>
|
||||
<div class="step" style={`background: ${colour};`}></div>
|
||||
|
||||
<style lang="scss">
|
||||
.step {
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<SearchInput {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story name="Default" args="{{}}" />
|
||||
<Story name="Default" args={{}} />
|
||||
|
|
|
|||
|
|
@ -38,17 +38,17 @@
|
|||
id="search--input"
|
||||
class="search--input body-caption pl-8"
|
||||
type="text"
|
||||
placeholder="{searchPlaceholder}"
|
||||
oninput="{input}"
|
||||
placeholder={searchPlaceholder}
|
||||
oninput={input}
|
||||
bind:value
|
||||
/>
|
||||
<div
|
||||
class="search--x absolute"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class:invisible="{!active}"
|
||||
onclick="{clear}"
|
||||
onkeyup="{clear}"
|
||||
class:invisible={!active}
|
||||
onclick={clear}
|
||||
onkeyup={clear}
|
||||
>
|
||||
<X />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
{#each links.social_links as link}
|
||||
{@const SvelteComponent = symbols[link.type]}
|
||||
<li class="social-links symbol">
|
||||
<a href="{normalizeUrl(link.url)}">
|
||||
<a href={normalizeUrl(link.url)}>
|
||||
<div class="button">
|
||||
<div class="social">
|
||||
<SvelteComponent />
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<ul class="link-group">
|
||||
{#each links.ad_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">
|
||||
<a href={normalizeUrl(link.url)}>
|
||||
{link.text}
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
</section>
|
||||
<p class="disclaimer">
|
||||
All quotes delayed a minimum of 15 minutes. <a
|
||||
href="{normalizeUrl(links.disclaimer_link)}"
|
||||
href={normalizeUrl(links.disclaimer_link)}
|
||||
>See here for a complete list of exchanges and delays</a
|
||||
>.
|
||||
</p>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<ul class="link-group">
|
||||
{#each links.misc_links.filter((d) => !d.self) as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">
|
||||
<a href={normalizeUrl(link.url)}>
|
||||
{link.text}
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
</section>
|
||||
<p class="copyright">
|
||||
© {links.copyright_year} Reuters.
|
||||
<a href="{normalizeUrl(links.copyright_link)}">All rights reserved</a>
|
||||
<a href={normalizeUrl(links.copyright_link)}>All rights reserved</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
const mobileBreakpoint = 745;
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth="{windowWidth}" />
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
{#if links.latest_links}
|
||||
<section class="quick-links">
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<ul>
|
||||
{#each links.latest_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">{link.text}</a>
|
||||
<a href={normalizeUrl(link.url)}>{link.text}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
<div class="symbol">
|
||||
<SvelteComponent />
|
||||
</div>
|
||||
<a href="{normalizeUrl(link.url)}">
|
||||
<a href={normalizeUrl(link.url)}>
|
||||
{link.text}
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
<ul>
|
||||
{#each links.latest_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">{link.text}</a>
|
||||
<a href={normalizeUrl(link.url)}>{link.text}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
<ul>
|
||||
{#each links.browse_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">{link.text}</a>
|
||||
<a href={normalizeUrl(link.url)}>{link.text}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
<div class="symbol">
|
||||
<SvelteComponent_1 />
|
||||
</div>
|
||||
<a href="{normalizeUrl(link.url)}">
|
||||
<a href={normalizeUrl(link.url)}>
|
||||
{link.text}
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
<ul>
|
||||
{#each links.about_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">{link.text}</a>
|
||||
<a href={normalizeUrl(link.url)}>{link.text}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
<ul>
|
||||
{#each links.stay_informed_links as link}
|
||||
<li>
|
||||
<a href="{normalizeUrl(link.url)}">{link.text}</a>
|
||||
<a href={normalizeUrl(link.url)}>{link.text}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
import Theme from '../Theme/Theme.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<div>
|
||||
<SiteFooter {...args} />
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
<Story
|
||||
name="Remove referrals"
|
||||
args="{{ includeReferrals: false }}"
|
||||
args={{ includeReferrals: false }}
|
||||
{...withStoryDocs(removeReferralsDocs)}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@
|
|||
|
||||
<footer
|
||||
class="my-0"
|
||||
style="{`
|
||||
style={`
|
||||
--nav-background: var(--theme-colour-background, #fff);
|
||||
--nav-primary: var(--theme-colour-text-primary, #404040);
|
||||
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
|
||||
--theme-font-family-sans-serif: Knowledge, sans-serif;
|
||||
`}"
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
{#if includeReferrals}
|
||||
|
|
@ -61,9 +61,9 @@
|
|||
/>
|
||||
</PaddingReset>
|
||||
{/if}
|
||||
<QuickLinks links="{data[0]}" />
|
||||
<CompanyLinks links="{data[0]}" />
|
||||
<LegalLinks links="{data[0]}" />
|
||||
<QuickLinks links={data[0]} />
|
||||
<CompanyLinks links={data[0]} />
|
||||
<LegalLinks links={data[0]} />
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@
|
|||
<div
|
||||
class="overlay"
|
||||
aria-modal="true"
|
||||
style="{`
|
||||
style={`
|
||||
--nav-background: var(--theme-colour-background, #fff);
|
||||
--nav-primary: var(--theme-colour-text-primary, #404040);
|
||||
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
|
||||
--nav-accent: var(--theme-colour-brand-logo, #fa6400);
|
||||
--nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgba(64,64,64,.08));
|
||||
`}"
|
||||
`}
|
||||
>
|
||||
<header class="header">
|
||||
<div class="logo">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
textColour="var(--nav-primary)"
|
||||
/>
|
||||
</div>
|
||||
<button class="button close-button" onclick="{releaseMobileMenu}">
|
||||
<button class="button close-button" onclick={releaseMobileMenu}>
|
||||
<div class="button-container">
|
||||
<CloseIcon />
|
||||
</div>
|
||||
|
|
@ -43,14 +43,14 @@
|
|||
</header>
|
||||
{#each data.sections as section}
|
||||
<section class="section">
|
||||
<a class="section-link" href="{normalizeUrl(section.url)}"
|
||||
<a class="section-link" href={normalizeUrl(section.url)}
|
||||
>{section.name}</a
|
||||
>
|
||||
{#if section.children}
|
||||
<ul class="subsections">
|
||||
{#each section.children as sub}
|
||||
<li>
|
||||
<a class="subsection-link" href="{normalizeUrl(sub.url)}">
|
||||
<a class="subsection-link" href={normalizeUrl(sub.url)}>
|
||||
{sub.name}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<svg
|
||||
class="arrow"
|
||||
class:rotated="{rotate}"
|
||||
class:rotated={rotate}
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@
|
|||
{#each sections as section}
|
||||
<section
|
||||
class="more-section-group"
|
||||
class:has-children="{section.children}"
|
||||
class:has-children={section.children}
|
||||
>
|
||||
<a href="{normalizeUrl(section.url)}" class="section-link">
|
||||
<a href={normalizeUrl(section.url)} class="section-link">
|
||||
{section.name}
|
||||
</a>
|
||||
{#if section.children}
|
||||
<ul class="subsections">
|
||||
{#each section.children as sub}
|
||||
<li>
|
||||
<a class="subsection-link" href="{normalizeUrl(sub.url)}"
|
||||
<a class="subsection-link" href={normalizeUrl(sub.url)}
|
||||
>{sub.name}</a
|
||||
>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</script>
|
||||
|
||||
<NavDropdown {headingText}>
|
||||
<a href="{normalizeUrl(section.url)}">
|
||||
<a href={normalizeUrl(section.url)}>
|
||||
<span class="heading">
|
||||
Browse {section.name}
|
||||
</span>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<ul class="sections-group">
|
||||
{#each section.children.slice(0, splitCount) as sub}
|
||||
<li>
|
||||
<a class="subsection-link" href="{normalizeUrl(sub.url)}">
|
||||
<a class="subsection-link" href={normalizeUrl(sub.url)}>
|
||||
{sub.name}
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<ul class="sections-group">
|
||||
{#each section.children.slice(splitCount) as sub}
|
||||
<li>
|
||||
<a class="subsection-link" href="{normalizeUrl(sub.url)}">
|
||||
<a class="subsection-link" href={normalizeUrl(sub.url)}>
|
||||
{sub.name}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@
|
|||
</script>
|
||||
|
||||
<div class="story-card">
|
||||
<a href="{normalizeUrl(story.canonical_url)}">
|
||||
<div class="story-text" class:has-thumbnail="{thumbnail}">
|
||||
<a href={normalizeUrl(story.canonical_url)}>
|
||||
<div class="story-text" class:has-thumbnail={thumbnail}>
|
||||
<span>{story.title}</span>
|
||||
<time datetime="{story.display_time}">{getTime(story.display_time)}</time>
|
||||
<time datetime={story.display_time}>{getTime(story.display_time)}</time>
|
||||
</div>
|
||||
{#if thumbnail}
|
||||
<div class="thumbnail">
|
||||
<img
|
||||
src="{thumbnail.renditions.square['120w']}"
|
||||
alt="{thumbnail.alt_text}"
|
||||
src={thumbnail.renditions.square['120w']}
|
||||
alt={thumbnail.alt_text}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
let hiddenSections = $derived(sections.slice(displayCount));
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth="{windowWidth}" />
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
<div class="nav-bar">
|
||||
<nav aria-label="Main navigation">
|
||||
|
|
@ -35,50 +35,50 @@
|
|||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<li
|
||||
class="nav-item category link"
|
||||
onmouseenter="{() => {
|
||||
onmouseenter={() => {
|
||||
navTimeout = setTimeout(
|
||||
() => activeSection.set(section.id),
|
||||
timeout
|
||||
);
|
||||
}}"
|
||||
onfocus="{() => activeSection.set(section.id)}"
|
||||
onmouseleave="{() => {
|
||||
}}
|
||||
onfocus={() => activeSection.set(section.id)}
|
||||
onmouseleave={() => {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}}"
|
||||
onblur="{() => {
|
||||
}}
|
||||
onblur={() => {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}}"
|
||||
onclick="{() => {
|
||||
}}
|
||||
onclick={() => {
|
||||
if ($activeSection === section.id) {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="nav-button link"
|
||||
class:open="{section.id === $activeSection}"
|
||||
class:open={section.id === $activeSection}
|
||||
>
|
||||
<a href="{normalizeUrl(section.url)}">
|
||||
<a href={normalizeUrl(section.url)}>
|
||||
{section.name}
|
||||
</a>
|
||||
<button class="button">
|
||||
<DownArrow rotate="{section.id === $activeSection}" />
|
||||
<DownArrow rotate={section.id === $activeSection} />
|
||||
</button>
|
||||
</div>
|
||||
{#if $activeSection === section.id}
|
||||
<SectionDropdown
|
||||
{section}
|
||||
headingText="{`Latest in ${section.name}`}"
|
||||
headingText={`Latest in ${section.name}`}
|
||||
/>
|
||||
{/if}
|
||||
</li>
|
||||
{:else}
|
||||
<li class="nav-item category link">
|
||||
<div class="nav-button link">
|
||||
<a href="{normalizeUrl(section.url)}">
|
||||
<a href={normalizeUrl(section.url)}>
|
||||
{section.name}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -88,35 +88,35 @@
|
|||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<li
|
||||
class="nav-item"
|
||||
onmouseenter="{() => {
|
||||
onmouseenter={() => {
|
||||
navTimeout = setTimeout(() => activeSection.set('more'), timeout);
|
||||
}}"
|
||||
onfocus="{() => activeSection.set('more')}"
|
||||
onmouseleave="{() => {
|
||||
}}
|
||||
onfocus={() => activeSection.set('more')}
|
||||
onmouseleave={() => {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}}"
|
||||
onblur="{() => {
|
||||
}}
|
||||
onblur={() => {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}}"
|
||||
onclick="{() => {
|
||||
}}
|
||||
onclick={() => {
|
||||
if ($activeSection === 'more') {
|
||||
clearTimeout(navTimeout);
|
||||
activeSection.set(null);
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="nav-button more link"
|
||||
class:open="{$activeSection === 'more'}"
|
||||
class:open={$activeSection === 'more'}
|
||||
>
|
||||
<button class="button">
|
||||
<span>More <DownArrow rotate="{$activeSection === 'more'}" /></span>
|
||||
<span>More <DownArrow rotate={$activeSection === 'more'} /></span>
|
||||
</button>
|
||||
</div>
|
||||
{#if $activeSection === 'more'}
|
||||
<MoreDropdown sections="{hiddenSections}" />
|
||||
<MoreDropdown sections={hiddenSections} />
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
import Theme from '../Theme/Theme.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<div>
|
||||
<SiteHeader {...args} />
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@
|
|||
</script>
|
||||
|
||||
<header
|
||||
style="{`
|
||||
style={`
|
||||
--nav-background: var(--theme-colour-background, #fff);
|
||||
--nav-primary: var(--theme-colour-text-primary, #404040);
|
||||
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
|
||||
--nav-accent: var(--theme-colour-brand-logo, #fa6400);
|
||||
--nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgb(255 255 255 / 10%));
|
||||
--theme-font-family-sans-serif: Knowledge, sans-serif;
|
||||
`}"
|
||||
`}
|
||||
>
|
||||
<a href="#main-content" class="skip-link"> Skip to main content </a>
|
||||
<div class="nav-container show-nav">
|
||||
|
|
@ -76,10 +76,10 @@
|
|||
class="menu-button"
|
||||
aria-label="Menu"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="{isMobileMenuOpen}"
|
||||
onclick="{() => {
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
onclick={() => {
|
||||
isMobileMenuOpen = !isMobileMenuOpen;
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<div class="button-container">
|
||||
<MenuIcon />
|
||||
|
|
@ -95,10 +95,10 @@
|
|||
|
||||
<MobileMenu
|
||||
{isMobileMenuOpen}
|
||||
releaseMobileMenu="{() => {
|
||||
releaseMobileMenu={() => {
|
||||
isMobileMenuOpen = false;
|
||||
}}"
|
||||
data="{data[0]}"
|
||||
}}
|
||||
data={data[0]}
|
||||
/>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<SiteHeadline {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
section: 'Graphics',
|
||||
sectionUrl: 'https://graphics.reuters.com',
|
||||
hed: 'Ukraine makes surprising gains in counteroffensive',
|
||||
|
|
@ -55,15 +55,15 @@
|
|||
],
|
||||
publishTime: new Date('2021-09-12').toISOString(),
|
||||
updateTime: new Date('2021-09-12T13:57:00').toISOString(),
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="ArchieML" {...withStoryDocs(archieML)}>
|
||||
<SiteHeadline
|
||||
hed="{content.Hed}"
|
||||
section="{content.Section}"
|
||||
sectionUrl="{content.SectionUrl}"
|
||||
authors="{content.Authors.split(',')}"
|
||||
publishTime="{content.Published}"
|
||||
hed={content.Hed}
|
||||
section={content.Section}
|
||||
sectionUrl={content.SectionUrl}
|
||||
authors={content.Authors.split(',')}
|
||||
publishTime={content.Published}
|
||||
/>
|
||||
</Story>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
class="section-title mb-0 font-subhed text-xs text-secondary font-bold uppercase whitespace-nowrap tracking-wider"
|
||||
>
|
||||
{#if sectionUrl}
|
||||
<a class="no-underline !text-secondary" href="{sectionUrl}"
|
||||
<a class="no-underline !text-secondary" href={sectionUrl}
|
||||
>{section}</a
|
||||
>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Spinner {...args} />
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<div
|
||||
style:width="100%"
|
||||
style:height="{`${width + containerPadding * 2}px`}"
|
||||
style:height={`${width + containerPadding * 2}px`}
|
||||
class="component-container flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
</script>
|
||||
|
||||
<nav aria-label="pagination" class="pagination fmt-4">
|
||||
<button onclick="{goToPreviousPage}" disabled="{pageNumber === 1}"
|
||||
<button onclick={goToPreviousPage} disabled={pageNumber === 1}
|
||||
><div class="icon-wrapper">
|
||||
<LeftArrow />
|
||||
<span class="visually-hidden">Previous page</span>
|
||||
|
|
@ -63,8 +63,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="{goToNextPage}"
|
||||
disabled="{pageNumber === Math.ceil(n / pageSize)}"
|
||||
onclick={goToNextPage}
|
||||
disabled={pageNumber === Math.ceil(n / pageSize)}
|
||||
><div class="icon-wrapper">
|
||||
<RightArrow />
|
||||
<span class="visually-hidden">Next page</span>
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@
|
|||
class="select--input body-caption fpx-2"
|
||||
name="select--input"
|
||||
id="select--input"
|
||||
oninput="{input}"
|
||||
oninput={input}
|
||||
>
|
||||
{#each options as obj}
|
||||
<option value="{obj.value}">{obj.text}</option>
|
||||
<option value={obj.value}>{obj.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@
|
|||
class="avoid-clicks"
|
||||
>
|
||||
<path
|
||||
class:active="{sortDirection === 'descending' && active}"
|
||||
class:active={sortDirection === 'descending' && active}
|
||||
d="M6.76474 20.2244L0.236082 13.4649C-0.0786943 13.139 -0.0786943 12.6104 0.236082 12.2845C0.550521 11.959 1.19794 11.96 1.51305 12.2845L7.33483 12.2845L13 12.2845C13.43 11.8545 14.1195 11.9593 14.4339 12.2849C14.7487 12.6107 14.7487 13.1394 14.4339 13.4653L7.90492 20.2244C7.59015 20.5503 7.07952 20.5503 6.76474 20.2244Z"
|
||||
></path>
|
||||
<path
|
||||
class:active="{sortDirection === 'ascending' && active}"
|
||||
class:active={sortDirection === 'ascending' && active}
|
||||
d="M7.90518 0.244414L14.4338 7.00385C14.7486 7.32973 14.7486 7.85838 14.4338 8.18427C14.1194 8.50981 13.472 8.50876 13.1569 8.18427L7.33509 8.18427L1.66992 8.18427C1.23992 8.61427 0.550443 8.50946 0.236003 8.18392C-0.0787725 7.85803 -0.0787725 7.32938 0.236003 7.0035L6.765 0.244414C7.07978 -0.0814713 7.5904 -0.0814713 7.90518 0.244414Z"
|
||||
></path>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
const currencyFormat = (v: number) => '$' + v.toFixed(1);
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Table {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -55,74 +55,74 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
data: homeRuns,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Metadata"
|
||||
{...withStoryDocs(metadataDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
data: homeRuns,
|
||||
title: 'Career home run leaders',
|
||||
dek: 'In baseball, a home run (also known as a "dinger" or "tater") occurs when a batter hits the ball over the outfield fence. When a home run is hit, the batter and any runners on base are able to score.',
|
||||
notes: 'Note: As of Opening Day 2023',
|
||||
source: 'Source: Baseball Reference',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Truncate"
|
||||
{...withStoryDocs(truncateDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: homeRuns,
|
||||
truncated: true,
|
||||
source: 'Source: Baseball Reference',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Paginate"
|
||||
{...withStoryDocs(paginateDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
title: 'Press Freedom Index',
|
||||
paginated: true,
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Search"
|
||||
{...withStoryDocs(searchDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
searchable: true,
|
||||
paginated: true,
|
||||
title: 'Press Freedom Index',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Filter"
|
||||
{...withStoryDocs(filterDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
paginated: true,
|
||||
filterField: 'Region',
|
||||
title: 'Press Freedom Index',
|
||||
notes: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Search and filter"
|
||||
{...withStoryDocs(bothDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
searchable: true,
|
||||
filterField: 'Region',
|
||||
|
|
@ -130,13 +130,13 @@
|
|||
title: 'Press Freedom Index',
|
||||
dek: 'Reporters Without Borders ranks countries based on their level of press freedom using criteria such as the degree of media pluralism and violence against journalists.',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Sort"
|
||||
{...withStoryDocs(sortDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
sortable: true,
|
||||
sortField: 'Score',
|
||||
|
|
@ -145,13 +145,13 @@
|
|||
title: 'Press Freedom Index',
|
||||
notes: 'Note: data as of 2018',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Format"
|
||||
{...withStoryDocs(formatDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: richestWomen,
|
||||
title: 'The Richest Women in the World',
|
||||
source: 'Source: Forbes',
|
||||
|
|
@ -159,16 +159,16 @@
|
|||
sortField: 'Net worth (in billions)',
|
||||
sortDirection: 'descending',
|
||||
fieldFormatters: { 'Net worth (in billions)': currencyFormat },
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Style"
|
||||
{...withStoryDocs(styleDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
id: 'custom-table',
|
||||
data: richestWomen,
|
||||
title: 'The Richest Women in the World',
|
||||
source: 'Source: Forbes',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -230,9 +230,9 @@
|
|||
{#if filterList}
|
||||
<div class="table--header--filter">
|
||||
<Select
|
||||
label="{filterLabel || filterField}"
|
||||
options="{filterList}"
|
||||
on:select="{handleFilterInput}"
|
||||
label={filterLabel || filterField}
|
||||
options={filterList}
|
||||
on:select={handleFilterInput}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -240,7 +240,7 @@
|
|||
<div class="table--header--search">
|
||||
<SearchInput
|
||||
bind:searchPlaceholder
|
||||
on:search="{handleSearchInput}"
|
||||
on:search={handleSearchInput}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -252,9 +252,7 @@
|
|||
<table
|
||||
class="w-full"
|
||||
class:paginated
|
||||
class:truncated="{truncated &&
|
||||
!showAll &&
|
||||
data.length > truncateLength}"
|
||||
class:truncated={truncated && !showAll && data.length > truncateLength}
|
||||
>
|
||||
<thead class="table--thead">
|
||||
<tr>
|
||||
|
|
@ -262,22 +260,22 @@
|
|||
<th
|
||||
scope="col"
|
||||
class="table--thead--th h4 pl-0 py-2 pr-2"
|
||||
class:sortable="{sortable && sortableFields.includes(field)}"
|
||||
class:sort-ascending="{sortable &&
|
||||
class:sortable={sortable && sortableFields.includes(field)}
|
||||
class:sort-ascending={sortable &&
|
||||
sortField === field &&
|
||||
sortDirection === 'ascending'}"
|
||||
class:sort-descending="{sortable &&
|
||||
sortDirection === 'ascending'}
|
||||
class:sort-descending={sortable &&
|
||||
sortField === field &&
|
||||
sortDirection === 'descending'}"
|
||||
data-field="{field}"
|
||||
on:click="{handleSort}"
|
||||
sortDirection === 'descending'}
|
||||
data-field={field}
|
||||
on:click={handleSort}
|
||||
>
|
||||
{field}
|
||||
{#if sortable && sortableFields.includes(field)}
|
||||
<div class="table--thead--sortarrow fml-1 avoid-clicks">
|
||||
<SortArrow
|
||||
bind:sortDirection
|
||||
active="{sortField === field}"
|
||||
active={sortField === field}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -287,13 +285,13 @@
|
|||
</thead>
|
||||
<tbody class="table--tbody">
|
||||
{#each currentPageData as item, idx}
|
||||
<tr data-row-index="{idx}">
|
||||
<tr data-row-index={idx}>
|
||||
{#each includedFields as field}
|
||||
<td
|
||||
class="body-note pl-0 py-2 pr-2"
|
||||
data-row-index="{idx}"
|
||||
data-field="{field}"
|
||||
data-value="{item[field]}"
|
||||
data-row-index={idx}
|
||||
data-field={field}
|
||||
data-value={item[field]}
|
||||
>
|
||||
{@html formatValue(item, field)}
|
||||
</td>
|
||||
|
|
@ -302,7 +300,7 @@
|
|||
{/each}
|
||||
{#if searchable && searchText && currentPageData.length === 0}
|
||||
<tr>
|
||||
<td class="no-results" colspan="{includedFields.length}">
|
||||
<td class="no-results" colspan={includedFields.length}>
|
||||
No results found for "{searchText}"
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -312,7 +310,7 @@
|
|||
<tfoot class="table--tfoot">
|
||||
{#if notes}
|
||||
<tr>
|
||||
<td class="" colspan="{includedFields.length}">
|
||||
<td class="" colspan={includedFields.length}>
|
||||
<div class="fmt-2">
|
||||
{@html notes}
|
||||
</div>
|
||||
|
|
@ -321,7 +319,7 @@
|
|||
{/if}
|
||||
{#if source}
|
||||
<tr>
|
||||
<td class="" colspan="{includedFields.length}">
|
||||
<td class="" colspan={includedFields.length}>
|
||||
<div class="fmt-1">
|
||||
{@html source}
|
||||
</div>
|
||||
|
|
@ -337,7 +335,7 @@
|
|||
aria-label="Show all button"
|
||||
class="show-all flex items-center justify-center fmt-2"
|
||||
>
|
||||
<button class="body-caption" on:click="{toggleTruncate}"
|
||||
<button class="body-caption" on:click={toggleTruncate}
|
||||
>{#if showAll}Show fewer rows{:else}Show {data.length -
|
||||
truncateLength} more rows{/if}</button
|
||||
>
|
||||
|
|
@ -347,8 +345,8 @@
|
|||
<Pagination
|
||||
bind:pageNumber
|
||||
bind:pageSize
|
||||
bind:pageLength="{currentPageData.length}"
|
||||
bind:n="{sortedData.length}"
|
||||
bind:pageLength={currentPageData.length}
|
||||
bind:n={sortedData.length}
|
||||
/>{/if}
|
||||
</article>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
import SharkImg from './stories/shark.jpg';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ ...args })}
|
||||
<TestForSvelte5 {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -36,9 +36,9 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
src: SharkImg,
|
||||
altText: "Duh dum! It's a shark!!",
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@
|
|||
|
||||
<Block {width} {id} class="photo {cls}">
|
||||
<div
|
||||
style:background-image="{`url(${src})`}"
|
||||
style:height="{`${height}px`}"
|
||||
style:background-image={`url(${src})`}
|
||||
style:height={`${height}px`}
|
||||
></div>
|
||||
<p class="visually-hidden">{altText}</p>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
import Headline from './../Headline/Headline.svelte';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<div class="reset-article">
|
||||
<Theme {...args}>
|
||||
|
|
@ -52,19 +52,19 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
theme: themes.light,
|
||||
base: 'light',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name="Custom theme" {...withStoryDocs(customiseDocs)}>
|
||||
<Theme
|
||||
base="dark"
|
||||
theme="{{
|
||||
theme={{
|
||||
colour: { accent: 'var(--tr-light-orange)' },
|
||||
font: { family: { hed: 'FreightText, serif' } },
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<ThemedPage />
|
||||
</Theme>
|
||||
|
|
@ -73,15 +73,15 @@
|
|||
<Story name="Custom Google font" {...withStoryDocs(customiseFontDocs)}>
|
||||
<Theme
|
||||
base="light"
|
||||
theme="{{
|
||||
theme={{
|
||||
font: { family: { hed: 'Bebas Neue, sans-serif' } },
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<div class="gfont">
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
/>
|
||||
</div>
|
||||
</Theme>
|
||||
|
|
@ -91,9 +91,9 @@
|
|||
<div id="pattern-bg">
|
||||
<Theme
|
||||
base="dark"
|
||||
theme="{{
|
||||
theme={{
|
||||
colour: { background: 'transparent' },
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
<SiteHeader />
|
||||
<ThemedPage />
|
||||
|
|
@ -102,22 +102,22 @@
|
|||
</Story>
|
||||
|
||||
<Story name="Inheritance" {...withStoryDocs(inheritanceDocs)}>
|
||||
<Theme theme="{themes.light}">
|
||||
<Theme theme={themes.light}>
|
||||
<div class="themed">
|
||||
<p>Theme</p>
|
||||
<Theme theme="{themes.dark}">
|
||||
<Theme theme={themes.dark}>
|
||||
<div class="themed">
|
||||
<p>Sub-theme</p>
|
||||
<Theme theme="{themes.light}">
|
||||
<Theme theme={themes.light}>
|
||||
<div class="themed">
|
||||
<p>Sub-sub</p>
|
||||
</div>
|
||||
</Theme>
|
||||
<Theme
|
||||
theme="{{
|
||||
theme={{
|
||||
colour: { background: 'steelblue', 'text-primary': '#fff' },
|
||||
font: { family: { note: 'FreightText, serif' } },
|
||||
}}"
|
||||
}}
|
||||
base="dark"
|
||||
>
|
||||
<div class="themed">
|
||||
|
|
|
|||
|
|
@ -11,23 +11,21 @@
|
|||
|
||||
<script lang="ts">
|
||||
import type { CustomTheme } from './@types/component';
|
||||
|
||||
|
||||
type Base = 'light' | 'dark';
|
||||
|
||||
|
||||
import flatten from './utils/flatten';
|
||||
import mergeThemes from './utils/merge';
|
||||
interface Props {
|
||||
/** Custom theme object. Can be a partial theme with just
|
||||
* what you want to change.
|
||||
*/
|
||||
* what you want to change.
|
||||
*/
|
||||
theme?: CustomTheme;
|
||||
/**
|
||||
* Base theme is one of `light` or `dark` and will be merged
|
||||
* with your custom theme to fill in any values you don't
|
||||
* explicitly set.
|
||||
*/
|
||||
* Base theme is one of `light` or `dark` and will be merged
|
||||
* with your custom theme to fill in any values you don't
|
||||
* explicitly set.
|
||||
*/
|
||||
base?: Base;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
|
@ -35,14 +33,18 @@
|
|||
let { theme = {}, base = 'light', children }: Props = $props();
|
||||
|
||||
/** @type {Theme} */
|
||||
let mergedTheme = $derived(mergeThemes({}, themes[base] || themes.light, theme));
|
||||
let mergedTheme = $derived(
|
||||
mergeThemes({}, themes[base] || themes.light, theme)
|
||||
);
|
||||
|
||||
let cssVariables = $derived(Object.entries(flatten({ theme: mergedTheme }))
|
||||
.map(([key, value]) => `--${key}: ${value};`)
|
||||
.join(' '));
|
||||
let cssVariables = $derived(
|
||||
Object.entries(flatten({ theme: mergedTheme }))
|
||||
.map(([key, value]) => `--${key}: ${value};`)
|
||||
.join(' ')
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="theme" style="{cssVariables}" style:display="contents">
|
||||
<div class="theme" style={cssVariables} style:display="contents">
|
||||
<!-- Clients can override the theme above by attaching custom properties to this element. -->
|
||||
<div class="theme-client-override" style="display: contents;">
|
||||
<!-- Themed content -->
|
||||
|
|
|
|||
|
|
@ -7,21 +7,21 @@
|
|||
|
||||
<Article>
|
||||
<Headline
|
||||
hed="{'Reuters Graphics Interactive'}"
|
||||
dek="{'The beginning of a beautiful page'}"
|
||||
section="{'Global news'}"
|
||||
authors="{['Jon McClure', 'Prasanta Kumar Dutta']}"
|
||||
publishTime="{new Date('2021-09-12').toISOString()}"
|
||||
hed={'Reuters Graphics Interactive'}
|
||||
dek={'The beginning of a beautiful page'}
|
||||
section={'Global news'}
|
||||
authors={['Jon McClure', 'Prasanta Kumar Dutta']}
|
||||
publishTime={new Date('2021-09-12').toISOString()}
|
||||
/>
|
||||
<BodyText
|
||||
text="{`Bacon ipsum dolor amet cupim porchetta chuck buffalo sirloin beef. Biltong ham brisket tenderloin hamburger doner.
|
||||
text={`Bacon ipsum dolor amet cupim porchetta chuck buffalo sirloin beef. Biltong ham brisket tenderloin hamburger doner.
|
||||
|
||||
Prosciutto kevin brisket sirloin pork loin shoulder cupim sausage chicken jowl strip steak rump pork ball tip ham hock. Swine pork belly fatback alcatra jowl.
|
||||
|
||||
## Brisket sirloin
|
||||
|
||||
Shank strip steak turkey shoulder shankle leberkas pork chop, t-bone picanha buffalo ground round burgdoggen ribeye.
|
||||
`}"
|
||||
`}
|
||||
/>
|
||||
<GraphicBlock
|
||||
title="Steak tar-tar"
|
||||
|
|
@ -31,7 +31,7 @@ Shank strip steak turkey shoulder shankle leberkas pork chop, t-bone picanha buf
|
|||
<div class="fake-graphic"></div>
|
||||
</GraphicBlock>
|
||||
<BodyText
|
||||
text="{'Bacon ipsum dolor amet cupim porchetta chuck buffalo sirloin beef. Biltong ham brisket tenderloin hamburger doner.'}"
|
||||
text={'Bacon ipsum dolor amet cupim porchetta chuck buffalo sirloin beef. Biltong ham brisket tenderloin hamburger doner.'}
|
||||
/>
|
||||
</Article>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<div>
|
||||
<ToolsHeader {...args} />
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story name="Default" args="{{}}" />
|
||||
<Story name="Default" args={{}} />
|
||||
|
||||
<style>
|
||||
div {
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@
|
|||
|
||||
<header
|
||||
{id}
|
||||
class="{cls}"
|
||||
class={cls}
|
||||
class:sticky
|
||||
style:background
|
||||
style:border-bottom="{borderBottom}"
|
||||
style:border-bottom={borderBottom}
|
||||
>
|
||||
<div class="logo-container">
|
||||
<a href="{homeLink}">
|
||||
<a href={homeLink}>
|
||||
<ReutersGraphicsLogo {...{ ...logoProps, ...{ width: '100%' } }} />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
</script>
|
||||
|
||||
<button
|
||||
onclick="{forwardBtnClick}"
|
||||
onclick={forwardBtnClick}
|
||||
style="
|
||||
opacity: {controlsOpacity};
|
||||
top: {controlsPosition === 'top left' || controlsPosition === 'top right' ?
|
||||
|
|
@ -50,18 +50,18 @@
|
|||
{#if resetCondition}
|
||||
<i class="play-pause-icon replay">
|
||||
{#if separateReplayIcon}
|
||||
<Fa icon="{faReply}" size="2x" color="{controlsColour}" />
|
||||
<Fa icon={faReply} size="2x" color={controlsColour} />
|
||||
{:else}
|
||||
<Fa icon="{faPlay}" size="2x" color="{controlsColour}" />
|
||||
<Fa icon={faPlay} size="2x" color={controlsColour} />
|
||||
{/if}
|
||||
</i>
|
||||
{:else if paused === false}
|
||||
<i class="play-pause-icon pause">
|
||||
<Fa icon="{faPause}" size="2x" color="{controlsColour}" />
|
||||
<Fa icon={faPause} size="2x" color={controlsColour} />
|
||||
</i>
|
||||
{:else if paused === true}
|
||||
<i class="play-pause-icon play">
|
||||
<Fa icon="{faPlay}" size="2x" color="{controlsColour}" />
|
||||
<Fa icon={faPlay} size="2x" color={controlsColour} />
|
||||
</i>
|
||||
{:else}
|
||||
error
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
import SoundVideo from './stories/videos/sound-video.mp4';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Video {...args} />
|
||||
{/snippet}
|
||||
|
|
@ -39,17 +39,17 @@
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'wide',
|
||||
notes: 'Optional caption for your video.',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Playing and looping"
|
||||
args="{{
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'normal',
|
||||
|
|
@ -57,13 +57,13 @@
|
|||
notes:
|
||||
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
|
||||
playVideoThreshold: 0.9,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(playAndLoopDocs)}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Controls"
|
||||
args="{{
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
src: SilentVideo,
|
||||
width: 'normal',
|
||||
|
|
@ -76,13 +76,13 @@
|
|||
separateReplayIcon: true,
|
||||
loopVideo: false,
|
||||
hoverToSeeControls: true,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(controlsDocs)}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Videos with sound"
|
||||
args="{{
|
||||
args={{
|
||||
ariaDescription: 'Compulsory description of your video for screen readers.',
|
||||
src: SoundVideo,
|
||||
width: 'normal',
|
||||
|
|
@ -94,6 +94,6 @@
|
|||
muteVideo: false,
|
||||
playVideoWhenInView: true,
|
||||
allowSoundToAutoplay: true,
|
||||
}}"
|
||||
}}
|
||||
{...withStoryDocs(controlsDocs)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@
|
|||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:click="{setInteractedWithDom}"
|
||||
on:touchstart="{setInteractedWithDom}"
|
||||
on:click={setInteractedWithDom}
|
||||
on:touchstart={setInteractedWithDom}
|
||||
/>
|
||||
|
||||
<GraphicBlock
|
||||
|
|
@ -179,18 +179,18 @@
|
|||
>
|
||||
<div
|
||||
role="figure"
|
||||
on:mouseover="{() => {
|
||||
on:mouseover={() => {
|
||||
interactiveControlsOpacity = controlsOpacity;
|
||||
}}"
|
||||
on:focus="{() => {
|
||||
}}
|
||||
on:focus={() => {
|
||||
interactiveControlsOpacity = controlsOpacity;
|
||||
}}"
|
||||
on:mouseout="{() => {
|
||||
}}
|
||||
on:mouseout={() => {
|
||||
interactiveControlsOpacity = 0;
|
||||
}}"
|
||||
on:blur="{() => {
|
||||
}}
|
||||
on:blur={() => {
|
||||
interactiveControlsOpacity = 0;
|
||||
}}"
|
||||
}}
|
||||
>
|
||||
{#if (hidden && ariaDescription) || !hidden}
|
||||
{#if ariaDescription}
|
||||
|
|
@ -202,25 +202,25 @@
|
|||
<IntersectionObserver
|
||||
{element}
|
||||
bind:intersecting
|
||||
threshold="{playVideoThreshold}"
|
||||
once="{false}"
|
||||
threshold={playVideoThreshold}
|
||||
once={false}
|
||||
>
|
||||
<div
|
||||
bind:this="{element}"
|
||||
bind:this={element}
|
||||
class="video-wrapper relative block"
|
||||
aria-hidden="{hidden}"
|
||||
bind:clientWidth="{widthVideoContainer}"
|
||||
bind:clientHeight="{heightVideoContainer}"
|
||||
aria-hidden={hidden}
|
||||
bind:clientWidth={widthVideoContainer}
|
||||
bind:clientHeight={heightVideoContainer}
|
||||
>
|
||||
{#if possibleToPlayPause}
|
||||
{#if showControls}
|
||||
<Controls
|
||||
on:pausePlayEvent="{pausePlayEvent}"
|
||||
on:pausePlayEvent={pausePlayEvent}
|
||||
{paused}
|
||||
{clickedOnPauseBtn}
|
||||
controlsOpacity="{hoverToSeeControls ?
|
||||
controlsOpacity={hoverToSeeControls ?
|
||||
interactiveControlsOpacity
|
||||
: controlsOpacity}"
|
||||
: controlsOpacity}
|
||||
{controlsPosition}
|
||||
{widthVideoContainer}
|
||||
{heightVideoContainer}
|
||||
|
|
@ -232,32 +232,32 @@
|
|||
{:else}
|
||||
<button
|
||||
class="border-0 m-0 p-0 bg-transparent absolute"
|
||||
on:click="{() => {
|
||||
on:click={() => {
|
||||
if (paused === true) {
|
||||
paused = false;
|
||||
} else {
|
||||
paused = true;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;"
|
||||
></button>
|
||||
{/if}
|
||||
{/if}
|
||||
<video
|
||||
bind:this="{videoElement}"
|
||||
bind:this={videoElement}
|
||||
{src}
|
||||
{poster}
|
||||
class="pointer-events-none relative"
|
||||
width="100%"
|
||||
muted="{muteVideo}"
|
||||
muted={muteVideo}
|
||||
playsinline
|
||||
preload="{preloadVideo}"
|
||||
loop="{loopVideo}"
|
||||
bind:currentTime="{time}"
|
||||
preload={preloadVideo}
|
||||
loop={loopVideo}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
bind:clientWidth="{widthVideo}"
|
||||
bind:clientHeight="{heightVideo}"
|
||||
bind:clientWidth={widthVideo}
|
||||
bind:clientHeight={heightVideo}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
|
|
@ -267,14 +267,14 @@
|
|||
<!-- Video element without Intersection observer -->
|
||||
<div
|
||||
class="video-wrapper relative"
|
||||
aria-hidden="{hidden}"
|
||||
bind:clientWidth="{widthVideoContainer}"
|
||||
bind:clientHeight="{heightVideoContainer}"
|
||||
aria-hidden={hidden}
|
||||
bind:clientWidth={widthVideoContainer}
|
||||
bind:clientHeight={heightVideoContainer}
|
||||
>
|
||||
{#if possibleToPlayPause}
|
||||
{#if showControls}
|
||||
<Controls
|
||||
on:pausePlayEvent="{pausePlayEvent}"
|
||||
on:pausePlayEvent={pausePlayEvent}
|
||||
{paused}
|
||||
{clickedOnPauseBtn}
|
||||
{controlsOpacity}
|
||||
|
|
@ -289,33 +289,33 @@
|
|||
{:else}
|
||||
<button
|
||||
class="border-0 m-0 p-0 bg-transparent absolute"
|
||||
on:click="{() => {
|
||||
on:click={() => {
|
||||
if (paused === true) {
|
||||
paused = false;
|
||||
} else {
|
||||
paused = true;
|
||||
}
|
||||
}}"
|
||||
}}
|
||||
style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;"
|
||||
></button>
|
||||
{/if}
|
||||
{/if}
|
||||
<video
|
||||
bind:this="{videoElement}"
|
||||
bind:this={videoElement}
|
||||
{src}
|
||||
{poster}
|
||||
class="pointer-events-none relative"
|
||||
width="100%"
|
||||
muted="{muteVideo}"
|
||||
muted={muteVideo}
|
||||
playsinline
|
||||
preload="{preloadVideo}"
|
||||
loop="{loopVideo}"
|
||||
bind:currentTime="{time}"
|
||||
preload={preloadVideo}
|
||||
loop={loopVideo}
|
||||
bind:currentTime={time}
|
||||
bind:duration
|
||||
bind:paused
|
||||
autoplay
|
||||
bind:clientWidth="{widthVideo}"
|
||||
bind:clientHeight="{heightVideo}"
|
||||
bind:clientWidth={widthVideo}
|
||||
bind:clientHeight={heightVideo}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@
|
|||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
<Template>
|
||||
{#snippet children({ args })}
|
||||
<Visible {...args} >
|
||||
<Visible {...args}>
|
||||
{#snippet children({ visible })}
|
||||
{#if visible}
|
||||
<p>Visible!</p>
|
||||
{:else}
|
||||
<p>Not yet visible.</p>
|
||||
{/if}
|
||||
{/snippet}
|
||||
{/snippet}
|
||||
</Visible>
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
<!-- @component `Visible` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-utilities-visible--docs) -->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Whether to change visibility just once.
|
||||
*
|
||||
* Useful for loading expensive images or other media and then keeping them around once they're first loaded.
|
||||
*/
|
||||
* Whether to change visibility just once.
|
||||
*
|
||||
* Useful for loading expensive images or other media and then keeping them around once they're first loaded.
|
||||
*/
|
||||
once?: boolean;
|
||||
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top`. */
|
||||
top?: number;
|
||||
|
|
@ -34,7 +29,7 @@
|
|||
left = 0,
|
||||
right = 0,
|
||||
threshold = 0,
|
||||
children
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
let visible = $state(false);
|
||||
|
|
@ -74,7 +69,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div bind:this="{container}">
|
||||
<div bind:this={container}>
|
||||
<!-- An element or component -->
|
||||
{@render children?.({ visible, })}
|
||||
{@render children?.({ visible })}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { Visible } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Visible >
|
||||
<Visible>
|
||||
{#snippet children({ visible })}
|
||||
{#if visible}
|
||||
<p>Visible!</p>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Once you've setup the `Meta` and `Template` components as below, you can write a
|
|||
import YourComponent from './YourComponent.svelte';
|
||||
</script>
|
||||
|
||||
<Meta title="Components/YourComponent" component="{YourComponent}" />
|
||||
<Meta title="Components/YourComponent" component={YourComponent} />
|
||||
|
||||
<Template let:args>
|
||||
<YourComponent {...args} />
|
||||
|
|
@ -28,9 +28,9 @@ Once you've setup the `Meta` and `Template` components as below, you can write a
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
|
|
@ -47,16 +47,16 @@ You can define additional stories with _different_ args to show how your compone
|
|||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Extra wide"
|
||||
args="{{
|
||||
args={{
|
||||
width: 'wider',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
|
|
@ -66,10 +66,10 @@ If you want even more control, you can skip the `args` and pass your component d
|
|||
<Story name="Super custom">
|
||||
<YourComponent
|
||||
width="fluid"
|
||||
data="{[
|
||||
data={[
|
||||
{ id: 'UK', value: 65 },
|
||||
{ id: 'USA', value: 265 },
|
||||
]}"
|
||||
]}
|
||||
/>
|
||||
</Story>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ You can customise the controls in Storybook's built-in [controls panel](https://
|
|||
};
|
||||
</script>
|
||||
|
||||
<Meta title="Components/YourComponent" component="{YourComponent}" {...meta} />
|
||||
<Meta title="Components/YourComponent" component={YourComponent} {...meta} />
|
||||
|
||||
<template let:args>
|
||||
<YourComponent {...args} />
|
||||
|
|
|
|||
|
|
@ -47,19 +47,19 @@ Now, import your markdown file in your story page component and attach it to eit
|
|||
};
|
||||
</script>
|
||||
|
||||
<Meta title="Components/YourComponent" component="{YourComponent}" {...meta} />
|
||||
<Meta title="Components/YourComponent" component={YourComponent} {...meta} />
|
||||
|
||||
<template let:args>
|
||||
<YourComponent {...args} />
|
||||
</template>
|
||||
|
||||
<!-- The first story will use the component docs in Meta... -->
|
||||
<Story name="Basic" args="{{ width: 'normal' }}" />
|
||||
<Story name="Basic" args={{ width: 'normal' }} />
|
||||
|
||||
<!-- Additional stories can use other docs now. -->
|
||||
<Story
|
||||
name="Another story"
|
||||
args="{{ width: 'normal' }}"
|
||||
args={{ width: 'normal' }}
|
||||
{...withStoryDocs(someStoryDocs)}
|
||||
/>
|
||||
```
|
||||
|
|
@ -110,7 +110,7 @@ Now, import your snippet file in your story page component and attach it to any
|
|||
<!-- Pass source in inside an object keyed by your snippet's language, e.g., svelte, scss, etc. -->
|
||||
<Story
|
||||
name="Basic"
|
||||
args="{{ width: 'normal' }}"
|
||||
args={{ width: 'normal' }}
|
||||
{...withSource({ svelte: defaultSnippet })}
|
||||
/>
|
||||
```
|
||||
|
|
@ -124,7 +124,7 @@ If you're adding source code AND custom docs to a story, you can chain `withSour
|
|||
|
||||
<Story
|
||||
name="Extra wide"
|
||||
args="{{ width: 'wider' }}"
|
||||
args={{ width: 'wider' }}
|
||||
{...withComponentDocs(componentDocs, withSource({ svelte: defaultSnippet }))}
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Now, import your media file directly in your code, which will resolve to the URL
|
|||
import myImageSrc from './stories/imgs/myImage.jpg';
|
||||
</script>
|
||||
|
||||
<Meta title="Components/YourComponent" component="{YourComponent}" />
|
||||
<Meta title="Components/YourComponent" component={YourComponent} />
|
||||
|
||||
<template let:args>
|
||||
<YourComponent {...args} />
|
||||
|
|
@ -42,9 +42,9 @@ Now, import your media file directly in your code, which will resolve to the URL
|
|||
|
||||
<Story
|
||||
name="Basic"
|
||||
args="{{
|
||||
args={{
|
||||
src: myImageSrc,
|
||||
altText: 'My image in the component',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ Let's look at a basic component, a `ProfileCard`, with a demo that looks like th
|
|||
|
||||
<ProfileCard
|
||||
name="Kitty"
|
||||
age="{2}"
|
||||
age={2}
|
||||
img="https://cats.com/cat1.jpg"
|
||||
birthday="{new Date('2020-09-25')}"
|
||||
birthday={new Date('2020-09-25')}
|
||||
bio="Some notes.\n\nWith multiple paragraphs."
|
||||
isGood="{true}"
|
||||
isGood={true}
|
||||
/>
|
||||
```
|
||||
|
||||
|
|
@ -100,12 +100,12 @@ Notice all the values in the data are **strings**. More on that soon.
|
|||
<!-- ... -->
|
||||
{:else if block.type === 'profile-card'}
|
||||
<ProfileCard
|
||||
name="{block.name}"
|
||||
age="{parseInt(block.age)}"
|
||||
img="{`${assets}/${block.picture}`}"
|
||||
birthday="{new Date(block.birthday)}"
|
||||
bio="{block.bio}"
|
||||
isGood="{block.isGood === 'true'}"
|
||||
name={block.name}
|
||||
age={parseInt(block.age)}
|
||||
img={`${assets}/${block.picture}`}
|
||||
birthday={new Date(block.birthday)}
|
||||
bio={block.bio}
|
||||
isGood={block.isGood === 'true'}
|
||||
/>
|
||||
<!-- ... -->
|
||||
{/if}
|
||||
|
|
@ -136,12 +136,12 @@ Once we've identified we have the right block for our component, we need to conv
|
|||
|
||||
```svelte
|
||||
<ProfileCard
|
||||
name="{block.name}"
|
||||
age="{parseInt(block.age)}"
|
||||
img="{`${assets}/${block.picture}`}"
|
||||
birthday="{new Date(block.birthday)}"
|
||||
bio="{block.bio}"
|
||||
isGood="{block.isGood === 'true'}"
|
||||
name={block.name}
|
||||
age={parseInt(block.age)}
|
||||
img={`${assets}/${block.picture}`}
|
||||
birthday={new Date(block.birthday)}
|
||||
bio={block.bio}
|
||||
isGood={block.isGood === 'true'}
|
||||
/>
|
||||
```
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ Let's look at another example component:
|
|||
```svelte
|
||||
<Timeline
|
||||
title="A brief history of BitCoin"
|
||||
dates="{[
|
||||
dates={[
|
||||
{
|
||||
date: new Date('1992-01-01'),
|
||||
subhed:
|
||||
|
|
@ -208,7 +208,7 @@ Let's look at another example component:
|
|||
subhed: 'The Winklevoss twins buy in',
|
||||
img: `${assets}/images/winkle-boys.jpeg`,
|
||||
},
|
||||
]}"
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -30,5 +30,5 @@ In the Graphics Kit, that means you'll need to prefix relative paths with the sp
|
|||
</script>
|
||||
|
||||
<!-- Use the assets module to prefix the path to your image. -->
|
||||
<FeautrePhoto src="{`${assets}/imgs/myImage.jpg`}" />
|
||||
<FeautrePhoto src={`${assets}/imgs/myImage.jpg`} />
|
||||
```
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ A component is usually composed of several parts: JavaScript for managing data,
|
|||
|
||||
<!-- HTML -->
|
||||
<figure>
|
||||
<img src="{imgSrc}" alt="{altText}" />
|
||||
<img src={imgSrc} alt={altText} />
|
||||
<figcaption>{caption}</figcaption>
|
||||
</figure>
|
||||
|
||||
|
|
|
|||
|
|
@ -32,5 +32,5 @@ pnpm i @reuters-graphics/graphics-components
|
|||
import { BodyText } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<BodyText text="{'Hello world!'}" />
|
||||
<BodyText text={'Hello world!'} />
|
||||
```
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue