Merge pull request #307 from reuters-graphics/feature/headpile

Feature/headpile
This commit is contained in:
Jon McClure 2025-05-28 09:40:55 +01:00 committed by GitHub
commit a0ff39be8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 310 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
'@reuters-graphics/graphics-components': patch
---
Adds new Headpile component.

View file

@ -0,0 +1,37 @@
import { Meta, Canvas } from '@storybook/blocks';
import * as HeadpileStories from './Headpile.stories.svelte';
<Meta of={HeadpileStories} />
# Headpile
The `Headpile` component is a headshot-bulleted list of people, identifying them with their names, roles and a short description of their significance to a story.
It's designed to be used with headshots that have had their background removed, which can be done in the [Preview app](https://support.apple.com/en-gb/guide/preview/prvw15636/mac?#apd320b3b1b750a4) on macOS.
```svelte
<script>
import { Headpile } from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths'; // 👈 If using in the graphics kit...
</script>
<Headpile
figures={[
{
img: `${assets}/images/person-A.jpg`,
name: 'General Abdel Fattah al-Burhan',
role: "Sudan's Sovereign Council Chief and military commander",
text: 'Burhan was little known in public life until taking part in the coup ...',
},
{
img: `${assets}/images/person-B.jpg`,
name: 'General Mohamed Hamdan Dagalo',
role: 'Leader of the Sudanese paramilitary Rapid Support Forces (RSF)',
text: 'Popularly known as Hemedti, Dagalo rose from lowly beginnings ...',
},
]}
/>
```
<Canvas of={HeadpileStories.Demo} />

View file

@ -0,0 +1,37 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Headpile from './Headpile.svelte';
import hed1 from './images/abdel.png';
import hed2 from './images/hemedti.png';
const { Story } = defineMeta({
title: 'Components/Text elements/Headpile',
component: Headpile,
argTypes: {},
});
const defaultArgs = [
{
name: 'General Abdel Fattah al-Burhan',
role: "Sudan's Sovereign Council Chief and military commander",
img: hed1,
text: 'Burhan was little known in public life until taking part in the coup against Bashir in 2019 after a popular uprising against his rule. In August 2019, his role as de facto head of state was affirmed when he became head of the Sovereign Council, a body comprising civilian and military leaders formed to oversee the transition towards elections.',
// colour: '#957caa',
},
{
name: 'General Mohamed Hamdan Dagalo',
role: 'Leader of the Sudanese paramilitary Rapid Support Forces (RSF)',
img: hed2,
text: "Popularly known as Hemedti, Dagalo rose from lowly beginnings as a camel trader to head a widely feared Arab militia that crushed a revolt in Darfur, winning him influence and eventually a role as Sudan's former deputy head of state.\r\n\r\nOver the past decade, he has been a key figure in Sudanese politics, aiding in the ousting of Bashir in 2019 and suppressing pro-democracy protests. As the country limped from one economic crisis to another, Hemedti became one of Sudans richest men, exporting gold from mines in Darfur seized by his fighters.",
colour: '#afb776',
},
];
</script>
<Story
name="Demo"
args={{
figures: defaultArgs,
}}
/>

View file

