Refactored application components.

Added accessibility tags.
This commit is contained in:
Jay
2025-07-20 11:40:09 -04:00
parent 5400a7acdd
commit 3256e4968a
4 changed files with 251 additions and 98 deletions
+23 -23
View File
@@ -1,4 +1,4 @@
.appContainer { .appWrapper {
height: 100vh; height: 100vh;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@@ -20,24 +20,24 @@
display: flex; display: flex;
} }
.alphaZone { .firstZone {
flex: 1; flex: 1;
} }
.betaZone { .secondZone {
flex: 1; flex: 1;
} }
.colorPickerContainer { .colorPickerWrapper {
} }
.colorValuesContainer { .colorValuesWrapper {
} }
.paletteEditorContainer { .paletteEditorWrapper {
} }
.paletteLibraryContainer { .paletteLibraryWrapper {
} }
} }
@@ -61,35 +61,35 @@
.mobileRightNav { .mobileRightNav {
} }
.mobileAlphaZone { .mobileFirstZone {
flex: 1; flex: 1;
} }
.mobileBetaZone { .mobileSecondZone {
flex: 1; flex: 1;
} }
.tabbedContainer { .tabWrapper {
max-height: 100vh; max-height: 100vh;
overflow-y: scroll; overflow-y: scroll;
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
} }
.tabbedContainer .tab { .tabWrapper .tab {
height: 100vh; height: 100vh;
scroll-snap-align: start; scroll-snap-align: start;
} }
.colorPickerContainer { .colorPickerWrapper {
} }
.colorValuesContainer { .colorValuesWrapper {
} }
.paletteEditorContainer { .paletteEditorWrapper {
} }
.paletteLibraryContainer { .paletteLibraryWrapper {
} }
} }
@@ -119,32 +119,32 @@
margin-left: auto; margin-left: auto;
} }
.mobileAlphaZone { .mobileFirstZone {
} }
.mobileBetaZone { .mobileSecondZone {
} }
.tabbedContainer { .tabWrapper {
display: flex; display: flex;
overflow-x: scroll; overflow-x: scroll;
scroll-snap-type: x mandatory; scroll-snap-type: x mandatory;
} }
.tabbedContainer .tab { .tabWrapper .tab {
min-width: 100vw; min-width: 100vw;
scroll-snap-align: start; scroll-snap-align: start;
} }
.colorPickerContainer { .colorPickerWrapper {
} }
.colorValuesContainer { .colorValuesWrapper {
} }
.paletteEditorContainer { .paletteEditorWrapper {
} }
.paletteLibraryContainer { .paletteLibraryWrapper {
} }
} }
+187 -49
View File
@@ -5,86 +5,224 @@ import ColorValues from "./components/ColorValues/ColorValues";
import { LeftMenu, RightMenu } from "./components/SideMenu"; import { LeftMenu, RightMenu } from "./components/SideMenu";
import clsx from "clsx"; import clsx from "clsx";
function App() { // Menu Button Components
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
const [isLeftMenuOpen, setIsLeftMenuOpen] = useState(false);
const toggleRightMenu = () => setIsRightMenuOpen(!isRightMenuOpen); interface MenuButtonProps {
const toggleLeftMenu = () => setIsLeftMenuOpen(!isLeftMenuOpen); onClick: () => void;
isOpen: boolean;
const RightMenuButton = () => ( }
<button className={styles.rightMenuButton} onClick={toggleRightMenu}>
</button>
);
const LeftMenuButton = () => (
<button className={styles.leftMenuButton} onClick={toggleLeftMenu}>
</button>
);
function LeftMenuButton({ onClick, isOpen }: MenuButtonProps) {
return ( return (
<div className={styles.appContainer}> <button
<div className={styles.mobileContent}> className={styles.leftMenuButton}
<div className={styles.mobileTopNav}> onClick={onClick}
<LeftMenuButton /> aria-label="Open left menu"
<RightMenuButton /> aria-haspopup="dialog"
</div> aria-expanded={isOpen}
>
</button>
);
}
<div className={styles.mobileLeftNav}> function RightMenuButton({ onClick, isOpen }: MenuButtonProps) {
<LeftMenuButton /> return (
</div> <button
className={styles.rightMenuButton}
onClick={onClick}
aria-label="Open right menu"
aria-haspopup="dialog"
aria-expanded={isOpen}
>
</button>
);
}
<div className={styles.mobileAlphaZone}> // Mobile Layout Components
<div className={styles.tabbedContainer}>
<div className={clsx(styles.tab, styles.colorPickerContainer)}> interface MenuStateProps {
isRightMenuOpen: boolean;
isLeftMenuOpen: boolean;
setIsRightMenuOpen: (state: boolean) => void;
setIsLeftMenuOpen: (state: boolean) => void;
}
function MobileTopNav({
onLeftMenuClick,
onRightMenuClick,
isRightMenuOpen,
isLeftMenuOpen,
}: {
onLeftMenuClick: () => void;
onRightMenuClick: () => void;
isRightMenuOpen: boolean;
isLeftMenuOpen: boolean;
}) {
return (
<nav className={styles.mobileTopNav} aria-label="Mobile top navigation">
<LeftMenuButton onClick={onLeftMenuClick} isOpen={isLeftMenuOpen} />
<RightMenuButton onClick={onRightMenuClick} isOpen={isRightMenuOpen} />
</nav>
);
}
function MobileLeftNav({ onClick, isOpen }: MenuButtonProps) {
return (
<nav className={styles.mobileLeftNav} aria-label="Mobile left navigation">
<LeftMenuButton onClick={onClick} isOpen={isOpen} />
</nav>
);
}
function MobileRightNav({ onClick, isOpen }: MenuButtonProps) {
return (
<nav className={styles.mobileRightNav} aria-label="Mobile right navigation">
<RightMenuButton onClick={onClick} isOpen={isOpen} />
</nav>
);
}
function MobileFirstZone() {
return (
<section className={styles.mobileFirstZone} aria-label="Color tools">
<div className={styles.tabWrapper} role="tablist">
<div
className={clsx(styles.tab, styles.colorPickerWrapper)}
role="tabpanel"
aria-label="Color Picker"
>
<ColorPicker /> <ColorPicker />
</div> </div>
<div className={clsx(styles.tab, styles.colorValuesContainer)}> <div
className={clsx(styles.tab, styles.colorValuesWrapper)}
role="tabpanel"
aria-label="Color values"
>
<ColorValues /> <ColorValues />
</div> </div>
</div> </div>
</div> </section>
);
}
<div className={styles.mobileBetaZone}> function MobileSecondZone() {
<div className={styles.paletteEditorContainer}></div> return (
</div> <section className={styles.mobileSecondZone} aria-label="Palette tools">
<div
className={styles.paletteEditorWrapper}
aria-label="Palette editor"
></div>
</section>
);
}
<div className={styles.mobileRightNav}> function MobileContent({
<RightMenuButton /> isLeftMenuOpen,
</div> setIsLeftMenuOpen,
isRightMenuOpen,
setIsRightMenuOpen,
}: MenuStateProps) {
const toggleRightMenu = () => setIsRightMenuOpen(!isRightMenuOpen);
const toggleLeftMenu = () => setIsLeftMenuOpen(!isLeftMenuOpen);
return (
<main className={styles.mobileContent}>
<MobileTopNav
onLeftMenuClick={toggleLeftMenu}
onRightMenuClick={toggleRightMenu}
isLeftMenuOpen={isLeftMenuOpen}
isRightMenuOpen={isRightMenuOpen}
/>
<MobileLeftNav onClick={toggleLeftMenu} isOpen={isLeftMenuOpen} />
<MobileFirstZone />
<MobileSecondZone />
<MobileRightNav onClick={toggleRightMenu} isOpen={isRightMenuOpen} />
<LeftMenu <LeftMenu
isOpen={isLeftMenuOpen} isOpen={isLeftMenuOpen}
onClose={() => setIsLeftMenuOpen(false)} onClose={() => setIsLeftMenuOpen(false)}
> >
<div>User Info</div> <div id="user-info" aria-label="User information">
User Info
</div>
</LeftMenu> </LeftMenu>
<RightMenu <RightMenu
isOpen={isRightMenuOpen} isOpen={isRightMenuOpen}
onClose={() => setIsRightMenuOpen(false)} onClose={() => setIsRightMenuOpen(false)}
> >
<div className={styles.paletteLibraryContainer}>Palette Library</div> <div
</RightMenu> id="palette-library"
className={styles.paletteLibraryWrapper}
aria-label="Palette library"
>
Palette Library
</div> </div>
</RightMenu>
</main>
);
}
<div className={styles.mainContent}> // Desktop Layout Components
<div className={styles.alphaZone}>
<div className={styles.colorPickerContainer}> function FirstZone() {
return (
<section className={styles.firstZone} aria-label="Color tools">
<div className={styles.colorPickerWrapper} aria-label="Color picker">
<ColorPicker /> <ColorPicker />
</div> </div>
<div className={styles.colorValuesContainer}> <div className={styles.colorValuesWrapper} aria-label="Color values">
<ColorValues /> <ColorValues />
</div> </div>
</div> </section>
);
}
<div className={styles.betaZone}> function SecondZone() {
<div className={styles.paletteEditorContainer}></div> return (
<div className={styles.paletteLibraryContainer}></div> <section className={styles.secondZone} aria-label="Palette tools">
</div> <div
</div> className={styles.paletteEditorWrapper}
aria-label="Palette editor"
></div>
<div
className={styles.paletteLibraryWrapper}
aria-label="Palette library"
></div>
</section>
);
}
function DesktopContent() {
return (
<main className={styles.mainContent}>
<FirstZone />
<SecondZone />
</main>
);
}
// Main App Component
function App() {
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
const [isLeftMenuOpen, setIsLeftMenuOpen] = useState(false);
return (
<div className={styles.appWrapper} role="application">
<MobileContent
isLeftMenuOpen={isLeftMenuOpen}
setIsLeftMenuOpen={setIsLeftMenuOpen}
isRightMenuOpen={isRightMenuOpen}
setIsRightMenuOpen={setIsRightMenuOpen}
/>
<DesktopContent />
</div> </div>
); );
} }
@@ -9,6 +9,14 @@
transition: 0.3s ease-out; transition: 0.3s ease-out;
} }
.sideMenu:not(.open) {
visibility: hidden;
}
.sideMenu.open {
visibility: visible;
}
.sideMenuUnderlay { .sideMenuUnderlay {
position: fixed; position: fixed;
top: 0; top: 0;
+7
View File
@@ -14,6 +14,7 @@ interface BaseMenuProps extends SideMenuProps {
function BaseMenu({ isOpen, onClose, children, position }: BaseMenuProps) { function BaseMenu({ isOpen, onClose, children, position }: BaseMenuProps) {
const isLeftMenu = position === "left"; const isLeftMenu = position === "left";
const menuId = `${position}-side-menu`;
const handleUnderlayClick = (e: React.MouseEvent<HTMLDivElement>) => { const handleUnderlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) { if (e.target === e.currentTarget) {
@@ -25,8 +26,13 @@ function BaseMenu({ isOpen, onClose, children, position }: BaseMenuProps) {
<div <div
className={clsx(styles.sideMenuUnderlay, { [styles.open]: isOpen })} className={clsx(styles.sideMenuUnderlay, { [styles.open]: isOpen })}
onClick={handleUnderlayClick} onClick={handleUnderlayClick}
aria-hidden={!isOpen}
> >
<div <div
id={menuId}
role="dialog"
aria-modal="true"
aria-label={`${position} side menu`}
data-cy={`${position}-menu`} data-cy={`${position}-menu`}
className={clsx( className={clsx(
styles.sideMenu, styles.sideMenu,
@@ -43,6 +49,7 @@ function BaseMenu({ isOpen, onClose, children, position }: BaseMenuProps) {
> >
<div className={styles.topNav}> <div className={styles.topNav}>
<button <button
aria-label="Close menu"
data-cy="close-menu" data-cy="close-menu"
className={styles.closeButton} className={styles.closeButton}
onClick={onClose} onClick={onClose}