Merge pull request 'Created column layout schema' (#107) from feature/triple-column-layout into develop

Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/107
This commit is contained in:
Denis Gorbunov 2022-08-25 09:00:34 +00:00
commit 040fecbcbf
9 changed files with 307 additions and 208 deletions

View File

@ -1,115 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Imports */
/* -------------------------------------------------------------------------- */
import React, { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { PropsPartion } from "./ContextMenuItem";
import classNames from "classnames";
import { ReactComponent as SelectIcon } from "assets/svg/select-arrow.svg";
type ChildType = React.ReactElement<any & PropsPartion[]>;
type ChildrenType = ChildType[] | ChildType;
/* -------------------------------------------------------------------------- */
/* Component props */
/* -------------------------------------------------------------------------- */
type MenuProps = {
emphasis?: "high" | "low";
disabled?: boolean;
className?: string | undefined;
button: React.ReactNode;
children: ChildrenType;
};
/* -------------------------------------------------------------------------- */
/* Styles */
/* -------------------------------------------------------------------------- */
const MenuButtonStyle = `
inline-flex
justify-center w-full
cursor-default
rounded
border border-gray-100
outline-8
py-2
px-2
text-base`;
const MenuItemStyle = `
absolute
left-0
mt-2 w-60
origin-top-left
rounded
shadow-lg
focus:outline-none
py-2 px-4 sm:text-sm`;
/* -------------------------------------------------------------------------- */
/* Component implementation */
/* -------------------------------------------------------------------------- */
/**
* Use width ContextMenuAction.tsx , for example:
* <ContextMenu button="MyButton" emphasis="low/high">
* <ContextMenuAction
* caption="First Menu"
* icon={icon}
* action={() => alert('click')}
* ></ContextMenuAction>
* ...
* </ContextMenu>
*/
export default function ContextMenu({
button,
children,
className,
emphasis = "low",
}: MenuProps) {
return (
<Menu as="div" className="relative inline-block text-right">
{({ open }) => (
<>
<Menu.Button
className={classNames([
`${MenuButtonStyle}`,
{
"hover:bg-gray-100 font-bold uppercase": emphasis === "high",
},
className,
])}
>
{button}
<SelectIcon
className={`${
open ? "rotate-180 transform" : "font-normal"
} my-2 mx-3 h-2 w-3 flex-center`}
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
show={open}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className={classNames([
className,
`${MenuItemStyle}`,
{ "ml-2": emphasis === "high" },
])}
>
{children}
</Menu.Items>
</Transition>
</>
)}
</Menu>
);
}

View File

@ -1,32 +0,0 @@
import classNames from "classnames";
import React from "react";
type Props = {
action: Function;
caption: string;
disabled?: boolean;
icon?: React.ReactNode;
className?: string | undefined;
};
export default function ContextMenuAction({
action,
caption,
disabled,
icon,
className,
}: Props) {
return (
<a
href="#"
onClick={(e) => action(e)}
className={classNames([
"group flex px-2 rounded items-center text-base hover:bg-gray-100",
className,
])}
>
{icon && <div className="mr-2 h-5 w-5">{icon}</div>}
<span className="px-2 py-2">{caption}</span>
</a>
);
}

View File

@ -5,7 +5,7 @@ import React, { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { PropsPartion } from "./ContextMenuItem";
import classNames from "classnames";
import { ReactComponent as SelectIcon } from "assets/svg/select-arrow.svg";
import { SVGCaretDown } from "components/icons";
type ChildType = React.ReactElement<any & PropsPartion[]>;
type ChildrenType = ChildType[] | ChildType;
@ -25,13 +25,13 @@ type MenuProps = {
/* -------------------------------------------------------------------------- */
const MenuButtonStyle = `
items-center
inline-flex
justify-center w-full
cursor-default
rounded
border border-gray-100
outline-8
bg-white
py-2
pl-4
pr-1
@ -43,7 +43,7 @@ left-0
mt-2 w-60
origin-top-left
rounded
bg-white
bg-white
shadow-lg
focus:outline-none
py-2 px-4 sm:text-sm`;
@ -51,17 +51,17 @@ py-2 px-4 sm:text-sm`;
/* -------------------------------------------------------------------------- */
/* Component implementation */
/* -------------------------------------------------------------------------- */
/**
* Use width ContextMenuAction.tsx , for example:
* <ContextMenu button="MyButton" emphasis="low/high">
* <ContextMenuAction
* caption="First Menu"
* icon={icon}
* action={() => alert('click')}
* ></ContextMenuAction>
* ...
* </ContextMenu>
*/
/**
* Use width ContextMenuAction.tsx , for example:
* <ContextMenu button="MyButton" emphasis="low/high">
* <ContextMenuAction
* caption="First Menu"
* icon={icon}
* action={() => alert('click')}
* ></ContextMenuAction>
* ...
* </ContextMenu>
*/
export default function ContextMenu({
button,
children,
@ -69,48 +69,50 @@ export default function ContextMenu({
emphasis = "low",
}: MenuProps) {
return (
<Menu as="div" className="relative inline-block text-right">
{({ open }) => (
<>
<Menu.Button
<Menu as="div" className="relative inline-block text-right">
{({ open }) => (
<>
<Menu.Button
className={classNames([
`${MenuButtonStyle}`,
{
"hover:bg-gray-100 font-bold uppercase": emphasis === "high",
},
className,
])}
>
{button}
<SVGCaretDown
className={`${
open ? "rotate-180 transform" : "font-normal"
} my-2 mx-3 w-4 flex-center fill-gray-900 stroke-gray-900`}
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
show={open}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className={classNames([
`${MenuButtonStyle}`,
{ "bg-gray-100 font-bold uppercase": emphasis === "high" },
className,
`${MenuItemStyle}`,
{ "ml-2": emphasis === "high" },
])}
>
{button}
<SelectIcon
className={`${
open ? "rotate-180 transform" : "font-normal"
} my-2 mx-3 h-2 w-3 flex-center`}
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
show={open}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className={classNames([
className,
`${MenuItemStyle}`,
{ "ml-2": emphasis === "high" },
])}
>
{children}
</Menu.Items>
</Transition>
</>
)}
</Menu>
{children}
</Menu.Items>
</Transition>
</>
)}
</Menu>
);
}

View File

@ -1,7 +1,6 @@
import classNames from "classnames";
import React from "react";
type Props = {
action: Function;
caption: string;
@ -18,19 +17,16 @@ export default function ContextMenuAction({
className,
}: Props) {
return (
<button
<a
href="#"
onClick={(e) => action(e)}
disabled={disabled}
className={classNames([
"group flex items-center text-base",
{ "opacity-50": disabled, "cursor-default": !disabled },
"group flex px-2 rounded items-center text-base hover:bg-gray-100",
className,
])}
>
{icon && <div className="mr-2 h-5 w-5">{icon}</div>}
<span className="px-2 py-2">{caption}</span>
</button>
</a>
);
}

View File

@ -0,0 +1,40 @@
.left-bar {
grid-area: lb;
max-width: 300px;
}
.main-bar {
grid-area: main;
}
.right-bar {
grid-area: rb;
max-width: 300px;
}
.column-layout-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-areas: "main" "rb";
}
@media (min-width: 768px) {
.right-bar {
max-width: 100%;
}
.column-layout-grid {
display: grid;
grid-template-columns: minmax(300px, 1fr);
grid-template-areas:
"lb main"
"lb rb";
}
}
@media (min-width: 1280px) {
.column-layout-grid {
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-areas: "lb main rb";
}
}

View File

@ -0,0 +1,123 @@
import React, {
Fragment,
useState,
useRef,
useEffect,
FC,
isValidElement,
} from "react";
import { Dialog, Transition } from "@headlessui/react";
import { BottomSheetModal } from "components/containers/modal/BottomSheetModal";
import { BottomBarAcceptCookies } from "components/containers/modal/BottomBarAcceptCookies";
import { Button } from "components/Button/Button";
import classNames from "classnames";
import "./ColumnLayout.css";
import { Children } from "react";
import MainColumn from "./Maincolumn";
import LeftColumn from "./LeftColumn";
import RightColumn from "./RightColumn";
/* -------------------------------------------------------------------------- */
/* Custom hook to track container width */
/* -------------------------------------------------------------------------- */
const useScreenWidth = () => {
const ref: any = useRef();
const [width, setWidth] = useState<null | number>(null);
const observer = useRef(
new ResizeObserver((entries) => {
const { width } = entries[0].contentRect;
setWidth(width);
})
);
useEffect(() => {
observer.current.observe(ref.current);
}, [ref, observer]);
return [ref, width];
};
/* -------------------------------------------------------------------------- */
/* Component extentions */
/* -------------------------------------------------------------------------- */
type ColumnExtentions = {
Left: React.FC<{
children: React.ReactNode;
openLeftBar?: boolean;
widthElement?: number;
className?: string;
}>;
Main: React.FC<{
children: React.ReactNode;
className?: string;
}>;
Right: React.FC<{
children: React.ReactNode;
className?: string;
}>;
};
/* -------------------------------------------------------------------------- */
/* Component properties */
/* -------------------------------------------------------------------------- */
type ColumnLayoutProps = {
children: React.ReactNode; //Column layout gets as children not more than three children
} & Omit<React.ComponentPropsWithRef<"div">, "">;
/* -------------------------------------------------------------------------- */
/* Component function */
/* -------------------------------------------------------------------------- */
export const ColumnLayout: React.FC<ColumnLayoutProps> & ColumnExtentions = ({
children,
}) => {
const mdScreen = 768;
//Hooks
const [ref, widthElement] = useScreenWidth(); // to track width of screen
const [openLeftBar, setOpenLeftBar] = useState(false); //to open or close left bar
function leftBar() {
return setOpenLeftBar(!openLeftBar);
}
// Change openLeftBar when width of screen > 768
useEffect(() => {
if (widthElement > mdScreen) {
setOpenLeftBar(false);
}
});
// TODO
const amountChildren = React.Children.count(children);
if (amountChildren > 3) {
alert("Layout gets only 3 or lesser children");
}
const columns = React.Children.map(children, (child) => {
if (
child &&
React.isValidElement(child) &&
React.Children.only(child).type === LeftColumn
) {
return React.cloneElement(child, {
openLeftBar: openLeftBar,
widthElement: widthElement,
});
} else {
return child;
}
});
return (
<div ref={ref} className="flex">
<div className="w-full px-2 xl:px-12 column-layout-grid">
{amountChildren <= 3 ? columns : undefined}
</div>
</div>
);
};
ColumnLayout.Left = LeftColumn;
ColumnLayout.Main = MainColumn;
ColumnLayout.Right = RightColumn;

View File

@ -0,0 +1,49 @@
import React, { Fragment, FunctionComponent as FC } from "react";
import { Transition } from "@headlessui/react";
import classNames from "classnames";
type LeftColumnProps = {
children: React.ReactNode;
openLeftBar?: boolean;
widthElement?: number;
className?: string;
} & Omit<React.ComponentPropsWithoutRef<"div">, "">;
const LeftColumn: React.FC<LeftColumnProps> = ({
className,
children,
widthElement = 0,
openLeftBar = false,
}): JSX.Element => {
const mdScreen = 768;
return (
<Transition
appear
as={Fragment}
show={widthElement < mdScreen ? openLeftBar : true}
enter={classNames(
widthElement < mdScreen ? "ease-in-out duration-150" : "transition-none"
)}
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="ease-in-out duration-150"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div
className={classNames(
"h-full left-bar",
{
"fixed inset-0 w-80": widthElement < mdScreen,
},
className
)}
>
{children}
</div>
</Transition>
);
};
LeftColumn.displayName = "LeftColumn";
export default LeftColumn;

View File

@ -0,0 +1,18 @@
import classNames from "classnames";
import React from "react";
type MainColumnProps = {
children: React.ReactNode;
className?: string;
};
function MainColumn({ children, className }: MainColumnProps) {
return (
<div className={classNames("h-full main-bar overflow-auto", className)}>
{children}
</div>
);
}
export default MainColumn;
MainColumn.displayName = "MainColumn";

View File

@ -0,0 +1,18 @@
import classNames from "classnames";
import React from "react";
type RightColumnProps = {
children: React.ReactNode;
className?: string;
};
function RightColumn({ children, className }: RightColumnProps) {
return (
<div className={classNames("h-full right-bar overflow-auto", className)}>
{children}
</div>
);
}
export default RightColumn;
RightColumn.displayName = "RightColumn";