set up photo pack

This commit is contained in:
MinamiFunakoshiTR 2025-03-24 09:57:10 -07:00
parent 894ff8442d
commit e9dff42de1
Failed to extract signature
101 changed files with 848 additions and 892 deletions

View file

@ -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!!",
}}"
}}
/>

View file

@ -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>

View file

@ -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>

View file

@ -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>
```

View file

@ -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)) {

View file

@ -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} />

View file

@ -46,7 +46,7 @@
});
</script>
<svelte:window bind:innerWidth="{windowWidth}" />
<svelte:window bind:innerWidth={windowWidth} />
<div
class="freestar-adslot leaderboard__sticky {cls}"

View file

@ -69,7 +69,7 @@
let adType = $derived(getAdType(placementName));
</script>
<svelte:window bind:innerWidth="{windowWidth}" />
<svelte:window bind:innerWidth={windowWidth} />
{#if windowWidth}
{#key placementName}

View file

@ -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" />

View file

@ -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;
}

View file

@ -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 />

View file

@ -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',
}}"
}}
/>

View file

@ -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',
}}"
}}
/>

View file

@ -35,7 +35,7 @@
<iframe
class="h-screen"
src="https://embed.documentcloud.org/documents/{slug}/?embed=1&amp;responsive=1&amp;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"

View file

@ -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)} -->

View file

@ -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>

View file

@ -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',
],
}}"
}}
/>

View file

@ -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>

View file

@ -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>

View file

@ -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}

View file

@ -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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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. */

View file

@ -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"

View file

@ -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 Russias 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 Russias 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>

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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}

View file

@ -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"

View file

@ -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"

View file

@ -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>

View file

@ -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>

View 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.

View file

@ -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)}
}}
/>

View file

@ -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}

View file

@ -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}
```

View file

@ -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.

View file

@ -1 +0,0 @@
If any of your images is missing `altText` a small warning will overlay the photo.

View 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;
};

View file

@ -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>

View file

@ -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)}
/>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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}

View file

@ -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)}
/>

View file

@ -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"
>

View file

@ -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="">

View file

@ -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="">

View file

@ -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="">

View file

@ -11,7 +11,7 @@
<p>The count is {count}</p>
<button
onclick="{() => {
onclick={() => {
count += 1;
}}">Click Me</button
}}>Click Me</button
>

View file

@ -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 {

View file

@ -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={{}} />

View file

@ -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>

View file

@ -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 />

View file

@ -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>

View file

@ -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>

View file

@ -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)}
/>

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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}

View file

@ -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>

View file

@ -27,7 +27,7 @@
import Theme from '../Theme/Theme.svelte';
</script>
<Template >
<Template>
{#snippet children({ args })}
<div>
<SiteHeader {...args} />

View file

@ -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">

View file

@ -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>

View file

@ -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}

View file

@ -26,7 +26,7 @@
import { Template, Story } from '@storybook/addon-svelte-csf';
</script>
<Template >
<Template>
{#snippet children({ args })}
<Spinner {...args} />
{/snippet}

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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',
}}"
}}
/>

View file

@ -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>

View file

@ -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!!",
}}"
}}
/>

View file

@ -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>

View file

@ -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">

View file

@ -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 -->

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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)}
/>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -2,7 +2,7 @@
import { Visible } from '@reuters-graphics/graphics-components';
</script>
<Visible >
<Visible>
{#snippet children({ visible })}
{#if visible}
<p>Visible!</p>

View file

@ -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>
```

View file

@ -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} />

View file

@ -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 }))}
/>
```

View file

@ -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',
}}"
}}
/>
```

View file

@ -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`,
},
]}"
]}
/>
```

View file

@ -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`} />
```

View file

@ -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>

View file

@ -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