Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
prototype(Neos.Presentation:Molecule.Carousel.Card) < prototype(Neos.Fusion:Component) {

headline = ''
text = ''
link = ''

renderer = afx`
<div class="flex flex-col justify-between items-start p-8 rounded-xl shadow-lg">
<Neos.Presentation:Headline text={props.headline} tagName="h3" display="title-lg"/>
<Neos.Presentation:Paragraph text={props.text}/>
<Neos.Presentation:Link text={props.link}/>
</div>
`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
prototype(Neos.Presentation:Carousel) < prototype(Neos.Fusion:Component) {

@styleguide {
title = 'Carousel'
props {
headline = 'A nice looking carousel!'
cards = Neos.Fusion:DataStructure {
0 {
headline = "Card One Headline"
text = "Card One Text"
link = "Card One Link"
}
1 {
headline = "Card Two Headline"
text = "Card Two Text"
link = "Card Two Link"
}
2 {
headline = "Card Three Headline"
text = "Card Three Text"
link = "Card Three Link"
}
3 {
headline = "Card Four Headline"
text = "Card Four Text"
link = "Card Four Link"
}
}
}
}

@propTypes {
headline = PropTypes:String
cards = PropTypes:Array {
type = PropTypes:DataStructure {
headline = PropTypes:String
text = PropTypes:String
link = PropTypes:String
}
}
}

@private {
cards = Neos.Fusion:Map {
items = ${props.cards}
itemRenderer = afx`
<Neos.Presentation:Molecule.Carousel.Card {...item} />
`
}
}

options = Neos.Fusion:DataStructure {
perPage = 3
perMove = 1
}

renderer = afx`
<div class="grid grid-cols-4 gap-4">
<div class="flex flex-col col-span-1 p-8 rounded-xl shadow-lg h-full">
<Neos.Presentation:Headline text={props.headline} display="headline-lg"/>
</div>
<Neos.Presentation:Slider {...props} items={private.cards} class={"col-span-3"} />
</div>
`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
prototype(Neos.Presentation:Slider.Fragment.Item) < prototype(Neos.Fusion:Component) {
videoUri = null
youtubeId = null
vimdeoId = null
content = null
class = 'flex flex-col items-center justify-center'
renderer = afx`
<li
data-splide-html-video={props.videoUri}
data-splide-youtube={props.youtubeId ? 'https://www.youtube.com/watch?v=' + props.youtubeId : null}
data-splide-vimeo={props.vimdeoId ? 'https://vimeo.com/' + props.vimdeoId : null}
class={Array.push("splide__slide", props.class)}
>
{props.content}
</li>
`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
prototype(Neos.Presentation:Slider) < prototype(Neos.Fusion:Component) {
// This is used for the living styleguide (Monocle)
// Read more about this in the README.md
@styleguide.props.items = Neos.Fusion:Map {
items = ${Array.range(1, 10)}
itemRenderer = afx`
<img class="w-full" src={"https://picsum.photos/800/400?random=" + item} alt="placeholder image" />
`
}

tagName = 'section'
sliderIsDecoration = false
class = null
slideItemClass = 'flex flex-col items-center justify-center'

options = Neos.Fusion:DataStructure {
# The gap between slides. The CSS format is acceptable, such as 1em.
gap = 12
}

attributes = Neos.Fusion:DataStructure

i18n = Neos.Fusion:Map {
items = ${['prev', 'next', 'first', 'last', 'slideX', 'pageX', 'play', 'pause', 'carousel', 'select', 'slide', 'slideLabel', 'playVideo']}
keyRenderer = ${item}
itemRenderer = ${I18n.translate('Neos.Presentation:Main:splide.' + item)}
}

_hasItems = ${Type.isArray(this.items) && Array.length(this.items)}
@if.hasItemsOrContent = ${this._hasItems || this.content}

renderer = Neos.Fusion:Tag {
tagName = ${props.tagName}
attributes {
x-data = 'slider'
data-splide = ${Json.stringify(Array.concat({i18n:props.i18n}, props.options))}
aria-label = ${props.label}
role = ${props.sliderIsDecoration ? 'group' : null}
class = ${Array.push('splide', props.class)}
@apply.attributes = ${props.attributes}
}
content = afx`
<div class="splide__track">
<ul class="splide__list" @if={props._hasItems && !props.content}>
<Neos.Fusion:Loop items={props.items}>
<Neos.Presentation:Slider.Fragment.Item class={props.slideItemClass} content={item} />
</Neos.Fusion:Loop>
</ul>
<!-- content is used as entry for contentcollections -->
{props.content}
</div>
`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Alpine from 'alpinejs';
import Splide from '@splidejs/splide';
import { Video } from '@splidejs/splide-extension-video';

function getFirstNode(nodeList) {
return [...nodeList].filter((node) => node.tagName === 'LI')[0];
}

function getIndexOfElement(element) {
return Array.from(element.parentElement.children).indexOf(element);
}

Alpine.data('slider', () => ({
init() {
const rootElement = this.$root;
const splide = new Splide(rootElement);
const inNeosBackend = window.name === 'neos-content-main';

// We are in the backend, so we need to refresh the instance on change
if (inNeosBackend) {
splide.on('mounted', function () {
// Update if a slide is added or removed
const observeTarget = rootElement.querySelector('.splide__list');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const addedNode = getFirstNode(mutation.addedNodes);
const removedNode = getFirstNode(mutation.removedNodes);
if (addedNode || removedNode) {
console.log('Refreshing instance');
splide.refresh();
}
if (addedNode) {
// Scroll to the new slide
splide.go(getIndexOfElement(addedNode));
}
});
});
observer.observe(observeTarget, { childList: true });

// Go to the slide if it gets selceted in the node tree
document.addEventListener('Neos.NodeSelected', (event) => {
const element = event.detail.element;
if (!element.classList.contains('splide__slide')) {
return;
}
splide.go(getIndexOfElement(element));
});
});
}

splide.mount({ Video });
// Disable the play button in the backend
splide.Components.Video.disable(inNeosBackend);
const maxIndex = splide.length - 1;
splide.on('autoplay:playing', (rate) => {
// Go to the first slide after the last slide
if (rate === 1 && maxIndex === splide.index) {
splide.go(0);
}
});
},
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@import "@splidejs/splide/dist/css/splide-core.min.css";
@import "@splidejs/splide/dist/css/themes/splide-default.min.css";
@import "@splidejs/splide-extension-video/dist/css/splide-extension-video.min.css";

.splide__pagination__page.is-active {
background: rgb(50, 50, 50);
}

.splide__slide {
& > figure {
width: 100%;
margin: 0;

& > img {
width: 100%;
height: auto;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import collapse from '@alpinejs/collapse';
import clipboard from '@ryangjchandler/alpine-clipboard';
import typewriter from '@marcreichel/alpine-typewriter';
import '../Molecule/LogoBar/LogoBar';
import '../Molecule/Slider/Slider';

// @ts-ignore
Alpine.plugin([anchor, clipboard, collapse, focus, intersect, typewriter]);
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"@floating-ui/dom": "^1.6.3",
"@marcreichel/alpine-typewriter": "^1.2.0",
"@ryangjchandler/alpine-clipboard": "^2.3.0",
"@splidejs/splide": "^4.1.4",
"@splidejs/splide-extension-video": "^0.8.0",
"alpinejs": "^3.13.5"
}
}