Skip to content

Commit

Permalink
Add context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalKowalczyk committed Oct 7, 2023
1 parent f9af3a9 commit 56f1eb1
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 16 deletions.
34 changes: 20 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeebo-react-grid",
"version": "1.2.2",
"version": "1.3.0",
"description": "React Grid Components - Row, Col, Grid, Container",
"repository": "MichalKowalczyk/codeebo-react-grid",
"license": "MIT",
Expand All @@ -26,6 +26,7 @@
"start": "rollup -c -w"
},
"dependencies": {
"react-detect-click-outside": "^1.1.7",
"react-fast-compare": "^3.2.0"
},
"peerDependencies": {
Expand Down
22 changes: 22 additions & 0 deletions src/components/action-btn-wrapper/action-btn-wrapper.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.crg.action-btn-wrapper {
border-radius: 6px;
display: inline-flex;
align-items: center;
cursor: pointer;
padding: 10px 8px 11px;
margin: 0px 4px;
user-select: none;

&.default-border {
box-shadow: inset 0px 0px 0px 1px var(--color-border-light);
}
&:hover {
box-shadow: inset 0px 0px 0px 1px var(--color-fonts);
}

&.action-btn-wrapper--disabled {
pointer-events: none !important;
color: var(--color-fonts-inactive);
box-shadow: inset 0px 0px 0px 1px var(--color-border-lighter);
}
}
22 changes: 22 additions & 0 deletions src/components/action-btn-wrapper/action-btn-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";
import "./action-btn-wrapper.scss";

interface Props {
onClick?: () => void;
children: React.ReactNode;
defaultBorder?: boolean;
disabled?: boolean;
}

const ActionBtnWrapper: React.FC<Props> = (props: Props) => {
return (
<div
className={`crg action-btn-wrapper ${props.defaultBorder ? "default-border" : ""} ${props.disabled ? "action-btn-wrapper--disabled" : ""}`}
onClick={props.onClick}
>
{props.children}
</div>
);
};

export default ActionBtnWrapper;
52 changes: 52 additions & 0 deletions src/components/menu/menu.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.crg.select-menu {
position: relative;
user-select: none;
float: right;

.action-btn {
padding: 4px 1px;
span {
display: block;
transform: scaleX(1.4) translateY(1px);
line-height: 0;
font-size: 1.4rem;
pointer-events: none;
}
}

.summary--disabled {
pointer-events: none !important;
}

.select-menu__modal {
border: 1px solid var(--color-border-lighter);
background: var(--color-bg-box);
position: absolute;
border-radius: 6px;
right: 4px;
z-index: 9990;

display: none;
&.select-menu__modal--open {
display: block;
}

.select-menu__modal__header {
font-size: 1.2rem;
font-weight: 600;
padding: 8px 10px;
border-bottom: 1px solid var(--color-border-lighter);
}
.select-menu__modal__items {
padding: 4px 0px;
}
.select-menu__modal__item {
font-size: 1.3rem;
padding: 8px 10px;
cursor: pointer;
&:hover {
background: var(--color-primary-10a);
}
}
}
}
105 changes: 105 additions & 0 deletions src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useRef, useState } from "react";
import ActionBtnWrapper from "../action-btn-wrapper/action-btn-wrapper";
import { useDetectClickOutside } from "react-detect-click-outside";
import "./menu.scss";

export interface ItemInput {
label: string | React.ReactNode;
customRender?: React.ReactNode;
onAction: () => any;
}
export type MenuItemType = ItemInput | { customRender: React.ReactNode } | null;

interface Props {
items: Array<MenuItemType>;
children?: React.ReactNode;
minWidth?: number;
header?: string;
isPrimary?: boolean;
disabled?: boolean;
}

const Menu: React.FC<Props> = (props: Props) => {
const { items, children, disabled = undefined, minWidth, header, isPrimary } = props;
const filteredNullItems = items.filter((n) => n) as Array<ItemInput>;
const [isOpen, setIsOpen] = useState<boolean>(false);

const open = () => {
if (isOpen) setIsOpen(false);
else {
// e.stopPropagation();
setIsOpen(true);
}
};

return filteredNullItems.length > 0 ? (
<div className={`select-menu`}>
<div>
<summary className={disabled ? "summary--disabled" : ""} onClick={open}>
{children ? (
<>{children}</>
) : (
<ActionBtnWrapper defaultBorder disabled={disabled}>
<div className={`action-btn ${isPrimary ? "primary" : ""}`}>
<span>&#9776;</span>
</div>
</ActionBtnWrapper>
)}
</summary>
{isOpen ? <ActionListWrapper isOpen={isOpen} items={filteredNullItems} setIsOpen={setIsOpen} header={header} minWidth={minWidth} /> : null}
</div>
</div>
) : (
<></>
);
};

export default Menu;

interface ActionListWrapperProps {
items: ItemInput[];
minWidth?: number;
header?: string;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
isOpen: boolean;
}

const ActionListWrapper: React.FC<ActionListWrapperProps> = (props: ActionListWrapperProps) => {
const { items, setIsOpen, minWidth, header, isOpen } = props;
const mounted: any = useRef({
isMounted: false,
});

const ref: any = useDetectClickOutside({
onTriggered: () => {
if (mounted.current.isMounted) setIsOpen((state) => (state === true ? false : true));
else mounted.current.isMounted = true;
},
});

return (
<div
className={`crg select-menu__modal ${isOpen ? "select-menu__modal--open" : ""}`}
style={{ minWidth: minWidth ? minWidth : "128px", width: "auto" }}
ref={ref}
>
{header && <div className="select-menu__modal__header">{header}</div>}
<div onClick={() => setIsOpen(false)} className="select-menu__modal__items">
{items.map((x: ItemInput, index: number) => {
return (
<div key={index}>
{x.customRender ? x.customRender : null}
{x.onAction ? (
<div className="select-menu__modal__item" onClick={x.onAction}>
<div>{x.label}</div>
</div>
) : (
<></>
)}
</div>
);
})}
</div>
</div>
);
};
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export { default as Grid } from "./components/grid/grid";
export { default as Row } from "./components/row/row";
export { default as Col } from "./components/col/col";
export { default as GenericList } from "./components/generic-list/generic-list";

export { default as Menu } from "./components/menu/menu";
export { default as ActionBtnWrapper } from "./components/menu/menu";
1 change: 1 addition & 0 deletions src/styles/_style.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@use "vars";
@use "flex";
@use "spacings";
Loading

0 comments on commit 56f1eb1

Please sign in to comment.