Thanks Veronika
Unfortunately it’s quite a large project, but the component files are added here:
MediaAndText.svelte:
<script lang="ts">
import SectionHeader from '../../atoms/SectionHeader/SectionHeader.svelte';
import SectionText from '../../atoms/SectionText/SectionText.svelte';
import {
EMediaAndTextCopyAlignment,
EMediaAndTextMediaPositionHorizontal,
EMediaAndTextMediaPositionVertical,
EMediaAndTextVerticalAlignment,
ETitleFontSizes,
EMediaAndTextColumnCount,
EMediaAndTextLayoutType
} from '../../../config/enums.js';
import { Image } from '@builder.io/sdk-svelte';
import VideoPlayer from '../../organisms/VideoPlayer/VideoPlayer.svelte';
export let layoutType = EMediaAndTextLayoutType.MT;
export let columns = EMediaAndTextColumnCount.SINGLE;
export let fullBleed: boolean = false;
export let heading = '';
export let titleSize: string = ETitleFontSizes.H3;
export let description1 = '';
export let description2 = '';
export let stickyText: boolean = false;
export let copyAlignment = EMediaAndTextCopyAlignment.LEFT;
export let verticalAlignment = EMediaAndTextVerticalAlignment.TOP;
export let mainImage = {};
export let mainVideoSrc = '';
export let mediaPositionHorizontal = EMediaAndTextMediaPositionHorizontal.RIGHT;
export let mediaPositionVertical = EMediaAndTextMediaPositionVertical.BOTTOM;
export let secondaryImage = {};
export let secondaryVideoSrc = '';
export let backgroundColour = '';
export let txtColour = '';
function sanitizeClassName(name: string): string {
// Remove invalid characters and replace them with a hyphen
return name.replace(/[^a-zA-Z0-9-_]/g, '-');
}
const safeLayoutType: string = sanitizeClassName(layoutType);
</script>
<div class="media-and-text-wrapper" class:media-and-text-wrapper--layout-is-fullbleed={fullBleed}>
<div
class="
media-and-text
media-and-text--columns-{columns}
media-and-text--layoutType-{safeLayoutType}
media-and-text--mediaPositionHorizontal-is-{mediaPositionHorizontal}
media-and-text--mediaPositionVertical-is-{mediaPositionVertical}
media-and-text--copyAlignment-is-{copyAlignment}
media-and-text--verticalAlignment-is-{verticalAlignment}
"
class:media-and-text--layout-is-fullbleed={fullBleed}
class:media-and-text--hasSecondaryMedia={secondaryImage?.src || secondaryVideoSrc}
class:media-and-text--hasSecondaryDescription={description2}
style="--bg-colour: {backgroundColour}; --txt-colour: {txtColour};"
>
{#if layoutType !== EMediaAndTextLayoutType.MO && (heading || description1 || (columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO && description2))}
<div class="media-and-text__text" class:media-and-text__text--is-sticky={stickyText}>
{#if heading}
<div class="media-and-text__heading">
<SectionHeader {titleSize}>{heading}</SectionHeader>
</div>
{/if}
{#if description1 || (columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO && description2)}
<div class="media-and-text__descriptions">
{#if description1}
<SectionText>{@html description1}</SectionText>
{/if}
{#if columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO}
{#if description2}
<SectionText>{@html description2}</SectionText>
{/if}
{/if}
</div>
{/if}
</div>
{/if}
{#if layoutType !== EMediaAndTextLayoutType.TO}
<div class="media-and-text__media">
{#if mainImage?.src || mainVideoSrc}
<div class="media-and-text__source media-and-text__source--primary">
{#if mainVideoSrc}
<VideoPlayer src={mainVideoSrc} autoPlay={true} />
{:else}
<Image {...mainImage} />
{/if}
</div>
{/if}
{#if columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.MO}
{#if secondaryImage?.src || secondaryVideoSrc}
<div class="media-and-text__source media-and-text__source--secondary">
{#if secondaryVideoSrc}
<VideoPlayer src={secondaryVideoSrc} autoPlay={true} />
{:else}
<Image {...secondaryImage} />
{/if}
</div>
{/if}
{/if}
</div>
{/if}
</div>
</div>
<style lang="scss">
@use '/src/scss/base';
@use 'sass-mq/mq' as *;
.media-and-text-wrapper {
padding: var(--layout-pagegrid-margin);
@include mq($from: tablet) {
display: grid;
column-gap: var(--layout-pagegrid-gutter);
grid-template-columns: [first] repeat(var(--layout-pagegrid-col-count), 1fr) [end];
}
&--layout-is-fullbleed {
display: block;
padding-left: 0;
padding-right: 0;
}
}
.media-and-text {
grid-column: 2 / -2;
background-color: var(--bg-colour);
color: var(--txt-colour);
display: flex;
flex-direction: column;
gap: var(--layout-pagegrid-gutter);
&--columns-Single {
&.media-and-text--mediaPositionVertical-is-Top {
@include mq($from: tablet) {
flex-direction: column-reverse;
}
}
&.media-and-text--mediaPositionVertical-is-Bottom {
@include mq($from: tablet) {
flex-direction: column;
}
}
}
&--columns-Double {
&.media-and-text--mediaPositionHorizontal-is-Right {
@include mq($from: tablet) {
flex-direction: row;
}
}
&.media-and-text--mediaPositionHorizontal-is-Left {
@include mq($from: tablet) {
flex-direction: row-reverse;
}
}
&.media-and-text--verticalAlignment-is-Top {
@include mq($from: tablet) {
align-items: start;
}
}
&.media-and-text--verticalAlignment-is-Middle {
@include mq($from: tablet) {
align-items: center;
}
}
&.media-and-text--verticalAlignment-is-Bottom {
@include mq($from: tablet) {
align-items: flex-end;
}
}
}
&--copyAlignment-is-Left {
}
&--copyAlignment-is-Right {
@include mq($from: tablet) {
text-align: right;
}
}
&--copyAlignment-is-Center {
@include mq($from: tablet) {
text-align: center;
}
}
&__text {
&--is-sticky {
@include mq($from: desktop) {
position: sticky;
top: 10rem;
}
}
.media-and-text--columns-Double.media-and-text--layoutType-Media-and-Text.media-and-text--hasSecondaryDescription
& {
@include mq($from: tablet) {
flex: 0 0 50%;
}
}
}
&__heading {
}
&__descriptions {
.media-and-text--columns-Single & {
@include mq($from: tablet) {
width: 80%;
}
}
.media-and-text--columns-Double.media-and-text--layoutType-Text-Only.media-and-text--hasSecondaryDescription
& {
@include mq($from: tablet) {
display: flex;
flex-direction: row;
gap: 2rem;
}
}
}
&__media {
display: grid;
gap: var(--layout-pagegrid-gutter);
width: 100%;
:global(picture),
:global(img) {
width: 100%;
}
.media-and-text--columns-Double.media-and-text--layoutType-Media-Only.media-and-text--hasSecondaryMedia
& {
@include mq($from: tablet) {
grid-template-columns: 1fr 1fr;
}
}
}
}
.media-and-text :global(.section-header) {
line-height: 1;
margin-bottom: 1.6rem;
margin-top: -0.7rem;
@include mq($from: tablet) {
margin-bottom: 3rem;
}
}
.media-and-text :global(.section-text) {
font-size: 1.4rem;
line-height: 20px;
@include mq($from: tablet) {
font-size: 1.6rem;
}
}
.media-and-text--columns-Double.media-and-text--layoutType-Text-Only.media-and-text--hasSecondaryDescription
:global(.section-text) {
@include mq($from: tablet) {
flex: 0 0 calc(50% - var(--layout-pagegrid-gutter) / 2);
}
}
</style>
MediaAndText.builder.js:
import MediaAndText from './MediaAndText.svelte';
import {
EMediaAndTextCopyAlignment,
EMediaAndTextMediaPositionHorizontal,
EMediaAndTextMediaPositionVertical,
EMediaAndTextVerticalAlignment,
ETitleFontSizes,
EMediaAndTextColumnCount,
EMediaAndTextLayoutType
} from '../../../config/enums.js';
import { DOCS_URL } from '../../../config/constants.js';
export default {
component: MediaAndText,
name: 'Media and Text',
description: 'Allows images/videos, heading and body copy.',
docsLink: DOCS_URL + '/storybook/?path=/docs/molecules-MediaAndText--default',
image: 'https://unpkg.com/@tabler/icons@3.5.0/icons/outline/layout-2.svg',
screenshot:
'https://cdn.builder.io/api/v1/image/assets%2F5d0546160e954b87b82e2663451afcb1%2Fbad684738e154368939c8a6273d3cf6a',
inputs: [
{
friendlyName: 'Layout Type',
name: 'layoutType',
type: 'text',
enum: Object.values(EMediaAndTextLayoutType),
defaultValue: EMediaAndTextLayoutType.MT
},
{
friendlyName: 'Layout Columns',
name: 'columns',
type: 'text',
enum: Object.values(EMediaAndTextColumnCount),
defaultValue: EMediaAndTextColumnCount.SINGLE
},
{
// This should only be for layoutType Media Only
friendlyName: 'Full Bleed/Edge-to-Edge?',
name: 'fullBleed',
type: 'boolean',
defaultValue: false,
helperText: 'Removes side padding/margins.',
showIf: `options.get('layoutType') === 'Media Only'`
},
{
// This should only be for layoutType that has text
friendlyName: 'Heading',
name: 'heading',
type: 'text',
showIf: `options.get('layoutType') !== 'Media Only'`
},
{
// This should only be for layoutType that has text
friendlyName: 'Heading Font Size',
name: 'titleSize',
type: 'text',
enum: Object.values(ETitleFontSizes),
defaultValue: ETitleFontSizes.H3,
showIf: `options.get('layoutType') !== 'Media Only' && options.get('heading') !== ''`
},
{
// This should only be for layoutType that has text
friendlyName: 'Description 1',
name: 'description1',
type: 'richText',
helperText: 'Please use formating sparingly.',
showIf: `options.get('layoutType') !== 'Media Only'`
},
{
// This should only be for layoutType Text Only
// This should only be for two column layouts
friendlyName: 'Description 2',
name: 'description2',
type: 'richText',
helperText: 'Please use formating sparingly.',
showIf: `options.get('layoutType') === 'Text Only' && options.get('columns') === 'Double'`
},
{
// This should only be for layoutType Media and Text
// This should only be for two column layouts
friendlyName: 'Sticky Text on Desktop?',
name: 'stickyText',
type: 'boolean',
defaultValue: false,
showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
},
{
// This should only be for layoutType that has text
friendlyName: 'Horizontal Text Alignment',
name: 'copyAlignment',
type: 'text',
enum: Object.values(EMediaAndTextCopyAlignment),
defaultValue: EMediaAndTextCopyAlignment.LEFT,
showIf: `options.get('layoutType') !== 'Media Only'`
},
{
// This should only be for layoutType Media and Text
// This should only be for two column layouts
friendlyName: 'Vertical Text Alignment',
name: 'verticalAlignment',
type: 'text',
enum: Object.values(EMediaAndTextVerticalAlignment),
defaultValue: EMediaAndTextVerticalAlignment.MIDDLE,
showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
},
{
// This should only be for layout type with media
friendlyName: 'Main Image',
name: 'mainImage',
type: 'object',
helperText: 'Any Aspect Ratio. Uploading a video will replace this image.',
showIf: `options.get('layoutType') !== 'Text Only'`,
subFields: [
{
friendlyName: 'Source',
name: 'src',
type: 'file',
allowedFileTypes: ['jpg', 'webm', 'gif', 'png']
},
{
friendlyName: 'Alternate Text',
name: 'alt',
type: 'text'
}
]
},
{
// This should only be for layout type with media
friendlyName: 'Main Video',
helperText: 'Any aspect ratio. This overrides the main image.',
name: 'mainVideoSrc',
type: 'file',
allowedFileTypes: ['mov', 'webm', 'mp4'],
showIf: `options.get('layoutType') !== 'Text Only'`
},
{
// This should only be for layoutType Media and Text
// This should only be for two column layouts
friendlyName: 'Main Media Horizontal Alignment',
name: 'mediaPositionHorizontal',
type: 'text',
helperText: 'Applies to tablet and larger viewports only',
enum: Object.values(EMediaAndTextMediaPositionHorizontal),
defaultValue: EMediaAndTextMediaPositionHorizontal.RIGHT,
showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
},
{
// This should only be for layoutType Media and Text
// This should only be for one column layouts
friendlyName: 'Main Media Vertical Alignment',
name: 'mediaPositionVertical',
type: 'text',
helperText: 'Applies to tablet and larger viewports only',
enum: Object.values(EMediaAndTextMediaPositionVertical),
defaultValue: EMediaAndTextMediaPositionVertical.BOTTOM,
showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Single'`
},
{
// This should only be for layout type with media ONLY
// This should only be for two column layouts
friendlyName: 'Secondary Image',
name: 'secondaryImage',
type: 'object',
helperText: 'Any Aspect Ratio. Uploading a video will replace this image.',
showIf: `options.get('layoutType') === 'Media Only' && options.get('columns') === 'Double'`,
subFields: [
{
friendlyName: 'Source',
name: 'src',
type: 'file',
allowedFileTypes: ['jpg', 'webm', 'gif', 'png']
},
{
friendlyName: 'Alternate Text',
name: 'alt',
type: 'text'
}
]
},
{
// This should only be for layout type with media ONLY
// This should only be for two column layouts
friendlyName: 'Secondary Video',
helperText: 'Any aspect ratio. This overrides the secondary image.',
name: 'secondaryVideoSrc',
type: 'file',
allowedFileTypes: ['mov', 'webm', 'mp4'],
showIf: `options.get('layoutType') === 'Media Only' && options.get('columns') === 'Double'`
},
{
friendlyName: 'Background Colour',
name: 'backgroundColour',
type: 'color'
},
{
// This should only be for layout type with text
friendlyName: 'Text Colour',
name: 'txtColour',
type: 'color',
showIf: `options.get('layoutType') !== 'Media Only'`
}
]
};