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'; import SharkImg from './stories/shark.jpg';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<YourComponent {...args} /> <YourComponent {...args} />
{/snippet} {/snippet}
@ -36,9 +36,9 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
src: SharkImg, src: SharkImg,
altText: "Duh dum! It's a shark!!", altText: "Duh dum! It's a shark!!",
}}" }}
/> />

View file

@ -36,8 +36,8 @@
<Block {width} {id} class="photo {cls}"> <Block {width} {id} class="photo {cls}">
<div <div
style:background-image="{`url(${src})`}" style:background-image={`url(${src})`}
style:height="{`${height}px`}" style:height={`${height}px`}
></div> ></div>
<p class="visually-hidden">{altText}</p> <p class="visually-hidden">{altText}</p>
</Block> </Block>

View file

@ -22,7 +22,7 @@ An action you can use to easily set [CSS variables](https://developer.mozilla.or
</script> </script>
<!-- Attach it to a parent element with the action --> <!-- Attach it to a parent element with the action -->
<div use:cssVariables="{variables}"> <div use:cssVariables={variables}>
<p>My text...</p> <p>My text...</p>
</div> </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; let elementWidth = 0;
</script> </script>
<div use:resizeObserver="{(element) => (elementWidth = element.clientWidth)}"> <div use:resizeObserver={(element) => (elementWidth = element.clientWidth)}>
My width is: {elementWidth} My width is: {elementWidth}
</div> </div>
``` ```

View file

@ -55,7 +55,7 @@
}); });
</script> </script>
<div data-freestar-ad="{dataFreestarAd || null}" id="{adId}"></div> <div data-freestar-ad={dataFreestarAd || null} id={adId}></div>
<style> <style>
:global(div.freestar-adslot:has(.unfulfilled-ad)) { :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 ```svelte
<!-- First inline ad on the page --> <!-- First inline ad on the page -->
<InlineAd n="{1}" /> <InlineAd n={1} />
<!-- ... second ... --> <!-- ... second ... -->
<InlineAd n="{2}" /> <InlineAd n={2} />
<!-- ... third and final. --> <!-- ... third and final. -->
<InlineAd n="{3}" /> <InlineAd n={3} />
``` ```
<Canvas of={InlineAdStories.Demo} /> <Canvas of={InlineAdStories.Demo} />

View file

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

View file

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

View file

@ -41,13 +41,13 @@ You can set custom column widths by passing an object to the `columnWidths` prop
```svelte ```svelte
<!-- Set custom column widths --> <!-- Set custom column widths -->
<Article <Article
columnWidths="{{ columnWidths={{
narrower: 310, narrower: 310,
narrow: 450, narrow: 450,
normal: 550, normal: 550,
wide: 675, wide: 675,
wider: 1400, wider: 1400,
}}" }}
> >
<!-- Custom column widths get passed down to the `Block` component --> <!-- Custom column widths get passed down to the `Block` component -->
<Block width="narrower" /> <Block width="narrower" />

View file

@ -36,7 +36,7 @@
import Article from '../Article/Article.svelte'; import Article from '../Article/Article.svelte';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Article id="block-demo-article"> <Article id="block-demo-article">
<div class="article-boundaries"> <div class="article-boundaries">
@ -51,9 +51,9 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
}}" }}
/> />
<Story name="Custom layouts" {...withStoryDocs(customLayoutsDocs)}> <Story name="Custom layouts" {...withStoryDocs(customLayoutsDocs)}>
@ -77,42 +77,41 @@
<Article id="block-demo-article"> <Article id="block-demo-article">
<div class="article-boundaries"> <div class="article-boundaries">
<div class="label">Article</div> <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 >narrower</Block
> >
<Block width="narrow" snap="{true}" class="block-snap-widths-demo" <Block width="narrow" snap={true} class="block-snap-widths-demo"
>narrow</Block >narrow</Block
> >
<Block width="normal" snap="{true}" class="block-snap-widths-demo" <Block width="normal" snap={true} class="block-snap-widths-demo"
>normal</Block >normal</Block
> >
<Block width="wide" snap="{true}" class="block-snap-widths-demo" <Block width="wide" snap={true} class="block-snap-widths-demo">wide</Block
>wide</Block
> >
<Block width="wider" snap="{true}" class="block-snap-widths-demo" <Block width="wider" snap={true} class="block-snap-widths-demo"
>wider</Block >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 >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 >narrow</Block
> >
<Block <Block
width="normal" width="normal"
snap="{true}" snap={true}
class="block-snap-widths-demo even skip-narrow" class="block-snap-widths-demo even skip-narrow"
>normal.skip-narrow</Block >normal.skip-narrow</Block
> >
<Block <Block
width="wide" width="wide"
snap="{true}" snap={true}
class="block-snap-widths-demo even skip-normal skip-narrow" class="block-snap-widths-demo even skip-normal skip-narrow"
>wide.skip-normal.skip-narrow</Block >wide.skip-normal.skip-narrow</Block
> >
<Block <Block
width="wider" width="wider"
snap="{true}" snap={true}
class="block-snap-widths-demo even skip-wide">wider.skip-wide</Block class="block-snap-widths-demo even skip-wide">wider.skip-wide</Block
> >
</div> </div>
@ -145,9 +144,9 @@
background: rgb(211, 132, 123); background: rgb(211, 132, 123);
} }
:global( :global(
#block-demo-article .label, #block-demo-article .label,
#block-demo-article div.article-block.block-snap-widths-demo #block-demo-article div.article-block.block-snap-widths-demo
) { ) {
padding-left: 3px; padding-left: 3px;
color: white; color: white;
} }

View file

@ -26,9 +26,9 @@
<div <div
{id} {id}
class="article-block fmx-auto {width} {cls}" class="article-block fmx-auto {width} {cls}"
class:snap="{snap && width !== 'fluid' && width !== 'widest'}" class:snap={snap && width !== 'fluid' && width !== 'widest'}
{role} {role}
aria-label="{ariaLabel}" aria-label={ariaLabel}
> >
<!-- block content --> <!-- block content -->
<slot /> <slot />

View file

@ -17,18 +17,18 @@
<Story <Story
name="Demo" name="Demo"
args="{{ args={{
src: 'https://reuters.com/graphics/USA-ABORTION/lgpdwggnwvo/media-embed.html', src: 'https://reuters.com/graphics/USA-ABORTION/lgpdwggnwvo/media-embed.html',
id: 'abortion-rights-map', id: 'abortion-rights-map',
ariaLabel: 'map', ariaLabel: 'map',
frameTitle: 'Global abortion access', frameTitle: 'Global abortion access',
}}" }}
/> />
<Story <Story
name="With chatter" name="With chatter"
tags="{['!autodocs']}" tags={['!autodocs']}
args="{{ args={{
frameTitle: 'Global abortion access', frameTitle: 'Global abortion access',
ariaLabel: 'map', ariaLabel: 'map',
id: 'abortion-rights-map', id: 'abortion-rights-map',
@ -37,5 +37,5 @@
description: 'A map of worldwide access to abortion.', description: 'A map of worldwide access to abortion.',
notes: 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', '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'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<DocumentCloud {...args} /> <DocumentCloud {...args} />
{/snippet} {/snippet}
@ -30,9 +30,9 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
slug: '3259984-Trump-Intelligence-Allegations', slug: '3259984-Trump-Intelligence-Allegations',
altText: 'These Reports Allege Trump Has Deep Ties To Russia', altText: 'These Reports Allege Trump Has Deep Ties To Russia',
}}" }}
/> />

View file

@ -35,7 +35,7 @@
<iframe <iframe
class="h-screen" class="h-screen"
src="https://embed.documentcloud.org/documents/{slug}/?embed=1&amp;responsive=1&amp;title=1" src="https://embed.documentcloud.org/documents/{slug}/?embed=1&amp;responsive=1&amp;title=1"
title="{altText}" title={altText}
width="700" width="700"
height="540" height="540"
sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox" 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"> <script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf'; import { defineMeta } from '@storybook/addon-svelte-csf';
// @ts-ignore raw // @ts-ignore raw
import componentDocs from './stories/docs/component.md?raw'; import componentDocs from './stories/docs/component.md?raw';
@ -15,7 +15,6 @@
// withStoryDocs, // withStoryDocs,
// } from '$lib/docs/utils/withParams.js'; // } from '$lib/docs/utils/withParams.js';
const { Story } = defineMeta({ const { Story } = defineMeta({
title: 'Components/Multimedia/FeaturePhoto', title: 'Components/Multimedia/FeaturePhoto',
component: FeaturePhoto, component: FeaturePhoto,
@ -39,34 +38,33 @@
import sharkSrc from './stories/shark.jpg'; import sharkSrc from './stories/shark.jpg';
</script> </script>
<Story <Story
name="Default" name="Default"
args="{{ args={{
src: sharkSrc, src: sharkSrc,
altText: 'A shark!', altText: 'A shark!',
width: 'normal', width: 'normal',
caption: 'Carcharodon carcharias - REUTERS', caption: 'Carcharodon carcharias - REUTERS',
}}" }}
/> />
<Story <Story
name="ArchieML" name="ArchieML"
args="{{ args={{
src: sharkSrc, src: sharkSrc,
altText: 'A shark!', altText: 'A shark!',
width: 'normal', width: 'normal',
caption: 'Carcharodon carcharias - REUTERS', caption: 'Carcharodon carcharias - REUTERS',
}}" }}
/> />
<!-- {...withStoryDocs(archieMLDocs)} --> <!-- {...withStoryDocs(archieMLDocs)} -->
<Story <Story
name="Missing altText" name="Missing altText"
args="{{ args={{
src: sharkSrc, src: sharkSrc,
width: 'normal', width: 'normal',
caption: 'Carcharodon carcharias - REUTERS', caption: 'Carcharodon carcharias - REUTERS',
}}" }}
/> />
<!-- {...withStoryDocs(missingAltTextDocs)} --> <!-- {...withStoryDocs(missingAltTextDocs)} -->

View file

@ -93,18 +93,18 @@
<Block {width} class="photo fmy-6 {cls}" {id}> <Block {width} class="photo fmy-6 {cls}" {id}>
<figure <figure
bind:this="{container}" bind:this={container}
aria-label="media" aria-label="media"
class="w-full flex flex-col relative" class="w-full flex flex-col relative"
> >
{#if !lazy || (intersectable && intersecting)} {#if !lazy || (intersectable && intersecting)}
<img class="w-full my-0" {src} alt="{altText}" /> <img class="w-full my-0" {src} alt={altText} />
{:else} {:else}
<div class="placeholder w-full" style="{`height: ${height}px;`}"></div> <div class="placeholder w-full" style={`height: ${height}px;`}></div>
{/if} {/if}
{#if caption} {#if caption}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<Block width="{textWidth}" class="notes w-full fmy-0"> <Block width={textWidth} class="notes w-full fmy-0">
<figcaption> <figcaption>
{caption} {caption}
</figcaption> </figcaption>

View file

@ -17,7 +17,7 @@
import { Template, Story } from '@storybook/addon-svelte-csf'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Framer {...args} /> <Framer {...args} />
{/snippet} {/snippet}
@ -25,9 +25,9 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
embeds: [ embeds: [
'https://graphics.reuters.com/USA-CONGRESS/FUNDRAISING/zjvqkawjlvx/embeds/en/embed/?zzz', 'https://graphics.reuters.com/USA-CONGRESS/FUNDRAISING/zjvqkawjlvx/embeds/en/embed/?zzz',
], ],
}}" }}
/> />

View file

@ -62,25 +62,20 @@
<div id="typeahead-container"> <div id="typeahead-container">
<div class="embed-link"> <div class="embed-link">
<a <a rel="external" target="_blank" href={activeEmbed} title={activeEmbed}>
rel="external" Live link <Fa icon={faLink} />
target="_blank"
href="{activeEmbed}"
title="{activeEmbed}"
>
Live link <Fa icon="{faLink}" />
</a> </a>
</div> </div>
<Typeahead <Typeahead
label="Select an embed" label="Select an embed"
value="{embedTitles[embeds.indexOf(activeEmbed)] || value={embedTitles[embeds.indexOf(activeEmbed)] ||
embedTitles[activeEmbedIndex] || embedTitles[activeEmbedIndex] ||
embedTitles[0]}" embedTitles[0]}
extract="{(d) => embedTitles[d.index]}" extract={(d) => embedTitles[d.index]}
data="{embeds.map((embed, index) => ({ index, embed }))}" data={embeds.map((embed, index) => ({ index, embed }))}
placeholder="{'Search'}" placeholder={'Search'}
showDropdownOnFocus="{true}" showDropdownOnFocus={true}
on:select="{({ detail }) => { on:select={({ detail }) => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.localStorage.setItem( window.localStorage.setItem(
'framer-active-embed', 'framer-active-embed',
@ -89,7 +84,7 @@
} }
activeEmbed = detail.original.embed; activeEmbed = detail.original.embed;
activeEmbedIndex = detail.original.index; activeEmbedIndex = detail.original.index;
}}" }}
/> />
</div> </div>
@ -101,7 +96,7 @@
<div id="home-link"> <div id="home-link">
<a rel="external" href="./../"> <a rel="external" href="./../">
<Fa icon="{faDesktop}" /> <Fa icon={faDesktop} />
</a> </a>
</div> </div>

View file

@ -95,48 +95,48 @@
</script> </script>
<svelte:window <svelte:window
onmousemove="{move}" onmousemove={move}
onmouseup="{end}" onmouseup={end}
onkeydown="{handleKeyDown}" onkeydown={handleKeyDown}
bind:innerWidth="{windowInnerWidth}" bind:innerWidth={windowInnerWidth}
/> />
<div id="resizer"> <div id="resizer">
<div class="slider"> <div class="slider">
<div class="label" style="{`opacity: ${sliding || isFocused ? 1 : 0};`}"> <div class="label" style={`opacity: ${sliding || isFocused ? 1 : 0};`}>
{pixelLabel || $width}px {pixelLabel || $width}px
</div> </div>
<button <button
class="icon left" class="icon left"
disabled="{$width === minWidth}" disabled={$width === minWidth}
onclick="{decrement}" onclick={decrement}
onfocus="{onFocus}" onfocus={onFocus}
onmouseover="{onFocus}" onmouseover={onFocus}
onmouseleave="{onBlur}" onmouseleave={onBlur}
> >
<Fa icon="{faMobileAlt}" fw /> <Fa icon={faMobileAlt} fw />
</button> </button>
<div class="slider-container" bind:this="{container}"> <div class="slider-container" bind:this={container}>
<div class="track"></div> <div class="track"></div>
<div <div
class="handle" class="handle"
tabindex="0" tabindex="0"
role="button" role="button"
style="left: calc({offset * 100}% - 5px);" style="left: calc({offset * 100}% - 5px);"
onmousedown="{start}" onmousedown={start}
onfocus="{onFocus}" onfocus={onFocus}
onblur="{onBlur}" onblur={onBlur}
></div> ></div>
</div> </div>
<button <button
class="icon right" class="icon right"
disabled="{$width === maxWidth}" disabled={$width === maxWidth}
onclick="{increment}" onclick={increment}
onfocus="{onFocus}" onfocus={onFocus}
onmouseover="{onFocus}" onmouseover={onFocus}
onmouseleave="{onBlur}" onmouseleave={onBlur}
> >
<Fa icon="{faDesktop}" fw /> <Fa icon={faDesktop} fw />
</button> </button>
</div> </div>
</div> </div>

View file

@ -158,55 +158,55 @@
</script> </script>
<svelte:window <svelte:window
on:click="{({ target }) => { on:click={({ target }) => {
if (!hideDropdown && !comboboxRef?.contains(target)) { if (!hideDropdown && !comboboxRef?.contains(target)) {
close(); close();
} }
}}" }}
/> />
<div <div
data-svelte-typeahead data-svelte-typeahead
bind:this="{comboboxRef}" bind:this={comboboxRef}
role="combobox" role="combobox"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="{id}-listbox" aria-owns="{id}-listbox"
class:dropdown="{results.length > 0}" class:dropdown={results.length > 0}
aria-controls="{id}-listbox" aria-controls="{id}-listbox"
aria-expanded="{showResults || aria-expanded={showResults ||
(isFocused && value.length > 0 && results.length === 0)}" (isFocused && value.length > 0 && results.length === 0)}
id="{id}-typeahead" id="{id}-typeahead"
> >
<Search <Search
{id} {id}
removeFormAriaAttributes="{true}" removeFormAriaAttributes={true}
{...$$restProps} {...$$restProps}
bind:ref="{searchRef}" bind:ref={searchRef}
aria-autocomplete="list" aria-autocomplete="list"
aria-controls="{id}-listbox" aria-controls="{id}-listbox"
aria-labelledby="{id}-label" aria-labelledby="{id}-label"
aria-activedescendant="{( aria-activedescendant={(
selectedIndex >= 0 && !hideDropdown && results.length > 0 selectedIndex >= 0 && !hideDropdown && results.length > 0
) ? ) ?
`${id}-result-${selectedIndex}` `${id}-result-${selectedIndex}`
: null}" : null}
bind:value bind:value
on:type on:type
on:input on:input
on:change on:change
on:focus on:focus
on:focus="{() => { on:focus={() => {
open(); open();
if (showDropdownOnFocus) { if (showDropdownOnFocus) {
showResults = true; showResults = true;
isFocused = true; isFocused = true;
} }
}}" }}
on:clear on:clear
on:clear="{open}" on:clear={open}
on:blur on:blur
on:keydown on:keydown
on:keydown="{(e) => { on:keydown={(e) => {
if (results.length === 0) return; if (results.length === 0) return;
switch (e.key) { switch (e.key) {
@ -228,10 +228,10 @@
close(); close();
break; break;
} }
}}" }}
/> />
<ul <ul
class:svelte-typeahead-list="{true}" class:svelte-typeahead-list={true}
role="listbox" role="listbox"
aria-labelledby="{id}-label" aria-labelledby="{id}-label"
id="{id}-listbox" id="{id}-listbox"
@ -241,24 +241,24 @@
<li <li
role="option" role="option"
id="{id}-result-{index}" id="{id}-result-{index}"
class:selected="{selectedIndex === index}" class:selected={selectedIndex === index}
class:disabled="{result.disabled}" class:disabled={result.disabled}
aria-selected="{selectedIndex === index}" aria-selected={selectedIndex === index}
on:click="{() => { on:click={() => {
if (result.disabled) return; if (result.disabled) return;
selectedIndex = index; selectedIndex = index;
select(); select();
}}" }}
on:keyup="{(e) => { on:keyup={(e) => {
if (e.key !== 'Enter') return; if (e.key !== 'Enter') return;
if (result.disabled) return; if (result.disabled) return;
selectedIndex = index; selectedIndex = index;
select(); select();
}}" }}
on:mouseenter="{() => { on:mouseenter={() => {
if (result.disabled) return; if (result.disabled) return;
selectedIndex = index; selectedIndex = index;
}}" }}
> >
<slot {result} {index} {value}> <slot {result} {index} {value}>
{@html result.string} {@html result.string}

View file

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
interface Props { interface Props {
/** /**
* Whether to wrap the graphic with an aria hidden tag. * Whether to wrap the graphic with an aria hidden tag.
*/ */
hidden?: boolean; hidden?: boolean;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} }

View file

@ -40,11 +40,11 @@
import PlaceholderImg from './stories/placeholder.png'; import PlaceholderImg from './stories/placeholder.png';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<GraphicBlock {...args}> <GraphicBlock {...args}>
<div class="demo-graphic"> <div class="demo-graphic">
<img src="{PlaceholderImg}" alt="placeholder" /> <img src={PlaceholderImg} alt="placeholder" />
</div> </div>
</GraphicBlock> </GraphicBlock>
{/snippet} {/snippet}
@ -52,14 +52,14 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
title: 'Bacon ipsum dolor amet t-bone', title: 'Bacon ipsum dolor amet t-bone',
description: 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.', '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: notes:
'Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)', 'Note: Data current as of Aug. 2, 2022.\n\nSource: [Google research](https://google.com)',
}}" }}
/> />
<Story name="ArchieML" {...withStoryDocs(archieMLDocs)}> <Story name="ArchieML" {...withStoryDocs(archieMLDocs)}>
@ -76,18 +76,18 @@
<Story name="Custom text" {...withStoryDocs(customTextDocs)}> <Story name="Custom text" {...withStoryDocs(customTextDocs)}>
<GraphicBlock width="normal"> <GraphicBlock width="normal">
{#snippet title()} {#snippet title()}
<div > <div>
<h5>My smaller title</h5> <h5>My smaller title</h5>
</div> </div>
{/snippet} {/snippet}
<div class="demo-graphic"> <div class="demo-graphic">
<img src="{PlaceholderImg}" alt="placeholder" /> <img src={PlaceholderImg} alt="placeholder" />
</div> </div>
{#snippet notes()} {#snippet notes()}
<aside > <aside>
<p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p> <p><strong>Note:</strong> Data current as of Aug. 2, 2022.</p>
</aside> </aside>
{/snippet} {/snippet}
</GraphicBlock> </GraphicBlock>
</Story> </Story>

View file

@ -76,23 +76,23 @@
<Block {id} {snap} {role} {width} {ariaLabel} class="graphic fmy-6 {cls}"> <Block {id} {snap} {role} {width} {ariaLabel} class="graphic fmy-6 {cls}">
{#if $$slots.title} {#if $$slots.title}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<TextBlock width="{textWidth}"> <TextBlock width={textWidth}>
<!-- Custom title content --> <!-- Custom title content -->
<slot name="title" /> <slot name="title" />
</TextBlock> </TextBlock>
</PaddingReset> </PaddingReset>
{:else if title} {:else if title}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<TextBlock width="{textWidth}"> <TextBlock width={textWidth}>
<h3>{title}</h3> <h3>{title}</h3>
{#if description} {#if description}
<Markdown source="{description}" /> <Markdown source={description} />
{/if} {/if}
</TextBlock> </TextBlock>
</PaddingReset> </PaddingReset>
{/if} {/if}
<AriaHidden hidden="{!!$$slots.aria || !!ariaDescription}"> <AriaHidden hidden={!!$$slots.aria || !!ariaDescription}>
<!-- Graphic content --> <!-- Graphic content -->
<slot /> <slot />
</AriaHidden> </AriaHidden>
@ -102,22 +102,22 @@
<!-- Custom ARIA markup --> <!-- Custom ARIA markup -->
<slot name="aria" /> <slot name="aria" />
{:else} {:else}
<Markdown source="{ariaDescription}" /> <Markdown source={ariaDescription} />
{/if} {/if}
</div> </div>
{/if} {/if}
{#if $$slots.notes} {#if $$slots.notes}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<TextBlock width="{textWidth}"> <TextBlock width={textWidth}>
<!-- Custom notes content --> <!-- Custom notes content -->
<slot name="notes" /> <slot name="notes" />
</TextBlock> </TextBlock>
</PaddingReset> </PaddingReset>
{:else if notes} {:else if notes}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<TextBlock width="{textWidth}"> <TextBlock width={textWidth}>
<aside> <aside>
<Markdown source="{notes}" /> <Markdown source={notes} />
</aside> </aside>
</TextBlock> </TextBlock>
</PaddingReset> </PaddingReset>

View file

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ContainerWidth } from '../@types/global'; import type { ContainerWidth } from '../@types/global';
import Block from '../Block/Block.svelte'; import Block from '../Block/Block.svelte';
interface Props { interface Props {
/** Width of the component within the text well. */ /** 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 --> <!-- 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 --> <!-- Artboard: xs -->
{#if width && width >= 0 && width < 510} {#if width && width >= 0 && width < 510}
<div id="g-_ai-chart-xs" class="g-artboard" style=""> <div id="g-_ai-chart-xs" class="g-artboard" style="">
@ -26,7 +26,7 @@
id="g-_ai-chart-xs-img" id="g-_ai-chart-xs-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartXs});`}" style={`background-image: url(${chartXs});`}
></div> ></div>
<div <div
id="g-ai0-1" id="g-ai0-1"
@ -159,7 +159,7 @@
id="g-_ai-chart-sm-img" id="g-_ai-chart-sm-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartSm});`}" style={`background-image: url(${chartSm});`}
></div> ></div>
<div <div
id="g-ai1-1" id="g-ai1-1"
@ -292,7 +292,7 @@
id="g-_ai-chart-md-img" id="g-_ai-chart-md-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartMd});`}" style={`background-image: url(${chartMd});`}
></div> ></div>
<div <div
id="g-ai2-1" id="g-ai2-1"

