Added WIP Menu
This commit is contained in:
403
src/components/Page/Menu/Menu.module.css
Normal file
403
src/components/Page/Menu/Menu.module.css
Normal file
@@ -0,0 +1,403 @@
|
||||
@layer components {
|
||||
/* === MenuGrid === */
|
||||
|
||||
.menu {
|
||||
pointer-events: none;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
top: var(--el-header-height);
|
||||
left: 0;
|
||||
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(5, auto);
|
||||
gap: 0;
|
||||
gap: var(--spacing-cozy);
|
||||
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - var(--el-header-height));
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
background-color: var(--color-palette-charcoal-gray);
|
||||
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
|
||||
&.isOpen {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet) {
|
||||
@mixin px var(--spacing-comfortable);
|
||||
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
gap: var(--spacing-comfortable);
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1";
|
||||
grid-template-columns: 2.5fr 1fr 3fr 1.5fr 1fr 1fr 4fr;
|
||||
grid-template-rows: 3fr 1fr 2.5fr 1.5fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuArea === */
|
||||
.area {
|
||||
/* === AREA VARIABLES === */
|
||||
--area-bg: transparent;
|
||||
--area-border: transparent;
|
||||
--area-animation-keyframe: none;
|
||||
--area-animation-duration: 0s;
|
||||
--area-animation-timing: linear;
|
||||
--area-bg-filter: grayscale(100%) contrast(150) brightness(150);
|
||||
|
||||
/** === TITLE VARIABLES === */
|
||||
--title-color: var(--color-palette-light-silver);
|
||||
--title-font: var(--font-header);
|
||||
--title-font-size: var(--typo-size-2xl);
|
||||
--title-font-weight: var(--typo-weight-black);
|
||||
--title-line-height: 1;
|
||||
--title-transform: uppercase;
|
||||
--title-spacing: var(--typo-spacing-comfortable);
|
||||
--title-hover-color: var(--color-tertiary);
|
||||
--title-current-color: var(--color-primary);
|
||||
--title-current-bg: var(--color-primary);
|
||||
|
||||
/** === SUBLINK VARIABLES === */;
|
||||
--sublink-color: var(--color-palette-light-silver);
|
||||
--sublink-font: var(--font-header);
|
||||
--sublink-font-size: var(--typo-size-2xl);
|
||||
--sublink-font-weight: var(--typo-weight-black);
|
||||
--sublink-transform: uppercase;
|
||||
--sublink-spacing: var(--typo-spacing-relaxed);
|
||||
--sublink-line-height: var(--typo-leading-relaxed);
|
||||
--sublink-letter-spacing: var(--typo-spacing-relaxed);
|
||||
--sublink-current-color: var(--color-primary);
|
||||
--sublink-current-bg: var(--color-primary);
|
||||
|
||||
/** === SUBTITLE VARIABLES === **/
|
||||
--divider-color: var(--color-palette-light-silver);
|
||||
--divider-width: var(--size-6);
|
||||
--divider-height: var(--size-2);
|
||||
--divider-font: var(--font-mono);
|
||||
--divider-symbol: ⯆;
|
||||
--divider-font-size: var(--typo-size-2xl);
|
||||
--divider-padding: 0 var(--typo-spacing-cozy);
|
||||
--subtitle-color: var(--color-palette-light-silver);
|
||||
--subtitle-font: var(--font-mono);
|
||||
--subtitle-font-size: var(--typo-size-2xl);
|
||||
--subtitle-text-transform: uppercase;
|
||||
--subtitle-letter-spacing: var(--typo-spacing-cozy);
|
||||
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border: var(--size-1) solid var(--area-border);
|
||||
|
||||
text-align: center;
|
||||
|
||||
background: var(--area-bg);
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:not(.current) {
|
||||
&:hover {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.area_1 { grid-area: area_1; }
|
||||
.area_2 { grid-area: area_2; }
|
||||
.area_3 { grid-area: area_3; }
|
||||
.area_4 { grid-area: area_4; }
|
||||
.area_5 { grid-area: area_5; }
|
||||
|
||||
.hasBGImg {
|
||||
&:hover {
|
||||
.bgImg {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bgImg {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
filter: var(--area-bg-filter);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuTitle === */
|
||||
.title {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mainlink {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--title-font);
|
||||
font-size: var(--title-font-size);
|
||||
font-weight: var(--title-font-weight);
|
||||
line-height: var(--title-line-height);
|
||||
color: var(--title-color);
|
||||
text-transform: var(--title-transform);
|
||||
letter-spacing: var(--title-spacing);
|
||||
|
||||
transition: any 0.5s ease-in-out;
|
||||
|
||||
&:focus {
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.current {
|
||||
pointer-events: none;
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
&:hover {
|
||||
color: var(--title-current-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
transition: border 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuSublinks === */
|
||||
.list {
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sublink {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--sublink-font);
|
||||
font-size: var(--sublink-font-size);
|
||||
font-weight: var(--sublink-font-weight);
|
||||
line-height: var(--sublink-line-height);
|
||||
color: var(--sublink-color);
|
||||
text-transform: var(--sublink-transform);
|
||||
letter-spacing: var(--sublink-spacing);;
|
||||
|
||||
&:not(.current),
|
||||
&:not(.focus) {
|
||||
transition: var(--sublink-hover-transition);
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
position: absolute;
|
||||
font-family: var(--sublink-hover-decorator-font);
|
||||
opacity: 0;
|
||||
transition: var(--sublink-hover-decorator-transition);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: var(--sublink-hover-decorator-left-symbol);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: var(--sublink-hover-decorator-right-symbol);
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--sublink-hover-color);
|
||||
|
||||
&::before {
|
||||
top: var(--sublink-hover-decorator-left-pos-y);
|
||||
left: var(--sublink-hover-decorator-left-pos-x);
|
||||
transform: var(--sublink-hover-decorator-left-transform);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: var(--sublink-hover-decorator-right-pos-y);
|
||||
right: var(--sublink-hover-decorator-rightt-pos-x);
|
||||
transform: var(--sublink-hover-decorator-right-transform);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
&:hover {
|
||||
color: var(--sublink-current-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.current {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* === MenuSubtitle === */
|
||||
.wrapper {
|
||||
position: relative;
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === UTILITY Classes */
|
||||
@media screen and (--bp-desktop) {
|
||||
.pos_tr {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.pos_tc {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.pos_tl {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.pos_cr {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1em;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.pos_c {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.pos_cl {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 1em;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.pos_br {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.pos_bc {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.pos_bl {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.vertical-rl {
|
||||
writing-mode: vertical-rl;
|
||||
|
||||
& * {
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-lr {
|
||||
writing-mode: vertical-lr;
|
||||
|
||||
& * {
|
||||
writing-mode: vertical-lr;
|
||||
}
|
||||
}
|
||||
|
||||
.sideways-rl {
|
||||
writing-mode: sideways-rl;
|
||||
|
||||
& * {
|
||||
writing-mode: sideways-rl;
|
||||
}
|
||||
}
|
||||
|
||||
.sideways-lr {
|
||||
writing-mode: sideways-lr;
|
||||
|
||||
& * {
|
||||
writing-mode: sideways-lr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* === CATEGORY Variants === */
|
||||
@media screen and (--bp-desktop) {
|
||||
.meta {}
|
||||
.kitchensink {}
|
||||
.awq {}
|
||||
.worldburner {}
|
||||
.chainbreaker {}
|
||||
}
|
||||
}
|
||||
121
src/components/Page/Menu/MenuArea/MenuArea.module.css
Normal file
121
src/components/Page/Menu/MenuArea/MenuArea.module.css
Normal file
@@ -0,0 +1,121 @@
|
||||
@layer components {
|
||||
.area {
|
||||
--area-bg: transparent;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text-align: center;
|
||||
|
||||
background: var(--area-bg);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
border-bottom: var(--size-1) solid var(--color-palette-gunmetal);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.area_1 {
|
||||
position: relative;
|
||||
grid-area: area_1;
|
||||
}
|
||||
|
||||
.area_2 {
|
||||
position: relative;
|
||||
grid-area: area_2;
|
||||
}
|
||||
|
||||
.area_3 {
|
||||
position: relative;
|
||||
grid-area: area_3;
|
||||
}
|
||||
|
||||
.area_4 {
|
||||
position: relative;
|
||||
grid-area: area_4;
|
||||
}
|
||||
|
||||
.area_5 {
|
||||
position: relative;
|
||||
grid-area: area_5;
|
||||
}
|
||||
|
||||
.hasBGImg {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
& .bgImg {
|
||||
animation: var(--area-animation-keyframe) var(--area-animation-duration) var(--area-animation-timing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bgImg {
|
||||
--area-bg-animation-keyframe: none;
|
||||
--area-bg--animation-duration: 0s;
|
||||
--area-bg--animation-timing: linear;
|
||||
--area-bg-filter: grayscale(100%) contrast(150) brightness(150);
|
||||
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
filter: var(--area-bg-filter);
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
.chainbreaker {
|
||||
--area-animation-keyframe: var(--kf-color-bleed);
|
||||
--area-animation-duration: var(--img-colorbleed-duration);
|
||||
--area-animation-timing: var(--img-colorbleed-timing);
|
||||
|
||||
& .bgImg {
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
&.hasBGImg {
|
||||
--divider-color: transparent;
|
||||
--title-color: transparent;
|
||||
--subtitle-color: transparent;
|
||||
--subtitle-transition: all 0.3s ease-in-out;
|
||||
--title-transition: color 0.3s ease-in-out;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 0;
|
||||
background: alpharize(var(--grid-bg), 0.66);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
--divider-color: var(--color-tertiary);
|
||||
--title-color: var(--color-tertiary);
|
||||
--subtitle-color: var(--color-tertiary);
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/components/Page/Menu/MenuArea/index.tsx
Normal file
46
src/components/Page/Menu/MenuArea/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
|
||||
import MenuItem from '@/components/Page/Menu//MenuItem/';
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
import styles from './MenuArea.module.css';
|
||||
|
||||
import { NavigationItem } from '@/lib/types/navigation';
|
||||
|
||||
interface MenuAreaProps {
|
||||
item: NavigationItem;
|
||||
}
|
||||
|
||||
const MenuArea = React.memo(({ item }: MenuAreaProps) => {
|
||||
const hasBGImg = !!item.background;
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
|
||||
const areaClasses = React.useMemo(() => {
|
||||
return [
|
||||
styles.area,
|
||||
styles[item.gridPosition],
|
||||
styles[item.variant],
|
||||
isCurrentPath(item.path) ? styles.current : '',
|
||||
item.background ? styles.hasBGImg : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}, [item.background, isCurrentPath]);
|
||||
|
||||
return (
|
||||
<section className={areaClasses}>
|
||||
{hasBGImg && (
|
||||
<img
|
||||
className={styles.bgImg}
|
||||
src={item.background}
|
||||
alt={`Background Image for ${item.name}`}
|
||||
/>
|
||||
)}
|
||||
<MenuItem item={item} />
|
||||
</section>
|
||||
);
|
||||
});
|
||||
|
||||
MenuArea.displayName = 'MenuArea';
|
||||
|
||||
export default MenuArea;
|
||||
97
src/components/Page/Menu/MenuGrid/MenuGrid.module.css
Normal file
97
src/components/Page/Menu/MenuGrid/MenuGrid.module.css
Normal file
@@ -0,0 +1,97 @@
|
||||
@layer components {
|
||||
.menu {
|
||||
--grid-bg: var(--color-palette-charcoal-gray);
|
||||
--grid-fg: var(--color-palette-light-silver);
|
||||
|
||||
/* === MenuTitle Vars === */
|
||||
--title-color: var(--grid-fg);
|
||||
--title-font: var(--font-mono);
|
||||
--title-font-size: var(--typo-size-2xl);
|
||||
--title-font-weight: var(--typo-weight-black);
|
||||
--title-line-height: 1;
|
||||
--title-transform: uppercase;
|
||||
--title-spacing: var(--typo-spacing-comfortable);
|
||||
--title-hover-fg: var(--primary);
|
||||
--title-hover-bg: transparent;
|
||||
--title-current-bg: var(--color-tertiary);
|
||||
--title-current-fg: var(--grid-bg);
|
||||
--title-focus-fg: var(--color-secondary);
|
||||
--title-focus-bg: var(--grid-bg);
|
||||
--title-transition: none;
|
||||
|
||||
/* === MenuSublinks Vars === */
|
||||
--sublink-color: var(--grid-fg);
|
||||
--sublink-font: var(--font-mono);
|
||||
--sublink-font-size: var(--typo-size-xl);
|
||||
--sublink-font-weight: var(--typo-weight-light);
|
||||
--sublink-transform: uppercase;
|
||||
--sublink-line-height: var(--typo-leading-relaxed);
|
||||
--sublink-spacing: var(--typo-spacing-loosest);
|
||||
--sublink-hover-fg: var(--color-tertiary);
|
||||
--sublink-hover-bg: transparent;
|
||||
--sublink-current-fg: var(--grid-bg);
|
||||
--sublink-current-bg: var(--color-primary);
|
||||
--sublink-focus-fg: var(--color-secondary);
|
||||
--sublink-focus-bg: var(--grid-bg);
|
||||
--sublink-transition: none;
|
||||
|
||||
/* === MenuSubtitle Vars === */
|
||||
--divider-color: var(--grid-fg);
|
||||
--divider-width: var(--size-12);
|
||||
--divider-height: var(--size-2);
|
||||
--divider-font: var(--font-mono);
|
||||
--divider-font-size: var(--typo-size-2xl);
|
||||
--divider-padding: 0 var(--typo-spacing-cozy);
|
||||
--subtitle-font: var(--font-mono);
|
||||
--subtitle-color: var(--grid-fg);
|
||||
--subtitle-font-size: var(--typo-size-xl);
|
||||
--subtitle-text-transform: uppercase;
|
||||
--subtitle-letter-spacing: var(--typo-spacing-cozy);
|
||||
--subtitle-transition: none;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
top: var(--el-header-height);
|
||||
left: 0;
|
||||
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"area_1"
|
||||
"area_2"
|
||||
"area_3"
|
||||
"area_4"
|
||||
"area_5";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(5, auto);
|
||||
|
||||
width: 100vw;
|
||||
height: calc(100vh - var(--el-header-height));
|
||||
|
||||
color: var(--grid-fg);
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
background-color: var(--grid-bg);
|
||||
|
||||
&.isOpen {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media screen and (--bp-desktop) {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_4 area_4 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_2"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1"
|
||||
"area_5 area_5 area_3 area_3 area_3 area_3 area_1";
|
||||
grid-template-columns: 2.5fr 1fr 3fr 1.5fr 1fr 1fr 4fr;
|
||||
grid-template-rows: 3fr 1fr 2.5fr 1.5fr 2fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/components/Page/Menu/MenuGrid/index.tsx
Normal file
67
src/components/Page/Menu/MenuGrid/index.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useMenu } from '@/contexts/MenuContext';
|
||||
import MenuArea from '@/components/Page/Menu/MenuArea/';
|
||||
|
||||
import styles from './MenuGrid.module.css';
|
||||
|
||||
interface MenuGridProps {
|
||||
navigationData: Awaited<
|
||||
ReturnType<
|
||||
typeof import('@/lib/readers/system/navigation').getNavigationData
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
export default function MenuGrid({ navigationData }: MenuGridProps) {
|
||||
const { isMenuOpen, closeMenu } = useMenu();
|
||||
const menuRef = React.useRef<HTMLElement>(null);
|
||||
const [isClosing, setIsClosing] = React.useState(false);
|
||||
|
||||
const handleClose = React.useCallback(() => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => {
|
||||
closeMenu();
|
||||
setIsClosing(false);
|
||||
}, 800);
|
||||
}, [closeMenu]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isMenuOpen) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
return () => document.removeEventListener('keydown', handleEscape);
|
||||
}, [isMenuOpen, handleClose]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMenuOpen && menuRef.current) {
|
||||
const initialFocus = menuRef.current.querySelector(
|
||||
'a, button'
|
||||
) as HTMLElement;
|
||||
initialFocus?.focus();
|
||||
}
|
||||
}, [isMenuOpen]);
|
||||
|
||||
if (!navigationData) return null;
|
||||
|
||||
const { items } = navigationData;
|
||||
|
||||
return (
|
||||
<nav
|
||||
ref={menuRef}
|
||||
className={`${styles.menu} ${isMenuOpen ? styles.isOpen : ''} ${isClosing ? styles.isClosing : ''}`}
|
||||
aria-hidden={!isMenuOpen}
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<MenuArea key={item.gridPosition} item={item} />
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
24
src/components/Page/Menu/MenuItem/index.tsx
Normal file
24
src/components/Page/Menu/MenuItem/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { hasSublinks, hasSubtitle } from '@/lib/types/navigation';
|
||||
import MenuTitle from '@/components/Page/Menu/MenuTitle/';
|
||||
import MenuSublinks from '@/components/Page/Menu/MenuSublinks/';
|
||||
import MenuSubtitle from '@/components/Page/Menu/MenuSubtitle/';
|
||||
|
||||
import { NavigationItem } from '@/lib/types/navigation';
|
||||
|
||||
interface MenuItem {
|
||||
item: NavigationItem;
|
||||
}
|
||||
|
||||
export default function MenuItem({ item }: MenuItem) {
|
||||
return (
|
||||
<>
|
||||
<MenuTitle path={item.path} name={item.name} variant={item.variant} />
|
||||
{hasSublinks(item) && (
|
||||
<MenuSublinks sublinks={item.sublinks.value} variant={item.variant} />
|
||||
)}
|
||||
{hasSubtitle(item) && (
|
||||
<MenuSubtitle subtitle={item.subtitle.value} variant={item.variant} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@layer components {
|
||||
.list {
|
||||
@media screen and (--bp-tablet-down) {
|
||||
@util hide-visually;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sublink {
|
||||
font-family: var(--sublink-font);
|
||||
font-size: var(--sublink-font-size);
|
||||
font-weight: var(--sublink-font-weight);
|
||||
line-height: var(--sublink-line-height);
|
||||
color: var(--sublink-color);
|
||||
text-transform: var(--sublink-transform);
|
||||
letter-spacing: var(--sublink-spacing);
|
||||
|
||||
&.current {
|
||||
padding: var(--spacing-snug) var(--spacing-tight);
|
||||
color: var(--title-current-fg);
|
||||
animation: var(--kf-text-glitch) 5s infinite;
|
||||
|
||||
&::before {
|
||||
content: "⟩";
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.current) {
|
||||
--pointer-left-symbol: "▶";
|
||||
--pointer-right-symbol: "◀";
|
||||
--pointer-distance: 1em;
|
||||
|
||||
@mixin anim-txt-pointer_focus;
|
||||
@mixin px var(--spacing-tight);
|
||||
|
||||
&:focus {
|
||||
color: var(--sublink-focus-fg);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:not(:focus) {
|
||||
--pointer-left-symbol: "{";
|
||||
--pointer-right-symbol: "}";
|
||||
--pointer-distance: 0.5em;
|
||||
|
||||
@mixin anim-txt-pointer;
|
||||
|
||||
&:hover {
|
||||
--sublink-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/components/Page/Menu/MenuSublinks/index.tsx
Normal file
29
src/components/Page/Menu/MenuSublinks/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './MenuSublinks.module.css';
|
||||
|
||||
import { SubNavigationItem } from '@/lib/types/navigation';
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
interface MenuSublinks {
|
||||
sublinks: readonly SubNavigationItem[];
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuSublinks({ sublinks, variant }: MenuSublinks) {
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
return (
|
||||
<ul className={`${styles.list} ${styles[`${variant}`]}`}>
|
||||
{sublinks.map((sublink: SubNavigationItem) => (
|
||||
<li className={styles.item} key={sublink.path}>
|
||||
<Link
|
||||
href={sublink.path}
|
||||
className={`${styles.sublink} ${isCurrentPath(sublink.path) ? styles.current : ''}`}
|
||||
>
|
||||
{sublink.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@layer components {
|
||||
.claim {
|
||||
|
||||
@media screen and (--bp-tablet-down) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dividersymbol {
|
||||
position: relative;
|
||||
|
||||
font-family: var(--divider-font);
|
||||
font-size: var(--divider-font-size);
|
||||
line-height: var(--divider-line-height);
|
||||
color: var(--divider-color);
|
||||
|
||||
transition: var(--subtitle-transition);
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
width: var(--divider-width);
|
||||
height: var(--divider-height);
|
||||
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: calc(var(--divider-width) *-1);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: calc(var(--divider-width) *-1);
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: var(--subtitle-font);
|
||||
font-size: var(--subtitle-font-size);
|
||||
color: var(--subtitle-color);
|
||||
text-transform: var(--subtitle-text-transform);
|
||||
letter-spacing: var(--subtitle-letter-spacing);
|
||||
|
||||
transition: var(--subtitle-transition);
|
||||
}
|
||||
}
|
||||
26
src/components/Page/Menu/MenuSubtitle/index.tsx
Normal file
26
src/components/Page/Menu/MenuSubtitle/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Subtitle, hasDivider } from '@/lib/types/navigation';
|
||||
import styles from './MenuSubtitle.module.css';
|
||||
|
||||
interface MenuSubtitleProps {
|
||||
subtitle: Subtitle;
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuSubtitle({ subtitle, variant }: MenuSubtitleProps) {
|
||||
const divider = hasDivider(subtitle) ? (
|
||||
<div className={styles.divider}>
|
||||
<span className={`${styles.dividersymbol}`}>
|
||||
{subtitle.divider.value}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${styles.claim} ${styles[`${variant}`]}`}>
|
||||
{divider}
|
||||
<p className={`${styles.subtitle}`}>{subtitle.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
src/components/Page/Menu/MenuTitle/MenuTitle.module.css
Normal file
63
src/components/Page/Menu/MenuTitle/MenuTitle.module.css
Normal file
@@ -0,0 +1,63 @@
|
||||
@layer components {
|
||||
.heading {
|
||||
position: relative;
|
||||
|
||||
&:has(+ ul) {
|
||||
@mixin my var(--spacing-comfortable);
|
||||
}
|
||||
}
|
||||
|
||||
.mainlink {
|
||||
position: relative;
|
||||
|
||||
border: none;
|
||||
|
||||
font-family: var(--title-font);
|
||||
font-size: var(--title-font-size);
|
||||
font-weight: var(--title-font-weight);
|
||||
line-height: var(--title-line-height);
|
||||
color: var(--title-color);
|
||||
text-transform: var(--title-transform);
|
||||
letter-spacing: var(--title-spacing);
|
||||
|
||||
background: transparent;
|
||||
|
||||
transition: var(--title-transition);
|
||||
|
||||
&.current {
|
||||
padding: var(--spacing-snug) var(--spacing-tight);
|
||||
color: var(--title-current-fg);
|
||||
animation: var(--kf-text-glitch) 5s infinite;
|
||||
|
||||
&::before {
|
||||
content: "⟩";
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.current) {
|
||||
--pointer-left-symbol: "▶";
|
||||
--pointer-right-symbol: "◀";
|
||||
--pointer-distance: 1em;
|
||||
|
||||
@mixin anim-txt-pointer_focus;
|
||||
@mixin px var(--spacing-tight);
|
||||
|
||||
&:focus {
|
||||
color: var(--title-focus-fg);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:not(:focus) {
|
||||
--pointer-left-symbol: "{";
|
||||
--pointer-right-symbol: "}";
|
||||
--pointer-distance: 0.5em;
|
||||
|
||||
@mixin anim-txt-pointer;
|
||||
|
||||
&:hover {
|
||||
--title-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/components/Page/Menu/MenuTitle/index.tsx
Normal file
25
src/components/Page/Menu/MenuTitle/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './MenuTitle.module.css';
|
||||
|
||||
import { useCurrentPath } from '@/hooks/useCurrentPath';
|
||||
|
||||
interface MenuTitleProps {
|
||||
name: string;
|
||||
path: string;
|
||||
variant: string;
|
||||
}
|
||||
|
||||
export default function MenuTitle({ name, path, variant }: MenuTitleProps) {
|
||||
const { isCurrentPath } = useCurrentPath();
|
||||
return (
|
||||
<h2 className={`${styles.heading} ${styles[`${variant}`]}`}>
|
||||
<Link
|
||||
className={`${styles.mainlink} ${isCurrentPath(path) ? styles.current : ''}`}
|
||||
href={path}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
7
src/components/Page/Menu/index.tsx
Normal file
7
src/components/Page/Menu/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { getNavigationData } from '@/lib/readers/system/navigation';
|
||||
import MenuGrid from '@/components/Page/Menu/MenuGrid';
|
||||
|
||||
export default async function Menu() {
|
||||
const navigation = await getNavigationData();
|
||||
return <MenuGrid navigationData={navigation} />;
|
||||
}
|
||||
Reference in New Issue
Block a user