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:
commit
040fecbcbf
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
40
src/components/layouts/ThreeColumn/ColumnLayout.css
Normal file
40
src/components/layouts/ThreeColumn/ColumnLayout.css
Normal 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";
|
||||
}
|
||||
}
|
123
src/components/layouts/ThreeColumn/ColumnLayout.tsx
Normal file
123
src/components/layouts/ThreeColumn/ColumnLayout.tsx
Normal 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;
|
49
src/components/layouts/ThreeColumn/LeftColumn.tsx
Normal file
49
src/components/layouts/ThreeColumn/LeftColumn.tsx
Normal 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;
|
18
src/components/layouts/ThreeColumn/Maincolumn.tsx
Normal file
18
src/components/layouts/ThreeColumn/Maincolumn.tsx
Normal 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";
|
18
src/components/layouts/ThreeColumn/RightColumn.tsx
Normal file
18
src/components/layouts/ThreeColumn/RightColumn.tsx
Normal 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";
|
Loading…
x
Reference in New Issue
Block a user