View file

@ -44,7 +44,7 @@
import Map from './stories/graphic.svelte'; import Map from './stories/graphic.svelte';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Headline {...args} /> <Headline {...args} />
{/snippet} {/snippet}
@ -52,49 +52,49 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
section: 'World News', section: 'World News',
hed: 'Reuters Graphics interactive', hed: 'Reuters Graphics interactive',
hedSize: 'normal', hedSize: 'normal',
dek: '', dek: '',
authors: [], authors: [],
}}" }}
/> />
<Story name="With dek" {...withStoryDocs(withDekDocs)}> <Story name="With dek" {...withStoryDocs(withDekDocs)}>
<Headline <Headline
hed="{'Reuters Graphics Interactive'}" hed={'Reuters Graphics Interactive'}
dek="{'The beginning of a beautiful page'}" dek={'The beginning of a beautiful page'}
section="{'Global news'}" section={'Global news'}
/> />
</Story> </Story>
<Story name="With byline" {...withStoryDocs(withBylineDocs)}> <Story name="With byline" {...withStoryDocs(withBylineDocs)}>
<Headline <Headline
hed="{'Reuters Graphics Interactive'}" hed={'Reuters Graphics Interactive'}
dek="{'The beginning of a beautiful page'}" dek={'The beginning of a beautiful page'}
section="{'Global news'}" section={'Global news'}
authors="{['Dea Bankova', 'Aditi Bhandari']}" authors={['Dea Bankova', 'Aditi Bhandari']}
publishTime="{new Date('2020-01-01').toISOString()}" publishTime={new Date('2020-01-01').toISOString()}
/> />
</Story> </Story>
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}> <Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
<Headline width="wide"> <Headline width="wide">
{#snippet hed()} {#snippet hed()}
<h1 class="custom-hed" > <h1 class="custom-hed">
<span class="small block text-base">The secret to</span> <span class="small block text-base">The secret to</span>
“The Nutcracker's” “The Nutcracker's”
<span class="small block text-base fpt-1">success</span> <span class="small block text-base fpt-1">success</span>
</h1> </h1>
{/snippet} {/snippet}
{#snippet dek()} {#snippet dek()}
<p class="custom-dek !fmt-3" > <p class="custom-dek !fmt-3">
How “The Nutcracker” ballet became an<span How “The Nutcracker” ballet became an<span
class="font-medium mx-1 px-1.5 py-1">American holday staple</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 >and a financial pillar of ballet companies across the country
</p> </p>
{/snippet} {/snippet}
</Headline> </Headline>
<style lang="scss"> <style lang="scss">
.custom-hed { .custom-hed {
@ -109,21 +109,20 @@
</Story> </Story>
<Story name="With crown image" {...withStoryDocs(withCrownImgDocs)}> <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 --> <!-- Add a crown -->
{#snippet crown()} {#snippet crown()}
<img <img
src={crownImgSrc}
src="{crownImgSrc}"
width="100" width="100"
class="mx-auto mb-0" class="mx-auto mb-0"
alt="Illustration of Europe" alt="Illustration of Europe"
/> />
{/snippet} {/snippet}
<!-- Override the hed with a named slot --> <!-- Override the hed with a named slot -->
{#snippet hed()} {#snippet hed()}
<h1 class="!font-serif !tracking-wide">Europa</h1> <h1 class="!font-serif !tracking-wide">Europa</h1>
{/snippet} {/snippet}
</Headline> </Headline>
</Story> </Story>
@ -131,17 +130,17 @@
<Headline <Headline
width="wider" width="wider"
class="!fmt-1" class="!fmt-1"
hed="{'Unfriendly skies'}" hed={'Unfriendly skies'}
dek="{'How Russias invasion of Ukraine is redrawing air routes'}" dek={'How Russias invasion of Ukraine is redrawing air routes'}
section="{'Ukraine Crisis'}" section={'Ukraine Crisis'}
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}" authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
publishTime="{new Date('2022-03-04').toISOString()}" publishTime={new Date('2022-03-04').toISOString()}
> >
<!-- Add a crown graphic --> <!-- Add a crown graphic -->
{#snippet crown()} {#snippet crown()}
<div > <div>
<Map /> <Map />
</div> </div>
{/snippet} {/snippet}
</Headline> </Headline>
</Story> </Story>

View file

@ -95,8 +95,8 @@
<!-- Headline named slot --> <!-- Headline named slot -->
<slot name="hed" /> <slot name="hed" />
{:else} {:else}
<h1 class="{hedClass}"> <h1 class={hedClass}>
<Markdown source="{hed}" parseInline /> <Markdown source={hed} parseInline />
</h1> </h1>
{/if} {/if}
{#if $$slots.dek} {#if $$slots.dek}
@ -106,7 +106,7 @@
</div> </div>
{:else if dek} {:else if dek}
<div class="dek fmx-auto fmb-6"> <div class="dek fmx-auto fmb-6">
<Markdown source="{dek}" /> <Markdown source={dek} />
</div> </div>
{/if} {/if}
</div> </div>

View file

@ -14,7 +14,7 @@
let width = $state(null); let width = $state(null);
</script> </script>
<div id="g-graphic-box" bind:clientWidth="{width}"> <div id="g-graphic-box" bind:clientWidth={width}>
<!-- Artboard: xs --> <!-- Artboard: xs -->
{#if width && width >= 0 && width < 510} {#if width && width >= 0 && width < 510}
<div id="g-graphic-xs" class="g-artboard" style=""> <div id="g-graphic-xs" class="g-artboard" style="">
@ -22,7 +22,7 @@
<div <div
id="g-graphic-xs-img" id="g-graphic-xs-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartXs});`}" style={`background-image: url(${chartXs});`}
></div> ></div>
<div <div
id="g-ai0-3" id="g-ai0-3"
@ -89,7 +89,7 @@
<div <div
id="g-graphic-sm-img" id="g-graphic-sm-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartSm});`}" style={`background-image: url(${chartSm});`}
></div> ></div>
<div <div
id="g-ai1-1" id="g-ai1-1"
@ -213,7 +213,7 @@
<div <div
id="g-graphic-md-img" id="g-graphic-md-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartMd});`}" style={`background-image: url(${chartMd});`}
></div> ></div>
<div <div
@ -346,7 +346,7 @@
<div <div
id="g-graphic-lg-img" id="g-graphic-lg-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartLg});`}" style={`background-image: url(${chartLg});`}
></div> ></div>
<div <div
id="g-ai3-1" id="g-ai3-1"
@ -485,7 +485,7 @@
<div <div
id="g-graphic-xl-img" id="g-graphic-xl-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartXl});`}" style={`background-image: url(${chartXl});`}
></div> ></div>
<div <div
id="g-ai4-1" id="g-ai4-1"

View file

@ -75,14 +75,14 @@
<Story <Story
name="With backdrop photo" name="With backdrop photo"
args="{{ args={{
section: 'World News', section: 'World News',
hed: 'Reuters Graphics Interactive', hed: 'Reuters Graphics Interactive',
dek: 'The beginning of a beautiful page', dek: 'The beginning of a beautiful page',
authors: ['Simon Scarr', 'Vijdan Mohammad Kawoosa'], authors: ['Simon Scarr', 'Vijdan Mohammad Kawoosa'],
publishTime: new Date('2022-03-04').toISOString(), publishTime: new Date('2022-03-04').toISOString(),
img: polarImgSrc, img: polarImgSrc,
}}" }}
/> />
<Story name="With transparent header" {...withStoryDocs(transparentHeaderDocs)}> <Story name="With transparent header" {...withStoryDocs(transparentHeaderDocs)}>
@ -95,9 +95,9 @@
section="World News" section="World News"
hed="Reuters Graphics Interactive" hed="Reuters Graphics Interactive"
dek="The beginning of a beautiful page" dek="The beginning of a beautiful page"
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}" authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
publishTime="{new Date('2022-03-04').toISOString()}" publishTime={new Date('2022-03-04').toISOString()}
img="{polarImgSrc}" img={polarImgSrc}
/> />
</div> </div>
</Story> </Story>
@ -108,12 +108,12 @@
</Block> </Block>
<HeroHeadline <HeroHeadline
hed="{'Earthquake devastates Afghanistan'}" hed={'Earthquake devastates Afghanistan'}
hedSize="{'big'}" hedSize={'big'}
hedWidth="wide" hedWidth="wide"
class="custom-hero mb-0" class="custom-hero mb-0"
dek="" dek=""
authors="{[ authors={[
'Anand Katakam', 'Anand Katakam',
'Vijdan Mohammad Kawoosa', 'Vijdan Mohammad Kawoosa',
'Adolfo Arranz', 'Adolfo Arranz',
@ -123,8 +123,8 @@
'Jitesh Chowdhury', 'Jitesh Chowdhury',
'Manas Sharma', 'Manas Sharma',
'Aditi Bhandari', 'Aditi Bhandari',
]}" ]}
publishTime="{new Date('2022-06-24').toISOString()}" publishTime={new Date('2022-06-24').toISOString()}
> >
<div slot="background"> <div slot="background">
<GraphicBlock <GraphicBlock
@ -135,7 +135,7 @@
notes="" notes=""
ariaDescription="Earthquake impact map" ariaDescription="Earthquake impact map"
> >
<svelte:component this="{QuakeMap}" /> <svelte:component this={QuakeMap} />
</GraphicBlock> </GraphicBlock>
</div> </div>
</HeroHeadline> </HeroHeadline>
@ -186,16 +186,16 @@
hed="The conflict in Ethiopia" hed="The conflict in Ethiopia"
hedSize="bigger" hedSize="bigger"
hedWidth="wide" hedWidth="wide"
authors="{['Aditi Bhandari ', 'David Lewis']}" authors={['Aditi Bhandari ', 'David Lewis']}
publishTime="{new Date('2020-12-18').toISOString()}" publishTime={new Date('2020-12-18').toISOString()}
> >
<div slot="background"> <div slot="background">
<Video <Video
width="widest" width="widest"
class="my-0" class="my-0"
showControls="{false}" showControls={false}
preloadVideo="auto" preloadVideo="auto"
playVideoWhenInView="{false}" playVideoWhenInView={false}
src="https://vm.reuters.tv/9c72e/titlef2ac(425954_R21MP41500).mp4" 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" 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." notes="Drone footage from the Village 8 refugee camp in Sudan."
@ -227,13 +227,13 @@
</Block> </Block>
<HeroHeadline <HeroHeadline
hed="{'Buried under the bricks'}" hed={'Buried under the bricks'}
hedWidth="wide" hedWidth="wide"
class="mb-0" class="mb-0"
dek="{'How mud-brick housing made the Morocco earthquake so deadly'}" dek={'How mud-brick housing made the Morocco earthquake so deadly'}
section="{'Global news'}" section={'Global news'}
authors="{['Mariano Zafra']}" authors={['Mariano Zafra']}
publishTime="{new Date('2020-01-01').toISOString()}" publishTime={new Date('2020-01-01').toISOString()}
> >
<div slot="inline"> <div slot="inline">
<FeaturePhoto <FeaturePhoto
@ -253,13 +253,13 @@
</Block> </Block>
<HeroHeadline <HeroHeadline
hed="{'The plunge from 29,000 feet'}" hed={'The plunge from 29,000 feet'}
hedWidth="wide" hedWidth="wide"
class="mb-0" class="mb-0"
dek="{'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}" dek={'How China Eastern Airlines flight MU5735 went from an uneventful flight at cruising altitude to disaster in just minutes.'}
section="{'Global news'}" section={'Global news'}
authors="{['Simon Scarr', 'Vijdan Mohammad Kawoosa']}" authors={['Simon Scarr', 'Vijdan Mohammad Kawoosa']}
publishTime="{new Date('2020-01-01').toISOString()}" publishTime={new Date('2020-01-01').toISOString()}
> >
<div slot="inline"> <div slot="inline">
<GraphicBlock <GraphicBlock
@ -270,7 +270,7 @@
notes="Source: Satellite image from Google, Maxar Technologies, CNES/Airbus, Landsat/Copernicus" notes="Source: Satellite image from Google, Maxar Technologies, CNES/Airbus, Landsat/Copernicus"
ariaDescription="Aerial map showing trajectory of crash" ariaDescription="Aerial map showing trajectory of crash"
> >
<svelte:component this="{CrashMap}" /> <svelte:component this={CrashMap} />
</GraphicBlock> </GraphicBlock>
</div> </div>
</HeroHeadline> </HeroHeadline>
@ -282,21 +282,21 @@
</Block> </Block>
<HeroHeadline <HeroHeadline
hed="{'Devastation in Derna'}" hed={'Devastation in Derna'}
hedWidth="wide" hedWidth="wide"
class="mb-0" class="mb-0"
dek="{'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}" dek={'How raging floods burst dams, destroyed neighbourhoods and killed thousands in Libya'}
section="{'Global news'}" section={'Global news'}
authors="{['Simon Scarr']}" authors={['Simon Scarr']}
publishTime="{new Date('2020-01-01').toISOString()}" publishTime={new Date('2020-01-01').toISOString()}
> >
<div slot="inline"> <div slot="inline">
<Video <Video
width="widest" width="widest"
class="my-0" class="my-0"
showControls="{false}" showControls={false}
preloadVideo="auto" preloadVideo="auto"
playVideoWhenInView="{false}" playVideoWhenInView={false}
src="https://www.reuters.com/graphics/LIBYA-STORM/EXPLAINER/klvyzqebzpg/cdn/video/drone.mp4" src="https://www.reuters.com/graphics/LIBYA-STORM/EXPLAINER/klvyzqebzpg/cdn/video/drone.mp4"
notes="Drone shots of Derna, Libya. September 14, 2023. REUTERS" notes="Drone shots of Derna, Libya. September 14, 2023. REUTERS"
ariaDescription="alttext fot video" ariaDescription="alttext fot video"
@ -308,14 +308,14 @@
<Story name="With custom hed" {...withStoryDocs(customHedDocs)}> <Story name="With custom hed" {...withStoryDocs(customHedDocs)}>
<HeroHeadline <HeroHeadline
class="custom-hed" class="custom-hed"
authors="{[ authors={[
'Prasanta Kumar Dutta', 'Prasanta Kumar Dutta',
'Dea Bankova', 'Dea Bankova',
'Aditi Bhandari', 'Aditi Bhandari',
'Anurag Rao', 'Anurag Rao',
]}" ]}
publishTime="{new Date('2023-05-11').toISOString()}" publishTime={new Date('2023-05-11').toISOString()}
img="{eurovisImgSrc}" img={eurovisImgSrc}
> >
<h1 slot="hed"> <h1 slot="hed">
<div class="body-note">A visual guide to</div> <div class="body-note">A visual guide to</div>

View file

@ -96,7 +96,7 @@
{#if $$slots.hed} {#if $$slots.hed}
<Headline <Headline
class="{cls} !text-{hedAlign}" class="{cls} !text-{hedAlign}"
width="{hedWidth}" width={hedWidth}
{section} {section}
{hedSize} {hedSize}
{hed} {hed}
@ -110,7 +110,7 @@
{:else} {:else}
<Headline <Headline
class="{cls} !text-{hedAlign}" class="{cls} !text-{hedAlign}"
width="{hedWidth}" width={hedWidth}
{section} {section}
{hedSize} {hedSize}
{hed} {hed}
@ -144,11 +144,11 @@
<!-- Inline hero --> <!-- Inline hero -->
{#if $$slots.inline} {#if $$slots.inline}
<Block width="fluid" class="hero-headline inline-hero"> <Block width="fluid" class="hero-headline inline-hero">
<PaddingReset containerIsFluid="{true}"> <PaddingReset containerIsFluid={true}>
{#if $$slots.hed} {#if $$slots.hed}
<Headline <Headline
class="{cls} !text-{hedAlign}" class="{cls} !text-{hedAlign}"
width="{hedWidth}" width={hedWidth}
{section} {section}
{hedSize} {hedSize}
{hed} {hed}
@ -162,7 +162,7 @@
{:else} {:else}
<Headline <Headline
class="{cls} !text-{hedAlign}" class="{cls} !text-{hedAlign}"
width="{hedWidth}" width={hedWidth}
{section} {section}
{hedSize} {hedSize}
{hed} {hed}

View file

@ -18,7 +18,7 @@
<!-- Generated by ai2html v0.100.0 - 2022-03-29 17:01 --> <!-- 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 --> <!-- Artboard: xs -->
{#if width && width >= 0 && width < 510} {#if width && width >= 0 && width < 510}
<div id="g-CRASH_1-xs" class="g-artboard" style=""> <div id="g-CRASH_1-xs" class="g-artboard" style="">
@ -27,7 +27,7 @@
id="g-CRASH_1-xs-img" id="g-CRASH_1-xs-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartXs});`}" style={`background-image: url(${chartXs});`}
></div> ></div>
<div <div
id="g-ai0-1" id="g-ai0-1"
@ -80,7 +80,7 @@
id="g-CRASH_1-sm-img" id="g-CRASH_1-sm-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartSm});`}" style={`background-image: url(${chartSm});`}
></div> ></div>
<div <div
id="g-ai1-1" id="g-ai1-1"
@ -133,7 +133,7 @@
id="g-CRASH_1-md-img" id="g-CRASH_1-md-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartMd});`}" style={`background-image: url(${chartMd});`}
></div> ></div>
<div <div
id="g-ai2-1" id="g-ai2-1"
@ -194,7 +194,7 @@
id="g-CRASH_1-lg-img" id="g-CRASH_1-lg-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartLg});`}" style={`background-image: url(${chartLg});`}
></div> ></div>
<div <div
id="g-ai3-1" id="g-ai3-1"
@ -255,7 +255,7 @@
id="g-CRASH_1-xl-img" id="g-CRASH_1-xl-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartXl});`}" style={`background-image: url(${chartXl});`}
></div> ></div>
<div <div
id="g-ai4-1" id="g-ai4-1"
@ -316,7 +316,7 @@
id="g-CRASH_1-xl_copy-img" id="g-CRASH_1-xl_copy-img"
class="g-aiImg" class="g-aiImg"
alt="" alt=""
style="{`background-image: url(${chartXxl});`}" style={`background-image: url(${chartXxl});`}
></div> ></div>
<div <div
id="g-ai5-1" id="g-ai5-1"

View file

@ -13,7 +13,7 @@
import chartXl from '././quake-map-top-xl.jpeg'; import chartXl from '././quake-map-top-xl.jpeg';
</script> </script>
<div id="g-quake-map-top-box" bind:clientWidth="{width}"> <div id="g-quake-map-top-box" bind:clientWidth={width}>
<!-- Artboard: xs --> <!-- Artboard: xs -->
{#if width && width >= 0 && width < 510} {#if width && width >= 0 && width < 510}
<div id="g-quake-map-top-xs" class="g-artboard" style=""> <div id="g-quake-map-top-xs" class="g-artboard" style="">
@ -21,7 +21,7 @@
<div <div
id="g-quake-map-top-xs-img" id="g-quake-map-top-xs-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartXs});`}" style={`background-image: url(${chartXs});`}
></div> ></div>
<div <div
id="g-ai0-1" id="g-ai0-1"
@ -102,7 +102,7 @@
<div <div
id="g-quake-map-top-sm-img" id="g-quake-map-top-sm-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartSm});`}" style={`background-image: url(${chartSm});`}
></div> ></div>
<div <div
id="g-ai1-1" id="g-ai1-1"
@ -183,7 +183,7 @@
<div <div
id="g-quake-map-top-md-img" id="g-quake-map-top-md-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartMd});`}" style={`background-image: url(${chartMd});`}
></div> ></div>
<div <div
id="g-ai2-1" id="g-ai2-1"
@ -271,7 +271,7 @@
<div <div
id="g-quake-map-top-lg-img" id="g-quake-map-top-lg-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartLg});`}" style={`background-image: url(${chartLg});`}
></div> ></div>
<div <div
id="g-ai3-1" id="g-ai3-1"
@ -359,7 +359,7 @@
<div <div
id="g-quake-map-top-xl-img" id="g-quake-map-top-xl-img"
class="g-aiImg" class="g-aiImg"
style="{`background-image: url(${chartXl});`}" style={`background-image: url(${chartXl});`}
></div> ></div>
<div <div
id="g-ai4-1" id="g-ai4-1"

View file

@ -29,7 +29,7 @@
const photos = photosJson.map((p) => ({ ...p, altText: p.caption })); const photos = photosJson.map((p) => ({ ...p, altText: p.caption }));
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<PhotoCarousel {...args} /> <PhotoCarousel {...args} />
{/snippet} {/snippet}
@ -37,18 +37,18 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'wider', width: 'wider',
photos, photos,
}}" }}
/> />
<Story <Story
name="Custom credits and captions" name="Custom credits and captions"
args="{{ args={{
width: 'wider', width: 'wider',
photos, photos,
}}" }}
{...withStoryDocs(customDocs)} {...withStoryDocs(customDocs)}
> >
<PhotoCarousel <PhotoCarousel
@ -58,11 +58,11 @@
}} }}
> >
{#snippet credit({ credit })} {#snippet credit({ credit })}
<p class="custom-credit" >{credit}</p> <p class="custom-credit">{credit}</p>
{/snippet} {/snippet}
{#snippet caption({ caption })} {#snippet caption({ caption })}
<p class="custom-caption" >{caption}</p> <p class="custom-caption">{caption}</p>
{/snippet} {/snippet}
</PhotoCarousel> </PhotoCarousel>
</Story> </Story>

View file

@ -111,17 +111,17 @@
</script> </script>
<Block {width} {id} class="photo-carousel fmy-6 {cls}"> <Block {width} {id} class="photo-carousel fmy-6 {cls}">
<div class="carousel-container" bind:clientWidth="{containerWidth}"> <div class="carousel-container" bind:clientWidth={containerWidth}>
<Splide <Splide
hasTrack="{false}" hasTrack={false}
options="{{ options={{
height: carouselHeight, height: carouselHeight,
fixedHeight: carouselHeight, fixedHeight: carouselHeight,
lazyLoad: 'nearby', lazyLoad: 'nearby',
preloadPages: preloadImages, preloadPages: preloadImages,
}}" }}
aria-label="{carouselAriaLabel}" aria-label={carouselAriaLabel}
on:move="{handleActiveChange}" on:move={handleActiveChange}
> >
<div class="image-container"> <div class="image-container">
<SplideTrack> <SplideTrack>
@ -134,20 +134,19 @@
> >
<img <img
class="w-full h-full fmy-0" class="w-full h-full fmy-0"
data-splide-lazy="{photo.src}" data-splide-lazy={photo.src}
alt="{photo.altText}" alt={photo.altText}
style:object-fit="{photo.objectFit || style:object-fit={photo.objectFit || defaultImageObjectFit}
defaultImageObjectFit}" style:object-position={photo.objectPosition ||
style:object-position="{photo.objectPosition || defaultImageObjectPosition}
defaultImageObjectPosition}"
/> />
{#if $$slots.credit} {#if $$slots.credit}
<slot name="credit" credit="{photo.credit}" /> <slot name="credit" credit={photo.credit} />
{:else} {:else}
<span <span
class="credit absolute fmb-1 fml-1 leading-tighter font-note text-xxs" class="credit absolute fmb-1 fml-1 leading-tighter font-note text-xxs"
class:contain-fit="{photo.objectFit === 'contain' || class:contain-fit={photo.objectFit === 'contain' ||
defaultImageObjectFit === 'contain'}" defaultImageObjectFit === 'contain'}
>{photo.credit}</span >{photo.credit}</span
> >
{/if} {/if}
@ -158,18 +157,18 @@
</SplideTrack> </SplideTrack>
{#if photos[activeImageIndex].caption} {#if photos[activeImageIndex].caption}
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<Block width="{textWidth}"> <Block width={textWidth}>
{#if $$slots.caption} {#if $$slots.caption}
<slot <slot
name="caption" name="caption"
caption="{photos[activeImageIndex].caption}" caption={photos[activeImageIndex].caption}
/> />
{:else} {:else}
{#key activeImageIndex} {#key activeImageIndex}
<p <p
class="caption body-caption text-center" class="caption body-caption text-center"
in:fly|local="{{ x: 20, duration: 175 }}" in:fly|local={{ x: 20, duration: 175 }}
> >
{photos[activeImageIndex].caption} {photos[activeImageIndex].caption}
</p> </p>
@ -185,10 +184,10 @@
<div class="splide__arrows fp-1"> <div class="splide__arrows fp-1">
<button class="splide__arrow splide__arrow--prev"> <button class="splide__arrow splide__arrow--prev">
<Fa icon="{faChevronLeft}" fw /> <Fa icon={faChevronLeft} fw />
</button> </button>
<button class="splide__arrow splide__arrow--next"> <button class="splide__arrow splide__arrow--next">
<Fa icon="{faChevronRight}" fw /> <Fa icon={faChevronRight} fw />
</button> </button>
</div> </div>
</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"> <script module lang="ts">
// @ts-ignore raw import { defineMeta } from '@storybook/addon-svelte-csf';
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 PhotoPack from './PhotoPack.svelte'; import PhotoPack from './PhotoPack.svelte';
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js'; const { Story } = defineMeta({
export const meta = {
title: 'Components/Multimedia/PhotoPack', title: 'Components/Multimedia/PhotoPack',
component: PhotoPack, component: PhotoPack,
...withComponentDocs(componentDocs),
argTypes: { argTypes: {
width: { width: {
control: 'select', control: 'select',
@ -24,37 +15,34 @@
options: ['normal', 'wide', 'wider', 'widest', 'fluid'], options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
}, },
}, },
}; });
</script> </script>
<script> <script>
import { Template, Story } from '@storybook/addon-svelte-csf';
const defaultImages = [ const defaultImages = [
{ {
src: 'https://via.placeholder.com/1024x768.jpg', 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',
altText: 'alt text',
caption: 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', "Spain's Sergio Busquets and Aymeric Laporte react before a Germany goal is disallowed following a VAR review.",
maxHeight: 400, altText: 'alt text',
}, },
{ {
src: 'https://via.placeholder.com/1640x1180.jpg', 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',
altText: 'alt text',
caption: 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', 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',
altText: 'alt text',
caption: 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', 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',
altText: 'alt text',
caption: 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', width: 'wide',
textWidth: 'normal', textWidth: 'normal',
gap: '15', gap: '15',
images: [ images: defaultImages,
{
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',
},
],
layouts: [ layouts: [
{ breakpoint: 750, rows: [2, 3] }, { breakpoint: 750, rows: [2, 3] },
{ breakpoint: 450, rows: [1, 2, 2] }, { breakpoint: 450, rows: [1, 2, 2] },
@ -106,8 +67,8 @@
const altTextImages = [ const altTextImages = [
{ {
src: 'https://via.placeholder.com/1024x768.jpg',
altText: 'alt text', altText: 'alt text',
src: 'https://via.placeholder.com/1024x768.jpg',
caption: 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', '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, maxHeight: 400,
@ -122,37 +83,23 @@
const altTextLayouts = [{ breakpoint: 450, rows: [2] }]; const altTextLayouts = [{ breakpoint: 450, rows: [2] }];
</script> </script>
<Template >
{#snippet children({ ...args })}
<PhotoPack {...args} />
{/snippet}
</Template>
<Story <Story
name="Default" name="Demo"
args="{{ args={{
width: 'wide', width: 'wide',
textWidth: 'normal', textWidth: 'normal',
images: defaultImages, images: defaultImages,
layouts: defaultLayouts, layouts: defaultLayouts,
}}" }}
/>
<Story
name="ArchieML"
{...withStoryDocs(archieMLDocs)}
args="{archieMLBlock}"
/> />
<Story <Story
name="Missing altText" name="Missing altText"
args="{{ exportName="MissingAltText"
args={{
width: 'wide', width: 'wide',
textWidth: 'normal', textWidth: 'normal',
images: altTextImages, images: altTextImages,
layouts: altTextLayouts, 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) --> <!-- @component `PhotoPack` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-multimedia-photopack--docs) -->
<script lang="ts"> <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; src: string;
altText: string; altText: string;
caption?: string; caption?: string;
maxHeight?: number; maxHeight?: number;
} }
/**
* Array of image objects
* @required
*/
export let images: Image[] = [];
interface Layout { export interface Layout {
breakpoint: number; breakpoint: number;
rows: 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'; type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
/** Width of the component within the text well. */ interface Props {
export let width: ContainerWidth = 'normal'; /** 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. * Sort layouts by descending breakpoints.
* Can't ever be wider than `width`. *
* @type {string} * @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'; let sortedLayouts = $derived(
layouts?.toSorted((a, b) => (a.breakpoint < b.breakpoint ? 1 : -1))
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
); );
$: 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> </script>
<Block {width} {id} class="photopack fmy-6 {cls}"> <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} {#each rows as row, ri}
<div <div
class="photopack-row flex justify-between" class="photopack-row flex justify-between"
style:gap="0 {gap}px" 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} {#each row as img, i}
<figure <figure
@ -105,9 +95,9 @@
> >
<img <img
class="m-0 w-full h-full object-cover" class="m-0 w-full h-full object-cover"
src="{img.src}" src={img.src}
alt="{img.altText}" alt={img.altText}
style:max-height="{img.maxHeight ? img.maxHeight + 'px' : ''}" style:max-height={img.maxHeight ? img.maxHeight + 'px' : ''}
/> />
{#if !img.altText} {#if !img.altText}
<div class="alt-warning absolute text-xxs py-1 px-2">altText</div> <div class="alt-warning absolute text-xxs py-1 px-2">altText</div>
@ -117,14 +107,14 @@
</div> </div>
{/each} {/each}
</div> </div>
<PaddingReset containerIsFluid="{width === 'fluid'}"> <PaddingReset containerIsFluid={width === 'fluid'}>
<div class="notes contents"> <div class="notes contents">
<Block width="{textWidth}" class="photopack-captions-container"> <Block width={textWidth} class="photopack-captions-container">
{#each rows as row, ri} {#each rows as row, ri}
{#each row as img, i} {#each row as img, i}
{#if img.caption} {#if img.caption}
<div id="{id}-figure-{ri}-{i}" class="caption"> <div id="{id}-figure-{ri}-{i}" class="caption">
<Markdown source="{img.caption}" /> <Markdown source={img.caption} />
</div> </div>
{/if} {/if}
{/each} {/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'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<PymChild {...args} /> <PymChild {...args} />
<div>Nothing to see here. 😎</div> <div>Nothing to see here. 😎</div>

View file

@ -41,7 +41,7 @@
import { Template, Story } from '@storybook/addon-svelte-csf'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<ReferralBlock {...args} /> <ReferralBlock {...args} />
{/snippet} {/snippet}
@ -49,21 +49,21 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
section: '/lifestyle/sports/', section: '/lifestyle/sports/',
number: 4, number: 4,
class: 'fmy-0', class: 'fmy-0',
heading: 'More World Cup coverage', heading: 'More World Cup coverage',
}}" }}
/> />
<Story <Story
name="By collection" name="By collection"
args="{{ args={{
collection: 'x-trump', collection: 'x-trump',
number: 6, number: 6,
class: 'fmy-8', class: 'fmy-8',
heading: 'The latest Trump coverage', heading: 'The latest Trump coverage',
}}" }}
{...withStoryDocs(collectionDocs)} {...withStoryDocs(collectionDocs)}
/> />

View file

@ -96,33 +96,33 @@
<Block {width} {id} class="referrals-block {cls}"> <Block {width} {id} class="referrals-block {cls}">
<div <div
class="block-container" class="block-container"
class:stacked="{clientWidth && clientWidth < 750}" class:stacked={clientWidth && clientWidth < 750}
bind:clientWidth bind:clientWidth
> >
{#if heading} {#if heading}
<div <div
class="heading h4 font-bold" class="heading h4 font-bold"
class:stacked="{clientWidth && clientWidth < 750}" class:stacked={clientWidth && clientWidth < 750}
> >
{heading} {heading}
</div> </div>
{/if} {/if}
<div <div
class="referral-container inline-flex flex-wrap w-full justify-between" class="referral-container inline-flex flex-wrap w-full justify-between"
class:stacked="{clientWidth && clientWidth < 750}" class:stacked={clientWidth && clientWidth < 750}
class:xs="{clientWidth && clientWidth < 450}" class:xs={clientWidth && clientWidth < 450}
> >
{#each referrals as referral} {#each referrals as referral}
<div class="referral"> <div class="referral">
<a <a
href="https://www.reuters.com{referral.canonical_url}" href="https://www.reuters.com{referral.canonical_url}"
target="{linkTarget}" target={linkTarget}
rel="{linkTarget === '_blank' ? 'noreferrer' : null}" rel={linkTarget === '_blank' ? 'noreferrer' : null}
> >
<div class="referral-pack flex justify-around my-0 mx-auto"> <div class="referral-pack flex justify-around my-0 mx-auto">
<div <div
class="headline" class="headline"
class:xs="{clientWidth && clientWidth < 450}" class:xs={clientWidth && clientWidth < 450}
> >
<div <div
class="kicker m-0 body-caption leading-tighter" class="kicker m-0 body-caption leading-tighter"
@ -145,14 +145,14 @@
</div> </div>
<div <div
class="image-container block m-0 overflow-hidden relative" class="image-container block m-0 overflow-hidden relative"
class:xs="{clientWidth && clientWidth < 450}" class:xs={clientWidth && clientWidth < 450}
> >
<img <img
class="block object-cover m-0 w-full" class="block object-cover m-0 w-full"
data-chromatic="ignore" data-chromatic="ignore"
src="{referral.thumbnail.renditions.landscape['240w']}" src={referral.thumbnail.renditions.landscape['240w']}
alt="{referral.thumbnail.caption || alt={referral.thumbnail.caption ||
referral.thumbnail.alt_text}" referral.thumbnail.alt_text}
/> />
</div> </div>
</div> </div>

View file

@ -22,8 +22,8 @@
{#if preload === 0 || (i >= (stackBackground ? 0 : index - preload) && i <= index + preload)} {#if preload === 0 || (i >= (stackBackground ? 0 : index - preload) && i <= index + preload)}
<div <div
class="step-background step-{i + 1} w-full absolute" class="step-background step-{i + 1} w-full absolute"
class:visible="{stackBackground ? i <= index : i === index}" class:visible={stackBackground ? i <= index : i === index}
class:invisible="{stackBackground ? i > index : i !== index}" class:invisible={stackBackground ? i > index : i !== index}
> >
<step.background {...step.backgroundProps || {}}></step.background> <step.background {...step.backgroundProps || {}}></step.background>
</div> </div>

View file

@ -11,7 +11,7 @@
let { step, backgroundWidth, index }: Props = $props(); let { step, backgroundWidth, index }: Props = $props();
</script> </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"> <div class="embedded-background step-{index + 1}" aria-hidden="true">
<step.background {...step.backgroundProps || {}}></step.background> <step.background {...step.backgroundProps || {}}></step.background>
</div> </div>

View file

@ -17,18 +17,18 @@
{#if typeof step.altText === 'string'} {#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden"> <div class="background-alt-text visually-hidden">
<Markdown source="{step.altText}" /> <Markdown source={step.altText} />
</div> </div>
{/if} {/if}
{:else if typeof step.foreground === 'string'} {:else if typeof step.foreground === 'string'}
<Block class="body-text step-{index + 1}"> <Block class="body-text step-{index + 1}">
<div class="embedded-foreground step-{index + 1}"> <div class="embedded-foreground step-{index + 1}">
<Markdown source="{step.foreground}" /> <Markdown source={step.foreground} />
</div> </div>
{#if typeof step.altText === 'string'} {#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden"> <div class="background-alt-text visually-hidden">
<Markdown source="{step.altText}" /> <Markdown source={step.altText} />
</div> </div>
{/if} {/if}
</Block> </Block>

View file

@ -19,20 +19,20 @@
<div class="empty-step-foreground"></div> <div class="empty-step-foreground"></div>
{#if typeof step.altText === 'string'} {#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden"> <div class="background-alt-text visually-hidden">
<Markdown source="{step.altText}" /> <Markdown source={step.altText} />
</div> </div>
{/if} {/if}
{:else} {:else}
<div class="step-foreground w-full"> <div class="step-foreground w-full">
{#if typeof step.foreground === 'string'} {#if typeof step.foreground === 'string'}
<Markdown source="{step.foreground}" /> <Markdown source={step.foreground} />
{:else} {:else}
<step.foreground {...step.foregroundProps || {}}></step.foreground> <step.foreground {...step.foregroundProps || {}}></step.foreground>
{/if} {/if}
</div> </div>
{#if typeof step.altText === 'string'} {#if typeof step.altText === 'string'}
<div class="background-alt-text visually-hidden"> <div class="background-alt-text visually-hidden">
<Markdown source="{step.altText}" /> <Markdown source={step.altText} />
</div> </div>
{/if} {/if}
{/if} {/if}

View file

@ -79,7 +79,7 @@
}; };
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Scroller {...args} /> <Scroller {...args} />
{/snippet} {/snippet}
@ -87,7 +87,7 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
steps: [ steps: [
{ {
background: BasicStep, background: BasicStep,
@ -112,14 +112,14 @@
backgroundWidth: 'fluid', backgroundWidth: 'fluid',
embeddedLayout: 'fb', embeddedLayout: 'fb',
embedded: false, embedded: false,
}}" }}
/> />
<Story name="ArchieML" args="{docBlock}" {...withStoryDocs(archieMLDocs)} /> <Story name="ArchieML" args={docBlock} {...withStoryDocs(archieMLDocs)} />
<Story <Story
name="Foreground components" name="Foreground components"
args="{{ args={{
steps: [ steps: [
{ {
background: BasicStep, background: BasicStep,
@ -142,13 +142,13 @@
backgroundWidth: 'fluid', backgroundWidth: 'fluid',
embeddedLayout: 'fb', embeddedLayout: 'fb',
embedded: false, embedded: false,
}}" }}
{...withStoryDocs(interactiveDocs)} {...withStoryDocs(interactiveDocs)}
/> />
<Story <Story
name="Ai2svelte" name="Ai2svelte"
args="{{ args={{
steps: [ steps: [
{ {
background: AiMap1, background: AiMap1,
@ -175,6 +175,6 @@
backgroundWidth: 'fluid', backgroundWidth: 'fluid',
embeddedLayout: 'fb', embeddedLayout: 'fb',
embedded: false, embedded: false,
}}" }}
{...withStoryDocs(ai2svelteDocs)} {...withStoryDocs(ai2svelteDocs)}
/> />

View file

@ -123,13 +123,13 @@
<div <div
slot="background" slot="background"
class="background min-h-screen relative p-0 flex justify-center" class="background min-h-screen relative p-0 flex justify-center"
class:right="{foregroundPosition === 'left opposite'}" class:right={foregroundPosition === 'left opposite'}
class:left="{foregroundPosition === 'right opposite'}" class:left={foregroundPosition === 'right opposite'}
aria-hidden="true" aria-hidden="true"
> >
<div class="scroller-graphic-well w-full"> <div class="scroller-graphic-well w-full">
<Block <Block
width="{backgroundWidth}" width={backgroundWidth}
class="background-container step-{index + class="background-container step-{index +
1} my-0 min-h-screen flex justify-center items-center relative" 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 --> <!-- 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 --> <!-- Artboard: XL -->
{#if width && width >= 1200} {#if width && width >= 1200}
<div id="g-step-1-xl" class="g-artboard" style=""> <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 --> <!-- 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 --> <!-- Artboard: XL -->
{#if width && width >= 1200} {#if width && width >= 1200}
<div id="g-step-2-xl" class="g-artboard" style=""> <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 --> <!-- 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 --> <!-- Artboard: XL -->
{#if width && width >= 1200} {#if width && width >= 1200}
<div id="g-step-3-xl" class="g-artboard" style=""> <div id="g-step-3-xl" class="g-artboard" style="">

View file

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

View file

@ -6,7 +6,7 @@
let { colour = 'lightblue' }: Props = $props(); let { colour = 'lightblue' }: Props = $props();
</script> </script>
<div class="step" style="{`background: ${colour};`}"></div> <div class="step" style={`background: ${colour};`}></div>
<style lang="scss"> <style lang="scss">
.step { .step {

View file

@ -17,10 +17,10 @@
import { Template, Story } from '@storybook/addon-svelte-csf'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<SearchInput {...args} /> <SearchInput {...args} />
{/snippet} {/snippet}
</Template> </Template>
<Story name="Default" args="{{}}" /> <Story name="Default" args={{}} />

View file

@ -38,17 +38,17 @@
id="search--input" id="search--input"
class="search--input body-caption pl-8" class="search--input body-caption pl-8"
type="text" type="text"
placeholder="{searchPlaceholder}" placeholder={searchPlaceholder}
oninput="{input}" oninput={input}
bind:value bind:value
/> />
<div <div
class="search--x absolute" class="search--x absolute"
role="button" role="button"
tabindex="0" tabindex="0"
class:invisible="{!active}" class:invisible={!active}
onclick="{clear}" onclick={clear}
onkeyup="{clear}" onkeyup={clear}
> >
<X /> <X />
</div> </div>

View file

@ -31,7 +31,7 @@
{#each links.social_links as link} {#each links.social_links as link}
{@const SvelteComponent = symbols[link.type]} {@const SvelteComponent = symbols[link.type]}
<li class="social-links symbol"> <li class="social-links symbol">
<a href="{normalizeUrl(link.url)}"> <a href={normalizeUrl(link.url)}>
<div class="button"> <div class="button">
<div class="social"> <div class="social">
<SvelteComponent /> <SvelteComponent />

View file

@ -11,7 +11,7 @@
<ul class="link-group"> <ul class="link-group">
{#each links.ad_links as link} {#each links.ad_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}"> <a href={normalizeUrl(link.url)}>
{link.text} {link.text}
</a> </a>
</li> </li>
@ -20,7 +20,7 @@
</section> </section>
<p class="disclaimer"> <p class="disclaimer">
All quotes delayed a minimum of 15 minutes. <a 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 >See here for a complete list of exchanges and delays</a
>. >.
</p> </p>
@ -28,7 +28,7 @@
<ul class="link-group"> <ul class="link-group">
{#each links.misc_links.filter((d) => !d.self) as link} {#each links.misc_links.filter((d) => !d.self) as link}
<li> <li>
<a href="{normalizeUrl(link.url)}"> <a href={normalizeUrl(link.url)}>
{link.text} {link.text}
</a> </a>
</li> </li>
@ -37,7 +37,7 @@
</section> </section>
<p class="copyright"> <p class="copyright">
© {links.copyright_year} Reuters. © {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> </p>
</div> </div>
</section> </section>

View file

@ -17,7 +17,7 @@
const mobileBreakpoint = 745; const mobileBreakpoint = 745;
</script> </script>
<svelte:window bind:innerWidth="{windowWidth}" /> <svelte:window bind:innerWidth={windowWidth} />
{#if links.latest_links} {#if links.latest_links}
<section class="quick-links"> <section class="quick-links">
@ -30,7 +30,7 @@
<ul> <ul>
{#each links.latest_links as link} {#each links.latest_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}">{link.text}</a> <a href={normalizeUrl(link.url)}>{link.text}</a>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -44,7 +44,7 @@
<div class="symbol"> <div class="symbol">
<SvelteComponent /> <SvelteComponent />
</div> </div>
<a href="{normalizeUrl(link.url)}"> <a href={normalizeUrl(link.url)}>
{link.text} {link.text}
</a> </a>
</li> </li>
@ -58,7 +58,7 @@
<ul> <ul>
{#each links.latest_links as link} {#each links.latest_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}">{link.text}</a> <a href={normalizeUrl(link.url)}>{link.text}</a>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -70,7 +70,7 @@
<ul> <ul>
{#each links.browse_links as link} {#each links.browse_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}">{link.text}</a> <a href={normalizeUrl(link.url)}>{link.text}</a>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -85,7 +85,7 @@
<div class="symbol"> <div class="symbol">
<SvelteComponent_1 /> <SvelteComponent_1 />
</div> </div>
<a href="{normalizeUrl(link.url)}"> <a href={normalizeUrl(link.url)}>
{link.text} {link.text}
</a> </a>
</li> </li>
@ -99,7 +99,7 @@
<ul> <ul>
{#each links.about_links as link} {#each links.about_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}">{link.text}</a> <a href={normalizeUrl(link.url)}>{link.text}</a>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -109,7 +109,7 @@
<ul> <ul>
{#each links.stay_informed_links as link} {#each links.stay_informed_links as link}
<li> <li>
<a href="{normalizeUrl(link.url)}">{link.text}</a> <a href={normalizeUrl(link.url)}>{link.text}</a>
</li> </li>
{/each} {/each}
</ul> </ul>

View file

@ -26,7 +26,7 @@
import Theme from '../Theme/Theme.svelte'; import Theme from '../Theme/Theme.svelte';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<div> <div>
<SiteFooter {...args} /> <SiteFooter {...args} />
@ -46,7 +46,7 @@
<Story <Story
name="Remove referrals" name="Remove referrals"
args="{{ includeReferrals: false }}" args={{ includeReferrals: false }}
{...withStoryDocs(removeReferralsDocs)} {...withStoryDocs(removeReferralsDocs)}
/> />

View file

@ -44,12 +44,12 @@
<footer <footer
class="my-0" class="my-0"
style="{` style={`
--nav-background: var(--theme-colour-background, #fff); --nav-background: var(--theme-colour-background, #fff);
--nav-primary: var(--theme-colour-text-primary, #404040); --nav-primary: var(--theme-colour-text-primary, #404040);
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0); --nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
--theme-font-family-sans-serif: Knowledge, sans-serif; --theme-font-family-sans-serif: Knowledge, sans-serif;
`}" `}
> >
<div> <div>
{#if includeReferrals} {#if includeReferrals}
@ -61,9 +61,9 @@
/> />
</PaddingReset> </PaddingReset>
{/if} {/if}
<QuickLinks links="{data[0]}" /> <QuickLinks links={data[0]} />
<CompanyLinks links="{data[0]}" /> <CompanyLinks links={data[0]} />
<LegalLinks links="{data[0]}" /> <LegalLinks links={data[0]} />
</div> </div>
</footer> </footer>

View file

@ -20,13 +20,13 @@
<div <div
class="overlay" class="overlay"
aria-modal="true" aria-modal="true"
style="{` style={`
--nav-background: var(--theme-colour-background, #fff); --nav-background: var(--theme-colour-background, #fff);
--nav-primary: var(--theme-colour-text-primary, #404040); --nav-primary: var(--theme-colour-text-primary, #404040);
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0); --nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
--nav-accent: var(--theme-colour-brand-logo, #fa6400); --nav-accent: var(--theme-colour-brand-logo, #fa6400);
--nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgba(64,64,64,.08)); --nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgba(64,64,64,.08));
`}" `}
> >
<header class="header"> <header class="header">
<div class="logo"> <div class="logo">
@ -35,7 +35,7 @@
textColour="var(--nav-primary)" textColour="var(--nav-primary)"
/> />
</div> </div>
<button class="button close-button" onclick="{releaseMobileMenu}"> <button class="button close-button" onclick={releaseMobileMenu}>
<div class="button-container"> <div class="button-container">
<CloseIcon /> <CloseIcon />
</div> </div>
@ -43,14 +43,14 @@
</header> </header>
{#each data.sections as section} {#each data.sections as section}
<section class="section"> <section class="section">
<a class="section-link" href="{normalizeUrl(section.url)}" <a class="section-link" href={normalizeUrl(section.url)}
>{section.name}</a >{section.name}</a
> >
{#if section.children} {#if section.children}
<ul class="subsections"> <ul class="subsections">
{#each section.children as sub} {#each section.children as sub}
<li> <li>
<a class="subsection-link" href="{normalizeUrl(sub.url)}"> <a class="subsection-link" href={normalizeUrl(sub.url)}>
{sub.name} {sub.name}
</a> </a>
</li> </li>

View file

@ -8,7 +8,7 @@
<svg <svg
class="arrow" class="arrow"
class:rotated="{rotate}" class:rotated={rotate}
focusable="false" focusable="false"
aria-hidden="true" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -11,16 +11,16 @@
{#each sections as section} {#each sections as section}
<section <section
class="more-section-group" 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} {section.name}
</a> </a>
{#if section.children} {#if section.children}
<ul class="subsections"> <ul class="subsections">
{#each section.children as sub} {#each section.children as sub}
<li> <li>
<a class="subsection-link" href="{normalizeUrl(sub.url)}" <a class="subsection-link" href={normalizeUrl(sub.url)}
>{sub.name}</a >{sub.name}</a
> >
</li> </li>

View file

@ -13,7 +13,7 @@
</script> </script>
<NavDropdown {headingText}> <NavDropdown {headingText}>
<a href="{normalizeUrl(section.url)}"> <a href={normalizeUrl(section.url)}>
<span class="heading"> <span class="heading">
Browse {section.name} Browse {section.name}
</span> </span>
@ -23,7 +23,7 @@
<ul class="sections-group"> <ul class="sections-group">
{#each section.children.slice(0, splitCount) as sub} {#each section.children.slice(0, splitCount) as sub}
<li> <li>
<a class="subsection-link" href="{normalizeUrl(sub.url)}"> <a class="subsection-link" href={normalizeUrl(sub.url)}>
{sub.name} {sub.name}
</a> </a>
</li> </li>
@ -33,7 +33,7 @@
<ul class="sections-group"> <ul class="sections-group">
{#each section.children.slice(splitCount) as sub} {#each section.children.slice(splitCount) as sub}
<li> <li>
<a class="subsection-link" href="{normalizeUrl(sub.url)}"> <a class="subsection-link" href={normalizeUrl(sub.url)}>
{sub.name} {sub.name}
</a> </a>
</li> </li>

View file

@ -8,16 +8,16 @@
</script> </script>
<div class="story-card"> <div class="story-card">
<a href="{normalizeUrl(story.canonical_url)}"> <a href={normalizeUrl(story.canonical_url)}>
<div class="story-text" class:has-thumbnail="{thumbnail}"> <div class="story-text" class:has-thumbnail={thumbnail}>
<span>{story.title}</span> <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> </div>
{#if thumbnail} {#if thumbnail}
<div class="thumbnail"> <div class="thumbnail">
<img <img
src="{thumbnail.renditions.square['120w']}" src={thumbnail.renditions.square['120w']}
alt="{thumbnail.alt_text}" alt={thumbnail.alt_text}
/> />
</div> </div>
{/if} {/if}

View file

@ -24,7 +24,7 @@
let hiddenSections = $derived(sections.slice(displayCount)); let hiddenSections = $derived(sections.slice(displayCount));
</script> </script>
<svelte:window bind:innerWidth="{windowWidth}" /> <svelte:window bind:innerWidth={windowWidth} />
<div class="nav-bar"> <div class="nav-bar">
<nav aria-label="Main navigation"> <nav aria-label="Main navigation">
@ -35,50 +35,50 @@
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<li <li
class="nav-item category link" class="nav-item category link"
onmouseenter="{() => { onmouseenter={() => {
navTimeout = setTimeout( navTimeout = setTimeout(
() => activeSection.set(section.id), () => activeSection.set(section.id),
timeout timeout
); );
}}" }}
onfocus="{() => activeSection.set(section.id)}" onfocus={() => activeSection.set(section.id)}
onmouseleave="{() => { onmouseleave={() => {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
}}" }}
onblur="{() => { onblur={() => {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
}}" }}
onclick="{() => { onclick={() => {
if ($activeSection === section.id) { if ($activeSection === section.id) {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
} }
}}" }}
> >
<div <div
class="nav-button link" 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} {section.name}
</a> </a>
<button class="button"> <button class="button">
<DownArrow rotate="{section.id === $activeSection}" /> <DownArrow rotate={section.id === $activeSection} />
</button> </button>
</div> </div>
{#if $activeSection === section.id} {#if $activeSection === section.id}
<SectionDropdown <SectionDropdown
{section} {section}
headingText="{`Latest in ${section.name}`}" headingText={`Latest in ${section.name}`}
/> />
{/if} {/if}
</li> </li>
{:else} {:else}
<li class="nav-item category link"> <li class="nav-item category link">
<div class="nav-button link"> <div class="nav-button link">
<a href="{normalizeUrl(section.url)}"> <a href={normalizeUrl(section.url)}>
{section.name} {section.name}
</a> </a>
</div> </div>
@ -88,35 +88,35 @@
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<li <li
class="nav-item" class="nav-item"
onmouseenter="{() => { onmouseenter={() => {
navTimeout = setTimeout(() => activeSection.set('more'), timeout); navTimeout = setTimeout(() => activeSection.set('more'), timeout);
}}" }}
onfocus="{() => activeSection.set('more')}" onfocus={() => activeSection.set('more')}
onmouseleave="{() => { onmouseleave={() => {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
}}" }}
onblur="{() => { onblur={() => {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
}}" }}
onclick="{() => { onclick={() => {
if ($activeSection === 'more') { if ($activeSection === 'more') {
clearTimeout(navTimeout); clearTimeout(navTimeout);
activeSection.set(null); activeSection.set(null);
} }
}}" }}
> >
<div <div
class="nav-button more link" class="nav-button more link"
class:open="{$activeSection === 'more'}" class:open={$activeSection === 'more'}
> >
<button class="button"> <button class="button">
<span>More <DownArrow rotate="{$activeSection === 'more'}" /></span> <span>More <DownArrow rotate={$activeSection === 'more'} /></span>
</button> </button>
</div> </div>
{#if $activeSection === 'more'} {#if $activeSection === 'more'}
<MoreDropdown sections="{hiddenSections}" /> <MoreDropdown sections={hiddenSections} />
{/if} {/if}
</li> </li>
</ul> </ul>

View file

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

View file

@ -40,14 +40,14 @@
</script> </script>
<header <header
style="{` style={`
--nav-background: var(--theme-colour-background, #fff); --nav-background: var(--theme-colour-background, #fff);
--nav-primary: var(--theme-colour-text-primary, #404040); --nav-primary: var(--theme-colour-text-primary, #404040);
--nav-rules: var(--theme-colour-brand-rules, #d0d0d0); --nav-rules: var(--theme-colour-brand-rules, #d0d0d0);
--nav-accent: var(--theme-colour-brand-logo, #fa6400); --nav-accent: var(--theme-colour-brand-logo, #fa6400);
--nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgb(255 255 255 / 10%)); --nav-shadow: 0 1px 4px 2px var(--theme-colour-brand-shadow, rgb(255 255 255 / 10%));
--theme-font-family-sans-serif: Knowledge, sans-serif; --theme-font-family-sans-serif: Knowledge, sans-serif;
`}" `}
> >
<a href="#main-content" class="skip-link"> Skip to main content </a> <a href="#main-content" class="skip-link"> Skip to main content </a>
<div class="nav-container show-nav"> <div class="nav-container show-nav">
@ -76,10 +76,10 @@
class="menu-button" class="menu-button"
aria-label="Menu" aria-label="Menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="{isMobileMenuOpen}" aria-expanded={isMobileMenuOpen}
onclick="{() => { onclick={() => {
isMobileMenuOpen = !isMobileMenuOpen; isMobileMenuOpen = !isMobileMenuOpen;
}}" }}
> >
<div class="button-container"> <div class="button-container">
<MenuIcon /> <MenuIcon />
@ -95,10 +95,10 @@
<MobileMenu <MobileMenu
{isMobileMenuOpen} {isMobileMenuOpen}
releaseMobileMenu="{() => { releaseMobileMenu={() => {
isMobileMenuOpen = false; isMobileMenuOpen = false;
}}" }}
data="{data[0]}" data={data[0]}
/> />
<style lang="scss"> <style lang="scss">

View file

@ -35,7 +35,7 @@
}; };
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<SiteHeadline {...args} /> <SiteHeadline {...args} />
{/snippet} {/snippet}
@ -43,7 +43,7 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
section: 'Graphics', section: 'Graphics',
sectionUrl: 'https://graphics.reuters.com', sectionUrl: 'https://graphics.reuters.com',
hed: 'Ukraine makes surprising gains in counteroffensive', hed: 'Ukraine makes surprising gains in counteroffensive',
@ -55,15 +55,15 @@
], ],
publishTime: new Date('2021-09-12').toISOString(), publishTime: new Date('2021-09-12').toISOString(),
updateTime: new Date('2021-09-12T13:57:00').toISOString(), updateTime: new Date('2021-09-12T13:57:00').toISOString(),
}}" }}
/> />
<Story name="ArchieML" {...withStoryDocs(archieML)}> <Story name="ArchieML" {...withStoryDocs(archieML)}>
<SiteHeadline <SiteHeadline
hed="{content.Hed}" hed={content.Hed}
section="{content.Section}" section={content.Section}
sectionUrl="{content.SectionUrl}" sectionUrl={content.SectionUrl}
authors="{content.Authors.split(',')}" authors={content.Authors.split(',')}
publishTime="{content.Published}" publishTime={content.Published}
/> />
</Story> </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" class="section-title mb-0 font-subhed text-xs text-secondary font-bold uppercase whitespace-nowrap tracking-wider"
> >
{#if sectionUrl} {#if sectionUrl}
<a class="no-underline !text-secondary" href="{sectionUrl}" <a class="no-underline !text-secondary" href={sectionUrl}
>{section}</a >{section}</a
> >
{:else} {:else}

View file

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

View file

@ -32,7 +32,7 @@
<div <div
style:width="100%" style:width="100%"
style:height="{`${width + containerPadding * 2}px`}" style:height={`${width + containerPadding * 2}px`}
class="component-container flex items-center justify-center" class="component-container flex items-center justify-center"
> >
<div <div

View file

@ -51,7 +51,7 @@
</script> </script>
<nav aria-label="pagination" class="pagination fmt-4"> <nav aria-label="pagination" class="pagination fmt-4">
<button onclick="{goToPreviousPage}" disabled="{pageNumber === 1}" <button onclick={goToPreviousPage} disabled={pageNumber === 1}
><div class="icon-wrapper"> ><div class="icon-wrapper">
<LeftArrow /> <LeftArrow />
<span class="visually-hidden">Previous page</span> <span class="visually-hidden">Previous page</span>
@ -63,8 +63,8 @@
</div> </div>
</div> </div>
<button <button
onclick="{goToNextPage}" onclick={goToNextPage}
disabled="{pageNumber === Math.ceil(n / pageSize)}" disabled={pageNumber === Math.ceil(n / pageSize)}
><div class="icon-wrapper"> ><div class="icon-wrapper">
<RightArrow /> <RightArrow />
<span class="visually-hidden">Next page</span> <span class="visually-hidden">Next page</span>

View file

@ -33,10 +33,10 @@
class="select--input body-caption fpx-2" class="select--input body-caption fpx-2"
name="select--input" name="select--input"
id="select--input" id="select--input"
oninput="{input}" oninput={input}
> >
{#each options as obj} {#each options as obj}
<option value="{obj.value}">{obj.text}</option> <option value={obj.value}>{obj.text}</option>
{/each} {/each}
</select> </select>
</div> </div>

View file

@ -26,11 +26,11 @@
class="avoid-clicks" class="avoid-clicks"
> >
<path <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" 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>
<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" 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> ></path>
</svg> </svg>

View file

@ -47,7 +47,7 @@
const currencyFormat = (v: number) => '$' + v.toFixed(1); const currencyFormat = (v: number) => '$' + v.toFixed(1);
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Table {...args} /> <Table {...args} />
{/snippet} {/snippet}
@ -55,74 +55,74 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
data: homeRuns, data: homeRuns,
}}" }}
/> />
<Story <Story
name="Metadata" name="Metadata"
{...withStoryDocs(metadataDocs)} {...withStoryDocs(metadataDocs)}
args="{{ args={{
width: 'normal', width: 'normal',
data: homeRuns, data: homeRuns,
title: 'Career home run leaders', 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.', 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', notes: 'Note: As of Opening Day 2023',
source: 'Source: Baseball Reference', source: 'Source: Baseball Reference',
}}" }}
/> />
<Story <Story
name="Truncate" name="Truncate"
{...withStoryDocs(truncateDocs)} {...withStoryDocs(truncateDocs)}
args="{{ args={{
data: homeRuns, data: homeRuns,
truncated: true, truncated: true,
source: 'Source: Baseball Reference', source: 'Source: Baseball Reference',
}}" }}
/> />
<Story <Story
name="Paginate" name="Paginate"
{...withStoryDocs(paginateDocs)} {...withStoryDocs(paginateDocs)}
args="{{ args={{
data: pressFreedom, data: pressFreedom,
title: 'Press Freedom Index', title: 'Press Freedom Index',
paginated: true, paginated: true,
source: 'Source: Reporters Without Borders', source: 'Source: Reporters Without Borders',
}}" }}
/> />
<Story <Story
name="Search" name="Search"
{...withStoryDocs(searchDocs)} {...withStoryDocs(searchDocs)}
args="{{ args={{
data: pressFreedom, data: pressFreedom,
searchable: true, searchable: true,
paginated: true, paginated: true,
title: 'Press Freedom Index', title: 'Press Freedom Index',
source: 'Source: Reporters Without Borders', source: 'Source: Reporters Without Borders',
}}" }}
/> />
<Story <Story
name="Filter" name="Filter"
{...withStoryDocs(filterDocs)} {...withStoryDocs(filterDocs)}
args="{{ args={{
data: pressFreedom, data: pressFreedom,
paginated: true, paginated: true,
filterField: 'Region', filterField: 'Region',
title: 'Press Freedom Index', title: 'Press Freedom Index',
notes: 'Source: Reporters Without Borders', notes: 'Source: Reporters Without Borders',
}}" }}
/> />
<Story <Story
name="Search and filter" name="Search and filter"
{...withStoryDocs(bothDocs)} {...withStoryDocs(bothDocs)}
args="{{ args={{
data: pressFreedom, data: pressFreedom,
searchable: true, searchable: true,
filterField: 'Region', filterField: 'Region',
@ -130,13 +130,13 @@
title: 'Press Freedom Index', 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.', 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', source: 'Source: Reporters Without Borders',
}}" }}
/> />
<Story <Story
name="Sort" name="Sort"
{...withStoryDocs(sortDocs)} {...withStoryDocs(sortDocs)}
args="{{ args={{
data: pressFreedom, data: pressFreedom,
sortable: true, sortable: true,
sortField: 'Score', sortField: 'Score',
@ -145,13 +145,13 @@
title: 'Press Freedom Index', title: 'Press Freedom Index',
notes: 'Note: data as of 2018', notes: 'Note: data as of 2018',
source: 'Source: Reporters Without Borders', source: 'Source: Reporters Without Borders',
}}" }}
/> />
<Story <Story
name="Format" name="Format"
{...withStoryDocs(formatDocs)} {...withStoryDocs(formatDocs)}
args="{{ args={{
data: richestWomen, data: richestWomen,
title: 'The Richest Women in the World', title: 'The Richest Women in the World',
source: 'Source: Forbes', source: 'Source: Forbes',
@ -159,16 +159,16 @@
sortField: 'Net worth (in billions)', sortField: 'Net worth (in billions)',
sortDirection: 'descending', sortDirection: 'descending',
fieldFormatters: { 'Net worth (in billions)': currencyFormat }, fieldFormatters: { 'Net worth (in billions)': currencyFormat },
}}" }}
/> />
<Story <Story
name="Style" name="Style"
{...withStoryDocs(styleDocs)} {...withStoryDocs(styleDocs)}
args="{{ args={{
id: 'custom-table', id: 'custom-table',
data: richestWomen, data: richestWomen,
title: 'The Richest Women in the World', title: 'The Richest Women in the World',
source: 'Source: Forbes', source: 'Source: Forbes',
}}" }}
/> />

View file

@ -230,9 +230,9 @@
{#if filterList} {#if filterList}
<div class="table--header--filter"> <div class="table--header--filter">
<Select <Select
label="{filterLabel || filterField}" label={filterLabel || filterField}
options="{filterList}" options={filterList}
on:select="{handleFilterInput}" on:select={handleFilterInput}
/> />
</div> </div>
{/if} {/if}
@ -240,7 +240,7 @@
<div class="table--header--search"> <div class="table--header--search">
<SearchInput <SearchInput
bind:searchPlaceholder bind:searchPlaceholder
on:search="{handleSearchInput}" on:search={handleSearchInput}
/> />
</div> </div>
{/if} {/if}
@ -252,9 +252,7 @@
<table <table
class="w-full" class="w-full"
class:paginated class:paginated
class:truncated="{truncated && class:truncated={truncated && !showAll && data.length > truncateLength}
!showAll &&
data.length > truncateLength}"
> >
<thead class="table--thead"> <thead class="table--thead">
<tr> <tr>
@ -262,22 +260,22 @@
<th <th
scope="col" scope="col"
class="table--thead--th h4 pl-0 py-2 pr-2" class="table--thead--th h4 pl-0 py-2 pr-2"
class:sortable="{sortable && sortableFields.includes(field)}" class:sortable={sortable && sortableFields.includes(field)}
class:sort-ascending="{sortable && class:sort-ascending={sortable &&
sortField === field && sortField === field &&
sortDirection === 'ascending'}" sortDirection === 'ascending'}
class:sort-descending="{sortable && class:sort-descending={sortable &&
sortField === field && sortField === field &&
sortDirection === 'descending'}" sortDirection === 'descending'}
data-field="{field}" data-field={field}
on:click="{handleSort}" on:click={handleSort}
> >
{field} {field}
{#if sortable && sortableFields.includes(field)} {#if sortable && sortableFields.includes(field)}
<div class="table--thead--sortarrow fml-1 avoid-clicks"> <div class="table--thead--sortarrow fml-1 avoid-clicks">
<SortArrow <SortArrow
bind:sortDirection bind:sortDirection
active="{sortField === field}" active={sortField === field}
/> />
</div> </div>
{/if} {/if}
@ -287,13 +285,13 @@
</thead> </thead>
<tbody class="table--tbody"> <tbody class="table--tbody">
{#each currentPageData as item, idx} {#each currentPageData as item, idx}
<tr data-row-index="{idx}"> <tr data-row-index={idx}>
{#each includedFields as field} {#each includedFields as field}
<td <td
class="body-note pl-0 py-2 pr-2" class="body-note pl-0 py-2 pr-2"
data-row-index="{idx}" data-row-index={idx}
data-field="{field}" data-field={field}
data-value="{item[field]}" data-value={item[field]}
> >
{@html formatValue(item, field)} {@html formatValue(item, field)}
</td> </td>
@ -302,7 +300,7 @@
{/each} {/each}
{#if searchable && searchText && currentPageData.length === 0} {#if searchable && searchText && currentPageData.length === 0}
<tr> <tr>
<td class="no-results" colspan="{includedFields.length}"> <td class="no-results" colspan={includedFields.length}>
No results found for "{searchText}" No results found for "{searchText}"
</td> </td>
</tr> </tr>
@ -312,7 +310,7 @@
<tfoot class="table--tfoot"> <tfoot class="table--tfoot">
{#if notes} {#if notes}
<tr> <tr>
<td class="" colspan="{includedFields.length}"> <td class="" colspan={includedFields.length}>
<div class="fmt-2"> <div class="fmt-2">
{@html notes} {@html notes}
</div> </div>
@ -321,7 +319,7 @@
{/if} {/if}
{#if source} {#if source}
<tr> <tr>
<td class="" colspan="{includedFields.length}"> <td class="" colspan={includedFields.length}>
<div class="fmt-1"> <div class="fmt-1">
{@html source} {@html source}
</div> </div>
@ -337,7 +335,7 @@
aria-label="Show all button" aria-label="Show all button"
class="show-all flex items-center justify-center fmt-2" 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 - >{#if showAll}Show fewer rows{:else}Show {data.length -
truncateLength} more rows{/if}</button truncateLength} more rows{/if}</button
> >
@ -347,8 +345,8 @@
<Pagination <Pagination
bind:pageNumber bind:pageNumber
bind:pageSize bind:pageSize
bind:pageLength="{currentPageData.length}" bind:pageLength={currentPageData.length}
bind:n="{sortedData.length}" bind:n={sortedData.length}
/>{/if} />{/if}
</article> </article>
</Block> </Block>

View file

@ -28,7 +28,7 @@
import SharkImg from './stories/shark.jpg'; import SharkImg from './stories/shark.jpg';
</script> </script>
<Template > <Template>
{#snippet children({ ...args })} {#snippet children({ ...args })}
<TestForSvelte5 {...args} /> <TestForSvelte5 {...args} />
{/snippet} {/snippet}
@ -36,9 +36,9 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
src: SharkImg, src: SharkImg,
altText: "Duh dum! It's a shark!!", altText: "Duh dum! It's a shark!!",
}}" }}
/> />

View file

@ -36,8 +36,8 @@
<Block {width} {id} class="photo {cls}"> <Block {width} {id} class="photo {cls}">
<div <div
style:background-image="{`url(${src})`}" style:background-image={`url(${src})`}
style:height="{`${height}px`}" style:height={`${height}px`}
></div> ></div>
<p class="visually-hidden">{altText}</p> <p class="visually-hidden">{altText}</p>
</Block> </Block>

View file

@ -40,7 +40,7 @@
import Headline from './../Headline/Headline.svelte'; import Headline from './../Headline/Headline.svelte';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<div class="reset-article"> <div class="reset-article">
<Theme {...args}> <Theme {...args}>
@ -52,19 +52,19 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
theme: themes.light, theme: themes.light,
base: 'light', base: 'light',
}}" }}
/> />
<Story name="Custom theme" {...withStoryDocs(customiseDocs)}> <Story name="Custom theme" {...withStoryDocs(customiseDocs)}>
<Theme <Theme
base="dark" base="dark"
theme="{{ theme={{
colour: { accent: 'var(--tr-light-orange)' }, colour: { accent: 'var(--tr-light-orange)' },
font: { family: { hed: 'FreightText, serif' } }, font: { family: { hed: 'FreightText, serif' } },
}}" }}
> >
<ThemedPage /> <ThemedPage />
</Theme> </Theme>
@ -73,15 +73,15 @@
<Story name="Custom Google font" {...withStoryDocs(customiseFontDocs)}> <Story name="Custom Google font" {...withStoryDocs(customiseFontDocs)}>
<Theme <Theme
base="light" base="light"
theme="{{ theme={{
font: { family: { hed: 'Bebas Neue, sans-serif' } }, font: { family: { hed: 'Bebas Neue, sans-serif' } },
}}" }}
> >
<div class="gfont"> <div class="gfont">
<Headline <Headline
hed="{'Reuters Graphics Interactive'}" hed={'Reuters Graphics Interactive'}
dek="{'The beginning of a beautiful page'}" dek={'The beginning of a beautiful page'}
section="{'Global news'}" section={'Global news'}
/> />
</div> </div>
</Theme> </Theme>
@ -91,9 +91,9 @@
<div id="pattern-bg"> <div id="pattern-bg">
<Theme <Theme
base="dark" base="dark"
theme="{{ theme={{
colour: { background: 'transparent' }, colour: { background: 'transparent' },
}}" }}
> >
<SiteHeader /> <SiteHeader />
<ThemedPage /> <ThemedPage />
@ -102,22 +102,22 @@
</Story> </Story>
<Story name="Inheritance" {...withStoryDocs(inheritanceDocs)}> <Story name="Inheritance" {...withStoryDocs(inheritanceDocs)}>
<Theme theme="{themes.light}"> <Theme theme={themes.light}>
<div class="themed"> <div class="themed">
<p>Theme</p> <p>Theme</p>
<Theme theme="{themes.dark}"> <Theme theme={themes.dark}>
<div class="themed"> <div class="themed">
<p>Sub-theme</p> <p>Sub-theme</p>
<Theme theme="{themes.light}"> <Theme theme={themes.light}>
<div class="themed"> <div class="themed">
<p>Sub-sub</p> <p>Sub-sub</p>
</div> </div>
</Theme> </Theme>
<Theme <Theme
theme="{{ theme={{
colour: { background: 'steelblue', 'text-primary': '#fff' }, colour: { background: 'steelblue', 'text-primary': '#fff' },
font: { family: { note: 'FreightText, serif' } }, font: { family: { note: 'FreightText, serif' } },
}}" }}
base="dark" base="dark"
> >
<div class="themed"> <div class="themed">

View file

@ -11,23 +11,21 @@
<script lang="ts"> <script lang="ts">
import type { CustomTheme } from './@types/component'; import type { CustomTheme } from './@types/component';
type Base = 'light' | 'dark'; type Base = 'light' | 'dark';
import flatten from './utils/flatten'; import flatten from './utils/flatten';
import mergeThemes from './utils/merge'; import mergeThemes from './utils/merge';
interface Props { interface Props {
/** Custom theme object. Can be a partial theme with just /** Custom theme object. Can be a partial theme with just
* what you want to change. * what you want to change.
*/ */
theme?: CustomTheme; theme?: CustomTheme;
/** /**
* Base theme is one of `light` or `dark` and will be merged * Base theme is one of `light` or `dark` and will be merged
* with your custom theme to fill in any values you don't * with your custom theme to fill in any values you don't
* explicitly set. * explicitly set.
*/ */
base?: Base; base?: Base;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} }
@ -35,14 +33,18 @@
let { theme = {}, base = 'light', children }: Props = $props(); let { theme = {}, base = 'light', children }: Props = $props();
/** @type {Theme} */ /** @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 })) let cssVariables = $derived(
.map(([key, value]) => `--${key}: ${value};`) Object.entries(flatten({ theme: mergedTheme }))
.join(' ')); .map(([key, value]) => `--${key}: ${value};`)
.join(' ')
);
</script> </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. --> <!-- Clients can override the theme above by attaching custom properties to this element. -->
<div class="theme-client-override" style="display: contents;"> <div class="theme-client-override" style="display: contents;">
<!-- Themed content --> <!-- Themed content -->

View file

@ -7,21 +7,21 @@
<Article> <Article>
<Headline <Headline
hed="{'Reuters Graphics Interactive'}" hed={'Reuters Graphics Interactive'}
dek="{'The beginning of a beautiful page'}" dek={'The beginning of a beautiful page'}
section="{'Global news'}" section={'Global news'}
authors="{['Jon McClure', 'Prasanta Kumar Dutta']}" authors={['Jon McClure', 'Prasanta Kumar Dutta']}
publishTime="{new Date('2021-09-12').toISOString()}" publishTime={new Date('2021-09-12').toISOString()}
/> />
<BodyText <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. 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 ## Brisket sirloin
Shank strip steak turkey shoulder shankle leberkas pork chop, t-bone picanha buffalo ground round burgdoggen ribeye. Shank strip steak turkey shoulder shankle leberkas pork chop, t-bone picanha buffalo ground round burgdoggen ribeye.
`}" `}
/> />
<GraphicBlock <GraphicBlock
title="Steak tar-tar" 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> <div class="fake-graphic"></div>
</GraphicBlock> </GraphicBlock>
<BodyText <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> </Article>

View file

@ -17,7 +17,7 @@
import { Template, Story } from '@storybook/addon-svelte-csf'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<div> <div>
<ToolsHeader {...args} /> <ToolsHeader {...args} />
@ -25,7 +25,7 @@
{/snippet} {/snippet}
</Template> </Template>
<Story name="Default" args="{{}}" /> <Story name="Default" args={{}} />
<style> <style>
div { div {

View file

@ -41,13 +41,13 @@
<header <header
{id} {id}
class="{cls}" class={cls}
class:sticky class:sticky
style:background style:background
style:border-bottom="{borderBottom}" style:border-bottom={borderBottom}
> >
<div class="logo-container"> <div class="logo-container">
<a href="{homeLink}"> <a href={homeLink}>
<ReutersGraphicsLogo {...{ ...logoProps, ...{ width: '100%' } }} /> <ReutersGraphicsLogo {...{ ...logoProps, ...{ width: '100%' } }} />
</a> </a>
</div> </div>

View file

@ -29,7 +29,7 @@
</script> </script>
<button <button
onclick="{forwardBtnClick}" onclick={forwardBtnClick}
style=" style="
opacity: {controlsOpacity}; opacity: {controlsOpacity};
top: {controlsPosition === 'top left' || controlsPosition === 'top right' ? top: {controlsPosition === 'top left' || controlsPosition === 'top right' ?
@ -50,18 +50,18 @@
{#if resetCondition} {#if resetCondition}
<i class="play-pause-icon replay"> <i class="play-pause-icon replay">
{#if separateReplayIcon} {#if separateReplayIcon}
<Fa icon="{faReply}" size="2x" color="{controlsColour}" /> <Fa icon={faReply} size="2x" color={controlsColour} />
{:else} {:else}
<Fa icon="{faPlay}" size="2x" color="{controlsColour}" /> <Fa icon={faPlay} size="2x" color={controlsColour} />
{/if} {/if}
</i> </i>
{:else if paused === false} {:else if paused === false}
<i class="play-pause-icon pause"> <i class="play-pause-icon pause">
<Fa icon="{faPause}" size="2x" color="{controlsColour}" /> <Fa icon={faPause} size="2x" color={controlsColour} />
</i> </i>
{:else if paused === true} {:else if paused === true}
<i class="play-pause-icon play"> <i class="play-pause-icon play">
<Fa icon="{faPlay}" size="2x" color="{controlsColour}" /> <Fa icon={faPlay} size="2x" color={controlsColour} />
</i> </i>
{:else} {:else}
error error

View file

@ -31,7 +31,7 @@
import SoundVideo from './stories/videos/sound-video.mp4'; import SoundVideo from './stories/videos/sound-video.mp4';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Video {...args} /> <Video {...args} />
{/snippet} {/snippet}
@ -39,17 +39,17 @@
<Story <Story
name="Default" name="Default"
args="{{ args={{
ariaDescription: 'Compulsory description of your video for screen readers.', ariaDescription: 'Compulsory description of your video for screen readers.',
src: SilentVideo, src: SilentVideo,
width: 'wide', width: 'wide',
notes: 'Optional caption for your video.', notes: 'Optional caption for your video.',
}}" }}
/> />
<Story <Story
name="Playing and looping" name="Playing and looping"
args="{{ args={{
ariaDescription: 'Compulsory description of your video for screen readers.', ariaDescription: 'Compulsory description of your video for screen readers.',
src: SilentVideo, src: SilentVideo,
width: 'normal', width: 'normal',
@ -57,13 +57,13 @@
notes: notes:
"World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters", "World's longest glass bridge opens to public in Vietnam. (c) 2022 Thomson Reuters",
playVideoThreshold: 0.9, playVideoThreshold: 0.9,
}}" }}
{...withStoryDocs(playAndLoopDocs)} {...withStoryDocs(playAndLoopDocs)}
/> />
<Story <Story
name="Controls" name="Controls"
args="{{ args={{
ariaDescription: 'Compulsory description of your video for screen readers.', ariaDescription: 'Compulsory description of your video for screen readers.',
src: SilentVideo, src: SilentVideo,
width: 'normal', width: 'normal',
@ -76,13 +76,13 @@
separateReplayIcon: true, separateReplayIcon: true,
loopVideo: false, loopVideo: false,
hoverToSeeControls: true, hoverToSeeControls: true,
}}" }}
{...withStoryDocs(controlsDocs)} {...withStoryDocs(controlsDocs)}
/> />
<Story <Story
name="Videos with sound" name="Videos with sound"
args="{{ args={{
ariaDescription: 'Compulsory description of your video for screen readers.', ariaDescription: 'Compulsory description of your video for screen readers.',
src: SoundVideo, src: SoundVideo,
width: 'normal', width: 'normal',
@ -94,6 +94,6 @@
muteVideo: false, muteVideo: false,
playVideoWhenInView: true, playVideoWhenInView: true,
allowSoundToAutoplay: true, allowSoundToAutoplay: true,
}}" }}
{...withStoryDocs(controlsDocs)} {...withStoryDocs(controlsDocs)}
/> />

View file

@ -165,8 +165,8 @@
</script> </script>
<svelte:window <svelte:window
on:click="{setInteractedWithDom}" on:click={setInteractedWithDom}
on:touchstart="{setInteractedWithDom}" on:touchstart={setInteractedWithDom}
/> />
<GraphicBlock <GraphicBlock
@ -179,18 +179,18 @@
> >
<div <div
role="figure" role="figure"
on:mouseover="{() => { on:mouseover={() => {
interactiveControlsOpacity = controlsOpacity; interactiveControlsOpacity = controlsOpacity;
}}" }}
on:focus="{() => { on:focus={() => {
interactiveControlsOpacity = controlsOpacity; interactiveControlsOpacity = controlsOpacity;
}}" }}
on:mouseout="{() => { on:mouseout={() => {
interactiveControlsOpacity = 0; interactiveControlsOpacity = 0;
}}" }}
on:blur="{() => { on:blur={() => {
interactiveControlsOpacity = 0; interactiveControlsOpacity = 0;
}}" }}
> >
{#if (hidden && ariaDescription) || !hidden} {#if (hidden && ariaDescription) || !hidden}
{#if ariaDescription} {#if ariaDescription}
@ -202,25 +202,25 @@
<IntersectionObserver <IntersectionObserver
{element} {element}
bind:intersecting bind:intersecting
threshold="{playVideoThreshold}" threshold={playVideoThreshold}
once="{false}" once={false}
> >
<div <div
bind:this="{element}" bind:this={element}
class="video-wrapper relative block" class="video-wrapper relative block"
aria-hidden="{hidden}" aria-hidden={hidden}
bind:clientWidth="{widthVideoContainer}" bind:clientWidth={widthVideoContainer}
bind:clientHeight="{heightVideoContainer}" bind:clientHeight={heightVideoContainer}
> >
{#if possibleToPlayPause} {#if possibleToPlayPause}
{#if showControls} {#if showControls}
<Controls <Controls
on:pausePlayEvent="{pausePlayEvent}" on:pausePlayEvent={pausePlayEvent}
{paused} {paused}
{clickedOnPauseBtn} {clickedOnPauseBtn}
controlsOpacity="{hoverToSeeControls ? controlsOpacity={hoverToSeeControls ?
interactiveControlsOpacity interactiveControlsOpacity
: controlsOpacity}" : controlsOpacity}
{controlsPosition} {controlsPosition}
{widthVideoContainer} {widthVideoContainer}
{heightVideoContainer} {heightVideoContainer}
@ -232,32 +232,32 @@
{:else} {:else}
<button <button
class="border-0 m-0 p-0 bg-transparent absolute" class="border-0 m-0 p-0 bg-transparent absolute"
on:click="{() => { on:click={() => {
if (paused === true) { if (paused === true) {
paused = false; paused = false;
} else { } else {
paused = true; paused = true;
} }
}}" }}
style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;" style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;"
></button> ></button>
{/if} {/if}
{/if} {/if}
<video <video
bind:this="{videoElement}" bind:this={videoElement}
{src} {src}
{poster} {poster}
class="pointer-events-none relative" class="pointer-events-none relative"
width="100%" width="100%"
muted="{muteVideo}" muted={muteVideo}
playsinline playsinline
preload="{preloadVideo}" preload={preloadVideo}
loop="{loopVideo}" loop={loopVideo}
bind:currentTime="{time}" bind:currentTime={time}
bind:duration bind:duration
bind:paused bind:paused
bind:clientWidth="{widthVideo}" bind:clientWidth={widthVideo}
bind:clientHeight="{heightVideo}" bind:clientHeight={heightVideo}
> >
<track kind="captions" /> <track kind="captions" />
</video> </video>
@ -267,14 +267,14 @@
<!-- Video element without Intersection observer --> <!-- Video element without Intersection observer -->
<div <div
class="video-wrapper relative" class="video-wrapper relative"
aria-hidden="{hidden}" aria-hidden={hidden}
bind:clientWidth="{widthVideoContainer}" bind:clientWidth={widthVideoContainer}
bind:clientHeight="{heightVideoContainer}" bind:clientHeight={heightVideoContainer}
> >
{#if possibleToPlayPause} {#if possibleToPlayPause}
{#if showControls} {#if showControls}
<Controls <Controls
on:pausePlayEvent="{pausePlayEvent}" on:pausePlayEvent={pausePlayEvent}
{paused} {paused}
{clickedOnPauseBtn} {clickedOnPauseBtn}
{controlsOpacity} {controlsOpacity}
@ -289,33 +289,33 @@
{:else} {:else}
<button <button
class="border-0 m-0 p-0 bg-transparent absolute" class="border-0 m-0 p-0 bg-transparent absolute"
on:click="{() => { on:click={() => {
if (paused === true) { if (paused === true) {
paused = false; paused = false;
} else { } else {
paused = true; paused = true;
} }
}}" }}
style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;" style="top: 0; left: 0; width: {widthVideoContainer}px; height: {heightVideoContainer}px;"
></button> ></button>
{/if} {/if}
{/if} {/if}
<video <video
bind:this="{videoElement}" bind:this={videoElement}
{src} {src}
{poster} {poster}
class="pointer-events-none relative" class="pointer-events-none relative"
width="100%" width="100%"
muted="{muteVideo}" muted={muteVideo}
playsinline playsinline
preload="{preloadVideo}" preload={preloadVideo}
loop="{loopVideo}" loop={loopVideo}
bind:currentTime="{time}" bind:currentTime={time}
bind:duration bind:duration
bind:paused bind:paused
autoplay autoplay
bind:clientWidth="{widthVideo}" bind:clientWidth={widthVideo}
bind:clientHeight="{heightVideo}" bind:clientHeight={heightVideo}
> >
<track kind="captions" /> <track kind="captions" />
</video> </video>

View file

@ -19,16 +19,16 @@
import { Template, Story } from '@storybook/addon-svelte-csf'; import { Template, Story } from '@storybook/addon-svelte-csf';
</script> </script>
<Template > <Template>
{#snippet children({ args })} {#snippet children({ args })}
<Visible {...args} > <Visible {...args}>
{#snippet children({ visible })} {#snippet children({ visible })}
{#if visible} {#if visible}
<p>Visible!</p> <p>Visible!</p>
{:else} {:else}
<p>Not yet visible.</p> <p>Not yet visible.</p>
{/if} {/if}
{/snippet} {/snippet}
</Visible> </Visible>
{/snippet} {/snippet}
</Template> </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) --> <!-- @component `Visible` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-utilities-visible--docs) -->
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
interface Props { interface Props {
/** /**
* Whether to change visibility just once. * Whether to change visibility just once.
* *
* Useful for loading expensive images or other media and then keeping them around once they're first loaded. * Useful for loading expensive images or other media and then keeping them around once they're first loaded.
*/ */
once?: boolean; once?: boolean;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top`. */ /** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top`. */
top?: number; top?: number;
@ -34,7 +29,7 @@
left = 0, left = 0,
right = 0, right = 0,
threshold = 0, threshold = 0,
children children,
}: Props = $props(); }: Props = $props();
let visible = $state(false); let visible = $state(false);
@ -74,7 +69,7 @@
}); });
</script> </script>
<div bind:this="{container}"> <div bind:this={container}>
<!-- An element or component --> <!-- An element or component -->
{@render children?.({ visible, })} {@render children?.({ visible })}
</div> </div>

View file

@ -2,7 +2,7 @@
import { Visible } from '@reuters-graphics/graphics-components'; import { Visible } from '@reuters-graphics/graphics-components';
</script> </script>
<Visible > <Visible>
{#snippet children({ visible })} {#snippet children({ visible })}
{#if visible} {#if visible}
<p>Visible!</p> <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'; import YourComponent from './YourComponent.svelte';
</script> </script>
<Meta title="Components/YourComponent" component="{YourComponent}" /> <Meta title="Components/YourComponent" component={YourComponent} />
<Template let:args> <Template let:args>
<YourComponent {...args} /> <YourComponent {...args} />
@ -28,9 +28,9 @@ Once you've setup the `Meta` and `Template` components as below, you can write a
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
}}" }}
/> />
``` ```
@ -47,16 +47,16 @@ You can define additional stories with _different_ args to show how your compone
<Story <Story
name="Default" name="Default"
args="{{ args={{
width: 'normal', width: 'normal',
}}" }}
/> />
<Story <Story
name="Extra wide" name="Extra wide"
args="{{ args={{
width: 'wider', 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"> <Story name="Super custom">
<YourComponent <YourComponent
width="fluid" width="fluid"
data="{[ data={[
{ id: 'UK', value: 65 }, { id: 'UK', value: 65 },
{ id: 'USA', value: 265 }, { id: 'USA', value: 265 },
]}" ]}
/> />
</Story> </Story>
``` ```

