From 462da86109bf0402df6f819cd2d02f3b64c860c2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:49:16 +0200 Subject: [PATCH 1/4] FEATURE: Tabs NodeType --- .../NodeTypes/Content/Tabs.Item.yaml | 17 ++ .../Neos.NeosIo/NodeTypes/Content/Tabs.yaml | 19 +++ .../Fusion/Content/Tabs/Tabs.Item.fusion | 11 ++ .../Private/Fusion/Content/Tabs/Tabs.fusion | 26 +++ .../Private/Fusion/Content/Tabs/Tabs.scss | 161 ++++++++++++++++++ .../Resources/Private/Fusion/Main.scss | 3 + .../Private/JavaScript/Components/Tabs.js | 54 ++++++ .../Private/JavaScript/Components/index.js | 4 +- composer.json | 3 +- composer.lock | 38 +++++ 10 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml create mode 100644 DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.yaml create mode 100644 DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.Item.fusion create mode 100644 DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion create mode 100644 DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss create mode 100644 DistributionPackages/Neos.NeosIo/Resources/Private/JavaScript/Components/Tabs.js 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 000000000..63d340b44 --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml @@ -0,0 +1,17 @@ +'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' + 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 000000000..2533877e4 --- /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 000000000..b1c729cdb --- /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 000000000..e312949ba --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion @@ -0,0 +1,26 @@ +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 000000000..ef80a0336 --- /dev/null +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss @@ -0,0 +1,161 @@ +// @use "../../../../public/Frontend/scss/helpers"; +// TODO border, TODO css variables + +.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; + text-transform: uppercase; + 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; + padding: 0; +} + +.tabs .indicator { + display: none; +} + +.tabs-content__wrapper { + position: relative; + + &::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; + // @include helpers.neos-borders(0px, 12px); + border: 1px solid #e7e7e8; + padding-left: 15px; + padding-right: 15px; + padding-bottom: 15px; + background: rgba(242, 242, 242, 0.5); + border-top: none; +} + +.tab-item:not([hidden]){ + display: inline-block; + width: 100%; +} + +.tabs .tab { + &: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; + } + } +} + +.tabs .tab { + position: relative; + color: var(--c-brand); + + .tab-link__inner { + pointer-events: none; + display: grid; + + border: 1px solid transparent; + border-bottom-color: #e7e7e8; + + font-weight: 540; + // @include helpers.neos-borders(12px, 0px); + + &:empty::after { + content: "Tab title"; + color: #ccc; + } + } + + &[aria-selected="true"] { + .tab-link__inner { + background-color: rgba(242, 242, 242, 0.5); + border: 1px solid #e7e7e8; + border-bottom: none; + } + + color: var(--c-accent); + } + + &:focus, &:active, &:focus[aria-selected="true"] { + &::before { + border-top-color: var(--c-accent); + } + + .tab-link__inner { + background-color: rgba(242, 242, 242, 0.5); + border-left-color: var(--c-accent); + border-right-color: var(--c-accent); + border-top-color: var(--c-accent); + } + } +} + +.tabs .spacer { + display: flex; + flex-grow: 1; + border-bottom: 1px solid #e7e7e8; +} diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Main.scss index 8c8de7fc9..29a70e721 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 000000000..66d53fef5 --- /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 d73ba6494..29234e3cc 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/composer.json b/composer.json index 0274eb04a..8571f3ad0 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 fe5b29728..728323f33 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", From d4790a2c5eb101c76ff08a6f6700ce6c193e8ad9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:43:10 +0200 Subject: [PATCH 2/4] FEATURE: Image grid with grey overlay --- .../Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml | 1 - DistributionPackages/Neos.NeosIo/NodeTypes/Content/Stage.yaml | 1 + .../Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml | 1 + .../Resources/Private/Scss/Molecules/_ImageGrid.scss | 4 ++++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/ImageGrid.yaml index de82e45f4..e26b2a417 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 efd9bc0ef..3dd8b23dc 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 index 63d340b44..f74ee1681 100644 --- a/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml +++ b/DistributionPackages/Neos.NeosIo/NodeTypes/Content/Tabs.Item.yaml @@ -13,5 +13,6 @@ ui: reloadIfChanged: true label: 'Tab Title' + showInCreationDialog: true inspector: group: 'default' diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Molecules/_ImageGrid.scss index bc6c01f38..8af6fc208 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); +} From fc7e985637df2b93e74d93e61006db0422731513 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 25 Jul 2023 17:09:33 +0200 Subject: [PATCH 3/4] FEATURE: Make tab borders cut off --- .../Private/Fusion/Content/Tabs/Tabs.scss | 97 +++++++------------ .../Resources/Private/Scss/Atoms/_Button.scss | 6 +- .../Private/Scss/_Framework/_Mixins.scss | 10 ++ 3 files changed, 48 insertions(+), 65 deletions(-) diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss index ef80a0336..253070f4b 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.scss @@ -1,6 +1,3 @@ -// @use "../../../../public/Frontend/scss/helpers"; -// TODO border, TODO css variables - .tabs { position: relative; overflow-x: auto; @@ -18,7 +15,6 @@ flex-shrink: 0; padding: 0; margin: 0; - text-transform: uppercase; background: none; appearance: none; border: none; @@ -53,6 +49,7 @@ .tabs { display: flex; + justify-content: center; padding: 0; } @@ -62,6 +59,7 @@ .tabs-content__wrapper { position: relative; + padding-top: 48px; &::after { bottom: -1px; @@ -79,13 +77,9 @@ display: flex; flex-direction: column; gap: 20px; - // @include helpers.neos-borders(0px, 12px); - border: 1px solid #e7e7e8; padding-left: 15px; padding-right: 15px; padding-bottom: 15px; - background: rgba(242, 242, 242, 0.5); - border-top: none; } .tab-item:not([hidden]){ @@ -93,69 +87,52 @@ width: 100%; } -.tabs .tab { - &: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; - } - } -} +.tabs { + .tab { + @include clippedForm; -.tabs .tab { position: relative; - color: var(--c-brand); + color: $headingsColor; .tab-link__inner { - pointer-events: none; - display: grid; - - border: 1px solid transparent; - border-bottom-color: #e7e7e8; - - font-weight: 540; - // @include helpers.neos-borders(12px, 0px); - - &:empty::after { - content: "Tab title"; - color: #ccc; - } + 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: rgba(242, 242, 242, 0.5); - border: 1px solid #e7e7e8; - border-bottom: none; - } + .tab-link__inner { + background-color: brand('primary'); + } - color: var(--c-accent); + color: #fff; } &:focus, &:active, &:focus[aria-selected="true"] { - &::before { - border-top-color: var(--c-accent); - } - - .tab-link__inner { - background-color: rgba(242, 242, 242, 0.5); - border-left-color: var(--c-accent); - border-right-color: var(--c-accent); - border-top-color: var(--c-accent); - } + .tab-link__inner { + background-color: brand('primary'); + } } -} -.tabs .spacer { - display: flex; - flex-grow: 1; - border-bottom: 1px solid #e7e7e8; + &: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/Scss/Atoms/_Button.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/Atoms/_Button.scss index c66cc1f03..3e7f8e24d 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/_Framework/_Mixins.scss b/DistributionPackages/Neos.NeosIo/Resources/Private/Scss/_Framework/_Mixins.scss index 3ebc025e4..c1e5456c2 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}; +} From 91d7014e7025fa915e45c448d1674cd2e4fdfe81 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Wed, 26 Jul 2023 16:03:52 +0200 Subject: [PATCH 4/4] TASK: Adjust tabs markup --- .../Resources/Private/Fusion/Content/Tabs/Tabs.fusion | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion index e312949ba..d09b499fb 100644 --- a/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion +++ b/DistributionPackages/Neos.NeosIo/Resources/Private/Fusion/Content/Tabs/Tabs.fusion @@ -11,12 +11,11 @@ prototype(Neos.NeosIo:Tabs) < prototype(Neos.Neos:ContentComponent) { id={'tab-' + node.identifier} aria-controls={'panel-' + node.identifier} > - + -