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