diff --git a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml index de82e45f..e26b2a41 100644 --- a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml @@ -20,7 +20,6 @@ Neos.NeosIo:Content.ImageGrid: group: content isMonochrome: type: boolean - defaultValue: false ui: reloadIfChanged: true label: 'Monochrome' diff --git a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Stage.yaml b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Stage.yaml index efd9bc0e..3dd8b23d 100644 --- a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Stage.yaml +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Stage.yaml @@ -33,6 +33,7 @@ 'Neos.NeosIo.FeatureList:FeatureList': true 'Neos.NeosIo.CaseStudies:Content.CaseList': true 'Neos.NeosIo:PostArchive': true + 'Neos.NeosIo:Tabs': true ui: label: Stage icon: icon-tasks diff --git a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml new file mode 100644 index 00000000..f74ee168 --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml @@ -0,0 +1,18 @@ +'Neos.NeosIo:Tabs.Item': + superTypes: + 'Neos.Neos:Content': true + 'Neos.Neos:ContentCollection': true + 'Neos.NeosIo:ContentInspectorGroupMixin': true + ui: + label: 'Tab' + icon: icon-square-o + position: 200 + properties: + title: + type: string + ui: + reloadIfChanged: true + label: 'Tab Title' + showInCreationDialog: true + inspector: + group: 'default' diff --git a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.yaml b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.yaml new file mode 100644 index 00000000..2533877e --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.yaml @@ -0,0 +1,19 @@ +'Neos.NeosIo:Tabs': + superTypes: + 'Neos.Neos:Content': true + 'Neos.Neos:ContentCollection': true + ui: + label: 'Tabs' + icon: icon-layer-group + position: 400 + constraints: + nodeTypes: + '*': false + 'Neos.NeosIo:Tabs.Item': true + options: + # `internezzo/childreload` + reloadIfChildChanged: true + template: + childNodes: + itemNode1: + type: 'Neos.NeosIo:Tabs.Item' diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.Item.fusion b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.Item.fusion new file mode 100644 index 00000000..b1c729cd --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.Item.fusion @@ -0,0 +1,11 @@ +prototype(Neos.NeosIo:Tabs.Item) < prototype(Neos.Neos:ContentComponent) { + content = Neos.Neos:ContentCollection { + nodePath = '.' + } + + renderer = afx` +
+ {props.content} +
+ ` +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion new file mode 100644 index 00000000..d09b499f --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion @@ -0,0 +1,25 @@ +prototype(Neos.NeosIo:Tabs) < prototype(Neos.Neos:ContentComponent) { + renderer = afx` +
+
+ + + +
+
+ +
+
+ ` +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss new file mode 100644 index 00000000..253070f4 --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss @@ -0,0 +1,138 @@ +.tabs { + position: relative; + overflow-x: auto; + overflow-y: hidden; + height: 48px; + width: 100%; + margin: 0 auto; + white-space: nowrap; + + .tab { + display: inline-block; + text-align: center; + line-height: 48px; + height: 48px; + flex-shrink: 0; + padding: 0; + margin: 0; + background: none; + appearance: none; + border: none; + cursor: pointer; + + &:focus, + &:focus.active { + outline: none; + } + + &:hover, + &.active { + background-color: transparent; + } + + .tab-link__inner { + padding: 0 24px; + height: 100%; + } + + font-size: 14px; + text-overflow: ellipsis; + overflow: hidden; + transition: color .28s ease, background-color .28s ease; + + &.disabled a, + &.disabled a:hover { + cursor: default; + } + } +} + +.tabs { + display: flex; + justify-content: center; + padding: 0; +} + +.tabs .indicator { + display: none; +} + +.tabs-content__wrapper { + position: relative; + padding-top: 48px; + + &::after { + bottom: -1px; + content: ""; + width: 16px; + border-top: 1px solid #e7e7e8; + position: absolute; + right: 12px; + transform-origin: right top; + transform: rotateZ(135deg); + } +} + +.tabs-content { + display: flex; + flex-direction: column; + gap: 20px; + padding-left: 15px; + padding-right: 15px; + padding-bottom: 15px; +} + +.tab-item:not([hidden]){ + display: inline-block; + width: 100%; +} + +.tabs { + .tab { + @include clippedForm; + + position: relative; + color: $headingsColor; + + .tab-link__inner { + pointer-events: none; + display: grid; + font-weight: bold; + font-family: $brand-font-family; + + &:empty::after { + content: "Tab title"; + color: #ccc; + } + } + + &[aria-selected="true"] { + .tab-link__inner { + background-color: brand('primary'); + } + + color: #fff; + } + + &:focus, &:active, &:focus[aria-selected="true"] { + .tab-link__inner { + background-color: brand('primary'); + } + } + + &:focus, + &:active, + &[aria-selected="true"] { + &:before { + content: ""; + width: 18px; + border-top: 1px solid #e7e7e8; + position: absolute; + left: -7px; + transform-origin: right top; + transform: rotateZ(-45deg); + z-index: 1; + } + } + } +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss index 8c8de7fc..29a70e72 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss @@ -1 +1,4 @@ @import "../Scss/Main"; + +// TODO HACKY, as in: they dont follow the Atomic Design Structure +@import "./Content/Tabs/Tabs"; diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/Tabs.js b/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/Tabs.js new file mode 100644 index 00000000..66d53fef --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/Tabs.js @@ -0,0 +1,54 @@ +import BaseComponent from "./BaseComponent"; + +/** + * Adapted code from https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tab_role#example + */ +export default class Tabs extends BaseComponent { + constructor(el) { + super(el); + + const tabwrapper = this.el; + + const tabList = tabwrapper.querySelector('[role="tablist"]'); + const tabs = tabwrapper.querySelectorAll('[role="tab"]'); + const tabPanels = tabwrapper.querySelectorAll('[role="tabpanel"]'); + tabPanels.forEach((panel, index) => index !== 0 && panel.toggleAttribute("hidden", true)); + + // Add a click event handler to each tab + tabs.forEach((tab) => { + tab.addEventListener("click", () => { + tabs.forEach((t) => t.setAttribute("aria-selected", t !== tab ? 'false' : 'true')); + + const controls = tab.getAttribute("aria-controls"); + tabPanels.forEach((panel) => panel.toggleAttribute("hidden", panel.id !== controls)); + }); + }); + + // Enable arrow navigation between tabs in the tab list + + let tabFocus = 0; + tabList.addEventListener("keydown", (e) => { + // Move right + if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { + tabs[tabFocus].setAttribute("tabindex", -1); + if (e.key === 'ArrowRight') { + tabFocus++; + // If we're at the end, go to the start + if (tabFocus >= tabs.length) { + tabFocus = 0; + } + // Move left + } else if (e.key === 'ArrowLeft') { + tabFocus--; + // If we're at the start, move to the end + if (tabFocus < 0) { + tabFocus = tabs.length - 1; + } + } + + tabs[tabFocus].setAttribute("tabindex", 0); + tabs[tabFocus].focus(); + } + }); + } +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/index.js b/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/index.js index d73ba649..29234e3c 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/index.js +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/index.js @@ -7,6 +7,7 @@ import ScrollTo from './ScrollTo'; import ProgressiveImage from './ProgressiveImage'; import ScrollClassToggler from './ScrollClassToggler'; import SentenceSwitcher from './SentenceSwitcher'; +import Tabs from './Tabs'; export { ClassToggler, @@ -16,5 +17,6 @@ export { SentenceSwitcher, ProgressiveImage, ScrollClassToggler, - EmptyClickHandler + EmptyClickHandler, + Tabs }; diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Atoms/_Button.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Atoms/_Button.scss index c66cc1f0..3e7f8e24 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Atoms/_Button.scss +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Atoms/_Button.scss @@ -83,11 +83,7 @@ $btn-horizontal-padding: 40px; @mixin btnClippedBorder() { .btn__content { - $clip-path: polygon(var(--btn-clip-width) 0px, 0 var(--btn-clip-height), 0 100%, calc(100% - var(--btn-clip-width)) 100%, 100% calc(100% - var(--btn-clip-height)), 100% 0); - - // Autoprefixer doesn't add the required webkit prefix for some reason - -webkit-clip-path: #{$clip-path}; - clip-path: #{$clip-path}; + @include clippedForm; } } diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss index bc6c01f3..8af6fc20 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss @@ -44,3 +44,7 @@ } } + +.image-grid__greyscale figure { + filter: grayscale(1); +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/_Framework/_Mixins.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/_Framework/_Mixins.scss index 3ebc025e..c1e5456c 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/_Framework/_Mixins.scss +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/_Framework/_Mixins.scss @@ -87,3 +87,13 @@ } } } + +@mixin clippedForm() { + $btn-clip-width: 14px; + $btn-clip-height: 10px; + $clip-path: polygon($btn-clip-width 0px, 0 $btn-clip-height, 0 100%, calc(100% - #{$btn-clip-width}) 100%, 100% calc(100% - #{$btn-clip-height}), 100% 0); + + // Autoprefixer doesn't add the required webkit prefix for some reason + -webkit-clip-path: #{$clip-path}; + clip-path: #{$clip-path}; +} diff --git a/composer.json b/composer.json index 0274eb04..8571f3ad 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,8 @@ "ttree/outofbandrendering": "dev-task/newer-neos-versions as 4.0.0", "cweagans/composer-patches": "^1.7", - "jcupitt/vips": "^1.0" + "jcupitt/vips": "^1.0", + "internezzo/childreload": "^1.0" }, "require-dev": { "roave/security-advisories": "dev-latest", diff --git a/composer.lock b/composer.lock index fe5b2972..728323f3 100644 --- a/composer.lock +++ b/composer.lock @@ -4083,6 +4083,44 @@ }, "time": "2023-06-07T14:49:52+00:00" }, + { + "name": "internezzo/childreload", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/internezzo/neos-childreload.git", + "reference": "cdc6bd7cc768aa3d7a3d04a371fe34d7118ffc8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/internezzo/neos-childreload/zipball/cdc6bd7cc768aa3d7a3d04a371fe34d7118ffc8a", + "reference": "cdc6bd7cc768aa3d7a3d04a371fe34d7118ffc8a", + "shasum": "" + }, + "require": { + "neos/neos": ">=3.3" + }, + "type": "neos-package", + "extra": { + "neos": { + "package-key": "Internezzo.ChildReload" + } + }, + "autoload": { + "psr-4": { + "Internezzo\\ChildReload\\": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "description": "Neos CMS package that helps to reload the page on change of any of the child nodes of the specific nodetype", + "support": { + "source": "https://github.com/internezzo/neos-childreload/tree/1.0.2" + }, + "time": "2019-07-26T09:47:31+00:00" + }, { "name": "jcupitt/vips", "version": "v1.0.10",