@ -0,0 +1,81 @@
<script lang="ts">
import type { ContainerWidth } from '../@types/global';
import Block from '../Block/Block.svelte';
import KeyFigure from './KeyFigure.svelte';
interface Props {
/**
* Add classes to target with custom CSS.
*/
class: string;
/**
* Add an id to target with custom CSS.
*/
id: string;
/**
* Width of the container.
*/
width: Extract<ContainerWidth, 'normal' | 'wide'>;
/**
* Default background colour to be used as a mount behind the headshot.
*/
colour: string;
/**
* Individual figures -- i.e., people -- for the headpile.
*/
figures: {
/**
* Headshot image src. Be sure to prefix the image
*
* ```typescript
* import { assets } from '$app/paths';
*
* const imgSrc = `${assets}/images/my-image.jpg`;
* ```
*/
img: string;
/**
* Figure name.
*/
name: string;
/**
* Figure role or title.
*/
role?: string;
/**
* Text describing the person.
*/
text: string;
/**
* Background colour to be used as a mount behind the headshot.
*/
colour?: string;
}[];
}
let {
figures,
class: cls,
id,
width = 'normal',
colour = '#cccccc',
}: Props = $props();
</script>
<Block class="fmy-6 {cls} {id} {width}">
<div class="figures">
{#each figures as figure}
<KeyFigure {...{ ...figure, colour: figure.colour ?? colour }} />
{/each}
</div>
</Block>
<style lang="scss">
@use '../../scss/mixins' as mixins;
div.figures {
display: flex;
flex-direction: column;
gap: 2.75rem;
}
</style>

View file

@ -0,0 +1,41 @@
<script lang="ts">
let { img = '', colour = 'var(--theme-colour-accent)' } = $props();
</script>
<div class="headshot-wrapper">
<div class="background" style="background-color: {colour};"></div>
<div class="headshot" style="background-image: url({img}); "></div>
</div>
<style lang="scss">
.headshot-wrapper {
width: 7rem;
height: 6.75rem;
position: relative;
margin-block-start: -1.75rem;
margin-block-end: -1.75rem;
border-radius: 0.25rem;
overflow: hidden;
}
.background {
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
width: 7rem;
height: 4.75rem;
display: inline-block;
border-radius: 0.25rem;
}
.headshot {
display: inline-block;
width: 100%;
height: 100%;
background-size: 106%;
background-position: center bottom;
background-repeat: no-repeat;
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,108 @@
<script lang="ts">
import { Markdown } from '@reuters-graphics/svelte-markdown';
import Headshot from './Headshot.svelte';
import { MediaQuery } from 'svelte/reactivity';
interface Props {
img: string;
name: string;
role?: string;
text: string;
colour?: string;
}
let { name, role, img, text, colour }: Props = $props();
const mobile = new MediaQuery('max-width: 600px');
</script>
<div>
<div class="wrapper-profile">
<div>
<Headshot {img} {colour} />
</div>
<div class="text">
<div class="title">{name}</div>
<div class="role">
{role || ''}
</div>
{#if !mobile.current}
<div class="description desktop">
<Markdown source={text} />
</div>
{/if}
</div>
</div>
{#if mobile.current}
<div class="description mobile">
<Markdown source={text} />
</div>
{/if}
</div>
<style lang="scss">
@use '../../scss/mixins' as mixins;
.wrapper-profile {
display: flex;
align-items: flex-start;
justify-content: start;
gap: 1rem;
width: 100%;
min-height: 5.5rem;
}
.title {
@include mixins.font-bold;
@include mixins.text-base;
@include mixins.leading-none;
@media (max-width: 450px) {
font-size: calc(0.9 * var(--theme-font-size-base, 1rem));
}
}
.role {
border-block-start: 0.5px solid var(--tr-muted-grey);
margin-inline-start: -0.75rem;
padding-inline-start: 0.75rem;
margin-block-start: 0.25rem;
padding-block-start: 0.25rem;
@include mixins.font-note;
@include mixins.text-secondary;
@include mixins.text-sm;
@include mixins.font-light;
@include mixins.leading-tighter;
@include mixins.fmb-4;
@media (max-width: 450px) {
@include mixins.text-xs;
}
}
.description {
:global(p) {
@include mixins.font-note;
font-size: calc(0.9 * var(--theme-font-size-base, 1rem));
font-weight: 300;
@include mixins.fmb-0;
text-wrap: pretty;
}
&.desktop {
display: block;
}
&.mobile {
display: none;
}
@media (max-width: 600px) {
&.desktop {
display: none;
}
&.mobile {
display: block;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

View file

@ -20,6 +20,7 @@ export { default as FeaturePhoto } from './components/FeaturePhoto/FeaturePhoto.
export { default as Framer } from './components/Framer/Framer.svelte';
export { default as GraphicBlock } from './components/GraphicBlock/GraphicBlock.svelte';
export { default as Headline } from './components/Headline/Headline.svelte';
export { default as Headpile } from './components/Headpile/Headpile.svelte';
export { default as HeroHeadline } from './components/HeroHeadline/HeroHeadline.svelte';
export { default as EndNotes } from './components/EndNotes/EndNotes.svelte';
export { default as InfoBox } from './components/InfoBox/InfoBox.svelte';