Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebranding - Carousel Component #574

Open
wants to merge 3 commits into
base: rebrand
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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>
`
}
@@ -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>
`
}
@@ -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>
`
}
@@ -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>
`
}
}
@@ -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);
}
});
},
}));
@@ -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;
}
}
}
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
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"
}
}