mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 20:01:59 +00:00
Add table and menu components
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledMessage = styled.p`
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Empty = ({ children }: { children: string }) => {
|
||||||
|
return <StyledMessage>{children}</StyledMessage>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Empty;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import { HiEllipsisVertical } from 'react-icons/hi2';
|
||||||
|
import { useOutsideClick } from '../../hooks/useOutsideClick';
|
||||||
|
import { StyledButton, StyledList, StyledMenu, StyledToggle } from './styled';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
|
import { IButton } from '../../globals/interfaces';
|
||||||
|
|
||||||
|
// Contexts
|
||||||
|
import MenusContext from '../../contexts/MenuContext';
|
||||||
|
|
||||||
|
const Menus = ({ children }: { children: JSX.Element }) => {
|
||||||
|
const [openId, setOpenId] = useState('');
|
||||||
|
|
||||||
|
const close = () => setOpenId('');
|
||||||
|
const open = setOpenId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenusContext.Provider value={{ openId, close, open }}>
|
||||||
|
{children}
|
||||||
|
</MenusContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Toggle = ({ id }: { id: string }): React.JSX.Element => {
|
||||||
|
const { openId, close, open } = useContext(MenusContext);
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
openId === '' || openId !== id ? open!(id) : close!();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledToggle onClick={(e) => handleClick(e)}>
|
||||||
|
<HiEllipsisVertical />
|
||||||
|
</StyledToggle>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const List = ({
|
||||||
|
id,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
children: JSX.Element[];
|
||||||
|
}): React.JSX.Element | null => {
|
||||||
|
const { openId, close } = useContext(MenusContext);
|
||||||
|
const ref = useOutsideClick(close!, false);
|
||||||
|
|
||||||
|
if (openId !== id) return null;
|
||||||
|
|
||||||
|
return <StyledList ref={ref}>{children}</StyledList>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Button = ({ children, icon, onClick }: IButton): React.JSX.Element => {
|
||||||
|
const { close } = useContext(MenusContext);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
onClick?.();
|
||||||
|
close!();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<StyledButton onClick={handleClick}>
|
||||||
|
{icon} <span>{children}</span>
|
||||||
|
</StyledButton>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Menus.Menu = StyledMenu;
|
||||||
|
Menus.Toggle = Toggle;
|
||||||
|
Menus.List = List;
|
||||||
|
Menus.Button = Button;
|
||||||
|
|
||||||
|
export default Menus;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const StyledMenu = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledButton = styled.button`
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--hover-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
color: var(--primary-color);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledToggle = styled.button`
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transform: translateX(5px);
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--hover-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledList = styled.ul`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
|
||||||
|
right: -10px;
|
||||||
|
top: 40px;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { ReactNode, useContext } from 'react';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
|
import { ITable, ITableBody } from '../../globals/interfaces';
|
||||||
|
|
||||||
|
// Contexts
|
||||||
|
import TableContext from '../../contexts/TableContext';
|
||||||
|
|
||||||
|
type CallbackMapFunc<T> = (value: T, index: number, array: T[]) => ReactNode;
|
||||||
|
|
||||||
|
const Table = ({ columns, children }: ITable) => {
|
||||||
|
return (
|
||||||
|
<TableContext.Provider value={columns!}>
|
||||||
|
<StyledTable>{children}</StyledTable>
|
||||||
|
</TableContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ children }: ITable) => {
|
||||||
|
const columns = useContext(TableContext);
|
||||||
|
return <StyledHeader columns={columns}>{children}</StyledHeader>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Body = <T,>({ data, render }: ITableBody<T>) => {
|
||||||
|
if (data && data.length > 0)
|
||||||
|
return <StyledBody>{data?.map(render as CallbackMapFunc<T>)}</StyledBody>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Row = ({ children }: ITable) => {
|
||||||
|
const columns = useContext(TableContext);
|
||||||
|
return <StyledRow columns={columns}>{children}</StyledRow>;
|
||||||
|
};
|
||||||
|
|
||||||
|
Table.Header = Header;
|
||||||
|
Table.Body = Body;
|
||||||
|
Table.Row = Row;
|
||||||
|
|
||||||
|
export default Table;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
|
import { ITable } from '../../globals/interfaces';
|
||||||
|
|
||||||
|
export const StyledTable = styled.div`
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CommonRow = styled.div<ITable>`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: ${(props) => props.columns};
|
||||||
|
column-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledBody = styled.div`
|
||||||
|
font-size: var(--fs-sm-x);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledRow = styled(CommonRow)`
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledHeader = styled(CommonRow)`
|
||||||
|
padding: 10px 20px;
|
||||||
|
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
background-color: var(--header-table-color);
|
||||||
|
border-top-left-radius: var(--radius-md);
|
||||||
|
border-top-right-radius: var(--radius-md);
|
||||||
|
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
// Interfaces
|
||||||
|
import { IMenusContext } from '../globals/interfaces';
|
||||||
|
|
||||||
|
const MenusContext = createContext<IMenusContext>({});
|
||||||
|
|
||||||
|
export default MenusContext;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
const TableContext = createContext('');
|
||||||
|
|
||||||
|
export default TableContext;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export interface IMenusContext {
|
||||||
|
openId?: string;
|
||||||
|
close?: () => void;
|
||||||
|
open?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IButton {
|
||||||
|
children?: string;
|
||||||
|
icon?: JSX.Element;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITable {
|
||||||
|
columns?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITableBody<T> {
|
||||||
|
data?: T[];
|
||||||
|
render?: (value: T) => JSX.Element;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type TUser = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
identifiedCode: string;
|
||||||
|
address: string;
|
||||||
|
phone: string;
|
||||||
|
roomId: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const useOutsideClick = (
|
||||||
|
handler: () => void,
|
||||||
|
listeningCapturing = true,
|
||||||
|
): React.MutableRefObject<HTMLUListElement | null> => {
|
||||||
|
const ref = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClick = (e: Event) => {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('click', handleClick, listeningCapturing);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
document.removeEventListener('click', handleClick, listeningCapturing);
|
||||||
|
}, [handler, listeningCapturing]);
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user