View file

@ -34,7 +34,7 @@ You can customise the controls in Storybook's built-in [controls panel](https://
}; };
</script> </script>
<Meta title="Components/YourComponent" component="{YourComponent}" {...meta} /> <Meta title="Components/YourComponent" component={YourComponent} {...meta} />
<template let:args> <template let:args>
<YourComponent {...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> </script>
<Meta title="Components/YourComponent" component="{YourComponent}" {...meta} /> <Meta title="Components/YourComponent" component={YourComponent} {...meta} />
<template let:args> <template let:args>
<YourComponent {...args} /> <YourComponent {...args} />
</template> </template>
<!-- The first story will use the component docs in Meta... --> <!-- 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. --> <!-- Additional stories can use other docs now. -->
<Story <Story
name="Another story" name="Another story"
args="{{ width: 'normal' }}" args={{ width: 'normal' }}
{...withStoryDocs(someStoryDocs)} {...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. --> <!-- Pass source in inside an object keyed by your snippet's language, e.g., svelte, scss, etc. -->
<Story <Story
name="Basic" name="Basic"
args="{{ width: 'normal' }}" args={{ width: 'normal' }}
{...withSource({ svelte: defaultSnippet })} {...withSource({ svelte: defaultSnippet })}
/> />
``` ```
@ -124,7 +124,7 @@ If you're adding source code AND custom docs to a story, you can chain `withSour
<Story <Story
name="Extra wide" name="Extra wide"
args="{{ width: 'wider' }}" args={{ width: 'wider' }}
{...withComponentDocs(componentDocs, withSource({ svelte: defaultSnippet }))} {...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'; import myImageSrc from './stories/imgs/myImage.jpg';
</script> </script>
<Meta title="Components/YourComponent" component="{YourComponent}" /> <Meta title="Components/YourComponent" component={YourComponent} />
<template let:args> <template let:args>
<YourComponent {...args} /> <YourComponent {...args} />
@ -42,9 +42,9 @@ Now, import your media file directly in your code, which will resolve to the URL
<Story <Story
name="Basic" name="Basic"
args="{{ args={{
src: myImageSrc, src: myImageSrc,
altText: 'My image in the component', 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 <ProfileCard
name="Kitty" name="Kitty"
age="{2}" age={2}
img="https://cats.com/cat1.jpg" 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." 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'} {:else if block.type === 'profile-card'}
<ProfileCard <ProfileCard
name="{block.name}" name={block.name}
age="{parseInt(block.age)}" age={parseInt(block.age)}
img="{`${assets}/${block.picture}`}" img={`${assets}/${block.picture}`}
birthday="{new Date(block.birthday)}" birthday={new Date(block.birthday)}
bio="{block.bio}" bio={block.bio}
isGood="{block.isGood === 'true'}" isGood={block.isGood === 'true'}
/> />
<!-- ... --> <!-- ... -->
{/if} {/if}
@ -136,12 +136,12 @@ Once we've identified we have the right block for our component, we need to conv
```svelte ```svelte
<ProfileCard <ProfileCard
name="{block.name}" name={block.name}
age="{parseInt(block.age)}" age={parseInt(block.age)}
img="{`${assets}/${block.picture}`}" img={`${assets}/${block.picture}`}
birthday="{new Date(block.birthday)}" birthday={new Date(block.birthday)}
bio="{block.bio}" bio={block.bio}
isGood="{block.isGood === 'true'}" isGood={block.isGood === 'true'}
/> />
``` ```
@ -191,7 +191,7 @@ Let's look at another example component:
```svelte ```svelte
<Timeline <Timeline
title="A brief history of BitCoin" title="A brief history of BitCoin"
dates="{[ dates={[
{ {
date: new Date('1992-01-01'), date: new Date('1992-01-01'),
subhed: subhed:
@ -208,7 +208,7 @@ Let's look at another example component:
subhed: 'The Winklevoss twins buy in', subhed: 'The Winklevoss twins buy in',
img: `${assets}/images/winkle-boys.jpeg`, 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> </script>
<!-- Use the assets module to prefix the path to your image. --> <!-- 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 --> <!-- HTML -->
<figure> <figure>
<img src="{imgSrc}" alt="{altText}" /> <img src={imgSrc} alt={altText} />
<figcaption>{caption}</figcaption> <figcaption>{caption}</figcaption>
</figure> </figure>

View file

@ -32,5 +32,5 @@ pnpm i @reuters-graphics/graphics-components
import { BodyText } from '@reuters-graphics/graphics-components'; import { BodyText } from '@reuters-graphics/graphics-components';
</script> </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