adds svelte 5 version of the official scroller demo

This commit is contained in:
MinamiFunakoshiTR 2025-05-22 11:28:44 -07:00
parent 217780d785
commit a44163ded4
Failed to extract signature
6 changed files with 224 additions and 95 deletions

View file

@ -10,19 +10,18 @@
let { index, steps, preload = 1, stackBackground = true }: Props = $props();
function shouldShowStep(i:number) {
function shouldShowStep(i: number) {
if (preload === 0) return true;
if (stackBackground) return i <= index;
return i >= index - preload && i <= index + preload;
}
function isStepVisible(i:number) {
function isStepVisible(i: number) {
if (stackBackground) return i <= index;
return i === index;
}
</script>
{#each steps as step, i}
{#if shouldShowStep(i)}
<div

View file

@ -6,68 +6,77 @@ import * as ScrollerBaseStories from './ScrollerBase.stories.svelte';
# ScrollerBase
The `ScrollerBase` component powers the [`Scroller` component](?path=/story/components-graphics-scroller--docs), which creates a basic storytelling graphic with preset layout options. The `ScrollerBase` component contains the bare minimum code necessary for a scrollytelling section, and allows for customisation beyond what the [`Scroller` component](?path=/story/components-graphics-scroller--docs) allows.
The `ScrollerBase` component powers the [`Scroller` component](?path=/story/components-graphics-scroller--docs), which creates a basic storytelling graphic with preset layout options. `ScrollerBase` contains the bare minimum code necessary for a scrollytelling section, and allows for customisation beyond what the [`Scroller` component](?path=/story/components-graphics-scroller--docs) allows.
`ScrollerBase` is a Svelte 5 version of the [svelte-scroller](https://github.com/sveltejs/svelte-scroller).
> **Important❗:** Make sure the HTML element containing each foreground is a div with the class `step-foreground-container`. If you're modifying this to something else, pass the appropriate selector to the `query` prop.
[Demo](?path=/story/components-graphics-scrollerbase--demo)
```svelte
<!-- App.svelte -->
<script>
<script lang="ts">
import { ScrollerBase } from '@reuters-graphics/graphics-components';
// Optional: Bind your own `index`, `progress`, and other bindable variables to use them in your code.
let myIndex = $state(0);
let myProgress = $state(0);
// Optional: Bind your own variables to use them in your code.
let count = $state(1);
let index = $state(0);
let offset = $state(0);
let progress = $state(0);
let top = $state(0.1);
let threshold = $state(0.5);
let bottom = $state(0.9);
</script>
<ScrollerBase
bind:index={myIndex}
bind:progress={myProgress}
{top}
{threshold}
{bottom}
bind:count
bind:index
bind:offset
bind:progress
query="div.step-foreground-container"
>
{#snippet backgroundSnippet()}
<!-- Add custom backgroud as a snippet -->
<div class="custom-background">
<p>
This is the background content. It will stay fixed in place while the
foreground scrolls over the top.
</p>
</div>
<!-- Add custom background HTML or component -->
<p class="mb-0">
Current step: <strong>{index + 1}/{count}</strong>
</p>
<progress class="mb-4" value={(index + 1) / count}></progress>
<p class="mb-0">Offset in current step</p>
<progress class="mb-4" value={offset}></progress>
<p class="mb-0">Total progress</p>
<progress class="mb-4" value={progress}></progress>
{/snippet}
{#snippet foregroundSnippet()}
<!-- Add custom foreground as a snippet -->
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the first section.</p>
</div>
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the second section.</p>
</div>
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the third section.</p>
</div>
<!-- Add custom foreground HTML or component -->
<div class="step-foreground-container">Step 1</div>
<div class="step-foreground-container">Step 2</div>
<div class="step-foreground-container">Step 3</div>
<div class="step-foreground-container">Step 4</div>
<div class="step-foreground-container">Step 5</div>
{/snippet}
</ScrollerBase>
```
> **Important❗:** Make sure the HTML element containing each foreground is a div with the class `step-foreground-container`. If you're modifying this to something else, pass the appropriate selector to the `query` prop.
<style lang="scss">
@use '@reuters-graphics/graphics-components/dist/scss/mixins' as mixins;
To add your own styling, you can write styles in a global SCSS stylesheet:
```scss
// global.scss
.custom-background {
padding: 20px;
border-radius: 5px;
height: 100vh;
}
.step-foreground-container {
height: 100vh;
p {
padding: 1em;
background-color: rgba(162, 220, 231, 0.5);
.scroller-demo-container {
width: mixins.$column-width-normal;
margin: auto;
}
}
.step-foreground-container {
height: 100vh;
width: 50%;
background-color: rgba(0, 0, 0, 0.2);
padding: 1em;
margin: 0 0 2em 0;
position: relative;
left: 50%;
}
</style>
```

View file

@ -1,6 +1,7 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import ScrollerBase from './ScrollerBase.svelte';
import ScrollerDemo from './demo/ScrollerDemo.svelte';
const { Story } = defineMeta({
title: 'Components/Graphics/ScrollerBase',
@ -8,51 +9,4 @@
});
</script>
<script lang="ts">
let myIndex = $state(0);
let myProgress = $state(0);
</script>
<Story name="Demo">
<ScrollerBase
bind:index={myIndex}
bind:progress={myProgress}
query="div.step-foreground-container"
>
{#snippet backgroundSnippet()}
<div class="custom-background">
<p>
This is the background content. It will stay fixed in place while the
foreground scrolls over the top.
</p>
</div>
{/snippet}
{#snippet foregroundSnippet()}
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the first section.</p>
</div>
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the second section.</p>
</div>
<div class="step-foreground-container flex items-center justify-center">
<p>Index {myIndex}: This is the third section.</p>
</div>
{/snippet}
</ScrollerBase>
</Story>
<style lang="scss">
.custom-background {
padding: 20px;
border-radius: 5px;
height: 100vh;
}
.step-foreground-container {
height: 100vh;
p {
padding: 1em;
background-color: rgba(162, 220, 231, 0.5);
}
}
</style>
<Story name="Demo"><ScrollerDemo /></Story>

View file

@ -0,0 +1,87 @@
<script lang="ts">
let { value = $bindable(0), label = 'label' } = $props();
let isDragging = false;
function drag(node, callback) {
function handleMousedown(event) {
function handleMousemove(event) {
event.preventDefault();
node.dispatchEvent(
new CustomEvent('drag', {
detail: {
value: event.clientY / window.innerHeight,
},
})
);
}
function handleMouseup(event) {
window.removeEventListener('mousemove', handleMousemove);
window.removeEventListener('mouseup', handleMouseup);
}
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('mouseup', handleMouseup);
}
node.addEventListener('mousedown', handleMousedown);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
},
};
}
// Round to 2 decimal places
function round(value) {
return Math.round(value * 100) / 100;
}
</script>
<div
class="label"
style="top: {value * 100}%"
use:drag
on:drag={(e) => (value = e.detail.value)}
>
<div class="drag-target"></div>
<hr />
<p>{label}: {round(value)}</p>
</div>
<style lang="scss">
@use '../../../scss/mixins' as mixins;
.label {
position: fixed;
top: 0;
right: 0;
width: 150px;
height: 0;
cursor: ns-resize;
.drag-target {
position: absolute;
height: 20px;
top: -10px;
}
hr {
position: absolute;
top: 0;
width: 100%;
height: 2px;
background: red;
border: none;
margin: 0;
}
p {
position: absolute;
@include mixins.font-sans;
@include mixins.font-medium;
@include mixins.text-sm;
}
}
</style>

View file

@ -0,0 +1,80 @@
<script lang="ts">
import ScrollerBase from '../ScrollerBase.svelte';
import DraggableLabel from './DraggableLabel.svelte';
import BodyText from '../../BodyText/BodyText.svelte';
let count = $state(1);
let index = $state(0);
let offset = $state(0);
let progress = $state(0);
let top = $state(0.1);
let threshold = $state(0.5);
let bottom = $state(0.9);
</script>
<BodyText
text="Read the documentation on the props `progress`, `top`, `threshold`, `bottom` under **Controls** to understand how they work."
/>
<div class="scroller-demo-container">
<ScrollerBase
{top}
{threshold}
{bottom}
bind:count
bind:index
bind:offset
bind:progress
query="div.step-foreground-container"
>
{#snippet backgroundSnippet()}
<p class="mb-0">
Current step: <strong>{index + 1}/{count}</strong>
</p>
<progress class="mb-4" value={(index + 1) / count}></progress>
<p class="mb-0">Offset in current step</p>
<progress class="mb-4" value={offset}></progress>
<p class="mb-0">Total progress</p>
<progress class="mb-4" value={progress}></progress>
{/snippet}
{#snippet foregroundSnippet()}
<div class="step-foreground-container font-medium">Step 1</div>
<div class="step-foreground-container font-medium">Step 2</div>
<div class="step-foreground-container font-medium">Step 3</div>
<div class="step-foreground-container font-medium">Step 4</div>
<div class="step-foreground-container font-medium">Step 5</div>
{/snippet}
</ScrollerBase>
</div>
<BodyText
text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"
/>
<DraggableLabel bind:value={top} label="top" />
<DraggableLabel bind:value={threshold} label="threshold" />
<DraggableLabel bind:value={bottom} label="bottom" />
<style lang="scss">
@use '../../../scss/mixins' as mixins;
.scroller-demo-container {
width: mixins.$column-width-normal;
margin: auto;
}
.step-foreground-container {
height: 100vh;
width: 50%;
background-color: rgba(0, 0, 0, 0.2);
padding: 1em;
margin: 0 0 2em 0;
// Make it align to the right
position: relative;
left: 50%;
}
</style>

View file

@ -50,5 +50,5 @@ export interface Theme {
export interface CustomTheme {
colour?: Partial<Colour>;
font?: Partial<CustomFont>;
[customProperty: string]: unknown;
[customProperty: string]: unknown;
}