feature: add more to project pane
This commit is contained in:
@ -4,25 +4,51 @@ import { Checkmark } from 'shared/icons';
|
||||
import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldName } from './Styles';
|
||||
|
||||
type Props = {
|
||||
label: Label;
|
||||
onLabelEdit: (labelId: string, labelName: string, color: string) => void;
|
||||
label: Label | null;
|
||||
onLabelEdit: (labelId: string | null, labelName: string, color: string) => void;
|
||||
};
|
||||
|
||||
const LabelManager = ({ label, onLabelEdit }: Props) => {
|
||||
const [currentLabel, setCurrentLabel] = useState('');
|
||||
console.log(label);
|
||||
const [currentLabel, setCurrentLabel] = useState(label ? label.name : '');
|
||||
const [currentColor, setCurrentColor] = useState<string | null>(label ? label.color : null);
|
||||
return (
|
||||
<EditLabelForm>
|
||||
<FieldLabel>Name</FieldLabel>
|
||||
<FieldName id="labelName" type="text" name="name" value={currentLabel} />
|
||||
<FieldName
|
||||
id="labelName"
|
||||
type="text"
|
||||
name="name"
|
||||
onChange={e => {
|
||||
setCurrentLabel(e.currentTarget.value);
|
||||
}}
|
||||
value={currentLabel}
|
||||
/>
|
||||
<FieldLabel>Select a color</FieldLabel>
|
||||
<div>
|
||||
{Object.values(LabelColors).map(labelColor => (
|
||||
<LabelBox color={labelColor}>
|
||||
<Checkmark color="#fff" size={12} />
|
||||
<LabelBox
|
||||
color={labelColor}
|
||||
onClick={() => {
|
||||
setCurrentColor(labelColor);
|
||||
}}
|
||||
>
|
||||
{labelColor === currentColor && <Checkmark color="#fff" size={12} />}
|
||||
</LabelBox>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<SaveButton type="submit" value="Save" />
|
||||
<SaveButton
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
console.log(currentColor);
|
||||
if (currentColor) {
|
||||
onLabelEdit(label ? label.labelId : null, currentLabel, currentColor);
|
||||
}
|
||||
}}
|
||||
type="submit"
|
||||
value="Save"
|
||||
/>
|
||||
<DeleteButton type="submit" value="Delete" />
|
||||
</div>
|
||||
</EditLabelForm>
|
||||
|
@ -1,46 +1,78 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Pencil, Checkmark } from 'shared/icons';
|
||||
|
||||
import { LabelSearch, ActiveIcon, Labels, Label, CardLabel, Section, SectionTitle, LabelIcon } from './Styles';
|
||||
import {
|
||||
LabelSearch,
|
||||
ActiveIcon,
|
||||
Labels,
|
||||
Label,
|
||||
CardLabel,
|
||||
Section,
|
||||
SectionTitle,
|
||||
LabelIcon,
|
||||
CreateLabelButton,
|
||||
} from './Styles';
|
||||
|
||||
type Props = {
|
||||
labels?: Label[];
|
||||
onLabelToggle: (labelId: string) => void;
|
||||
onLabelEdit: (labelId: string, labelName: string, color: string) => void;
|
||||
onLabelEdit: (labelId: string) => void;
|
||||
onLabelCreate: () => void;
|
||||
};
|
||||
const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit }) => {
|
||||
const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
|
||||
const [currentLabel, setCurrentLabel] = useState('');
|
||||
const [currentSearch, setCurrentSearch] = useState('');
|
||||
return (
|
||||
<>
|
||||
<LabelSearch type="text" />
|
||||
<LabelSearch
|
||||
type="text"
|
||||
placeholder="search labels..."
|
||||
onChange={e => {
|
||||
setCurrentSearch(e.currentTarget.value);
|
||||
}}
|
||||
value={currentSearch}
|
||||
/>
|
||||
<Section>
|
||||
<SectionTitle>Labels</SectionTitle>
|
||||
<Labels>
|
||||
{labels &&
|
||||
labels.map(label => (
|
||||
<Label>
|
||||
<LabelIcon>
|
||||
<Pencil />
|
||||
</LabelIcon>
|
||||
<CardLabel
|
||||
key={label.labelId}
|
||||
color={label.color}
|
||||
active={currentLabel === label.labelId}
|
||||
onMouseEnter={() => {
|
||||
setCurrentLabel(label.labelId);
|
||||
}}
|
||||
onClick={() => onLabelToggle(label.labelId)}
|
||||
>
|
||||
{label.name}
|
||||
{label.active && (
|
||||
<ActiveIcon>
|
||||
<Checkmark color="#fff" />
|
||||
</ActiveIcon>
|
||||
)}
|
||||
</CardLabel>
|
||||
</Label>
|
||||
))}
|
||||
labels
|
||||
.filter(label => currentSearch === '' || label.name.toLowerCase().startsWith(currentSearch.toLowerCase()))
|
||||
.map(label => (
|
||||
<Label key={label.labelId}>
|
||||
<LabelIcon
|
||||
onClick={() => {
|
||||
onLabelEdit(label.labelId);
|
||||
}}
|
||||
>
|
||||
<Pencil color="#c2c6dc" />
|
||||
</LabelIcon>
|
||||
<CardLabel
|
||||
key={label.labelId}
|
||||
color={label.color}
|
||||
active={currentLabel === label.labelId}
|
||||
onMouseEnter={() => {
|
||||
setCurrentLabel(label.labelId);
|
||||
}}
|
||||
onClick={() => onLabelToggle(label.labelId)}
|
||||
>
|
||||
{label.name}
|
||||
{label.active && (
|
||||
<ActiveIcon>
|
||||
<Checkmark color="#fff" />
|
||||
</ActiveIcon>
|
||||
)}
|
||||
</CardLabel>
|
||||
</Label>
|
||||
))}
|
||||
</Labels>
|
||||
<CreateLabelButton
|
||||
onClick={() => {
|
||||
onLabelCreate();
|
||||
}}
|
||||
>
|
||||
Create a new label
|
||||
</CreateLabelButton>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import React, { useState, useRef, createRef } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import LabelColors from 'shared/constants/labelColors';
|
||||
import LabelManager from 'shared/components/PopupMenu/LabelManager';
|
||||
@ -7,10 +7,12 @@ import ListActions from 'shared/components/ListActions';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import DueDateManager from 'shared/components/DueDateManager';
|
||||
import MiniProfile from 'shared/components/MiniProfile';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import PopupMenu from '.';
|
||||
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
import BaseStyles from 'App/BaseStyles';
|
||||
import produce from 'immer';
|
||||
|
||||
export default {
|
||||
component: PopupMenu,
|
||||
@ -37,19 +39,93 @@ const labelData = [
|
||||
},
|
||||
];
|
||||
|
||||
const OpenLabelBtn = styled.span``;
|
||||
|
||||
type TabProps = {
|
||||
tab: number;
|
||||
};
|
||||
|
||||
const LabelManagerEditor = () => {
|
||||
const [labels, setLabels] = useState(labelData);
|
||||
const [currentLabel, setCurrentLabel] = useState('');
|
||||
const { setTab } = usePopup();
|
||||
return (
|
||||
<>
|
||||
<Popup title="Labels" tab={0} onClose={action('on close')}>
|
||||
<LabelManager
|
||||
labels={labels}
|
||||
onLabelCreate={() => {
|
||||
setTab(2);
|
||||
}}
|
||||
onLabelEdit={labelId => {
|
||||
setCurrentLabel(labelId);
|
||||
setTab(1);
|
||||
}}
|
||||
onLabelToggle={labelId => {
|
||||
setLabels(
|
||||
produce(labels, draftState => {
|
||||
const idx = labels.findIndex(label => label.labelId === labelId);
|
||||
if (idx !== -1) {
|
||||
draftState[idx] = { ...draftState[idx], active: !labels[idx].active };
|
||||
}
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Popup>
|
||||
<Popup onClose={action('on close')} title="Edit label" tab={1}>
|
||||
<LabelEditor
|
||||
label={labels.find(label => label.labelId === currentLabel) ?? null}
|
||||
onLabelEdit={(_labelId, name, color) => {
|
||||
setLabels(
|
||||
produce(labels, draftState => {
|
||||
const idx = labels.findIndex(label => label.labelId === currentLabel);
|
||||
if (idx !== -1) {
|
||||
draftState[idx] = { ...draftState[idx], name, color };
|
||||
}
|
||||
}),
|
||||
);
|
||||
setTab(0);
|
||||
}}
|
||||
/>
|
||||
</Popup>
|
||||
<Popup onClose={action('on close')} title="Create new label" tab={2}>
|
||||
<LabelEditor
|
||||
label={null}
|
||||
onLabelEdit={(_labelId, name, color) => {
|
||||
setLabels([...labels, { labelId: name, name, color, active: false }]);
|
||||
setTab(0);
|
||||
}}
|
||||
/>
|
||||
</Popup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenLabelsButton = () => {
|
||||
const $buttonRef = createRef<HTMLButtonElement>();
|
||||
const [currentLabel, setCurrentLabel] = useState('');
|
||||
const [labels, setLabels] = useState(labelData);
|
||||
const { showPopup, setTab } = usePopup();
|
||||
console.log(labels);
|
||||
return (
|
||||
<OpenLabelBtn
|
||||
ref={$buttonRef}
|
||||
onClick={() => {
|
||||
showPopup($buttonRef, <LabelManagerEditor />);
|
||||
}}
|
||||
>
|
||||
Open
|
||||
</OpenLabelBtn>
|
||||
);
|
||||
};
|
||||
|
||||
export const LabelsPopup = () => {
|
||||
const [isPopupOpen, setPopupOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{isPopupOpen && (
|
||||
<PopupMenu title="Label" top={10} onClose={() => setPopupOpen(false)} left={10}>
|
||||
<LabelManager labels={labelData} onLabelToggle={action('label toggle')} onLabelEdit={action('label edit')} />
|
||||
</PopupMenu>
|
||||
)}
|
||||
<button type="submit" onClick={() => setPopupOpen(true)}>
|
||||
Open
|
||||
</button>
|
||||
</>
|
||||
<PopupProvider>
|
||||
<OpenLabelsButton />
|
||||
</PopupProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -58,7 +134,13 @@ export const LabelsLabelEditor = () => {
|
||||
return (
|
||||
<>
|
||||
{isPopupOpen && (
|
||||
<PopupMenu title="Change Label" top={10} onClose={() => setPopupOpen(false)} left={10}>
|
||||
<PopupMenu
|
||||
onPrevious={action('on previous')}
|
||||
title="Change Label"
|
||||
top={10}
|
||||
onClose={() => setPopupOpen(false)}
|
||||
left={10}
|
||||
>
|
||||
<LabelEditor label={labelData[0]} onLabelEdit={action('label edit')} />
|
||||
</PopupMenu>
|
||||
)}
|
||||
@ -201,12 +283,19 @@ export const MiniProfilePopup = () => {
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
{popupData.isOpen && (
|
||||
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
||||
<PopupMenu
|
||||
noHeader
|
||||
title="Due Date"
|
||||
top={popupData.top}
|
||||
onClose={() => setPopupData(initalState)}
|
||||
left={popupData.left}
|
||||
>
|
||||
<MiniProfile
|
||||
displayName="Jordan Knott"
|
||||
profileIcon={{ url: null, bgColor: '#000', initials: 'JK' }}
|
||||
username="@jordanthedev"
|
||||
bio="Stuff and things"
|
||||
onRemoveFromTask={action('mini profile')}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
@ -236,3 +325,4 @@ export const MiniProfilePopup = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,20 +1,34 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
|
||||
export const Container = styled.div<{ top: number; left: number; ref: any }>`
|
||||
export const Container = styled.div<{ invert: boolean; top: number; left: number; ref: any }>`
|
||||
left: ${props => props.left}px;
|
||||
top: ${props => props.top}px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 8px 16px -4px rgba(9, 30, 66, 0.25), 0 0 0 1px rgba(9, 30, 66, 0.08);
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 304px;
|
||||
z-index: 100000000000;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
width: 316px;
|
||||
padding-top: 10px;
|
||||
height: auto;
|
||||
z-index: 40000;
|
||||
${props =>
|
||||
props.invert &&
|
||||
css`
|
||||
transform: translate(-100%);
|
||||
`}
|
||||
`;
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
padding: 5px;
|
||||
padding-top: 8px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 25px 0 rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
color: #c2c6dc;
|
||||
background: #262c49;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-color: #414561;
|
||||
`;
|
||||
|
||||
export const Header = styled.div`
|
||||
@ -26,10 +40,10 @@ export const Header = styled.div`
|
||||
|
||||
export const HeaderTitle = styled.span`
|
||||
box-sizing: border-box;
|
||||
color: #5e6c84;
|
||||
color: #c2c6dc;
|
||||
display: block;
|
||||
line-height: 40px;
|
||||
border-bottom: 1px solid rgba(9, 30, 66, 0.13);
|
||||
border-bottom: 1px solid #414561;
|
||||
margin: 0 12px;
|
||||
overflow: hidden;
|
||||
padding: 0 32px;
|
||||
@ -46,23 +60,30 @@ export const Content = styled.div`
|
||||
padding: 0 12px 12px;
|
||||
`;
|
||||
export const LabelSearch = styled.input`
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 85ms;
|
||||
transition-timing-function: ease;
|
||||
margin: 4px 0 12px;
|
||||
width: 100%;
|
||||
background-color: #fafbfc;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 2px #dfe1e6;
|
||||
color: #172b4d;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
font-family: 'Droid Sans';
|
||||
font-weight: 400;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 85ms;
|
||||
transition-timing-function: ease;
|
||||
|
||||
background: #262c49;
|
||||
outline: none;
|
||||
color: #c2c6dc;
|
||||
border-color: #414561;
|
||||
|
||||
&:focus {
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Section = styled.div`
|
||||
@ -70,7 +91,7 @@ export const Section = styled.div`
|
||||
`;
|
||||
|
||||
export const SectionTitle = styled.h4`
|
||||
color: #5e6c84;
|
||||
color: #c2c6dc;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
@ -95,7 +116,7 @@ export const CardLabel = styled.span<{ active: boolean; color: string }>`
|
||||
props.active &&
|
||||
css`
|
||||
margin-left: 4px;
|
||||
box-shadow: -8px 0 ${mixin.darken(props.color, 0.15)};
|
||||
box-shadow: -8px 0 ${mixin.darken(props.color, 0.12)};
|
||||
border-radius: 3px;
|
||||
`}
|
||||
|
||||
@ -113,6 +134,7 @@ export const CardLabel = styled.span<{ active: boolean; color: string }>`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-height: 31px;
|
||||
`;
|
||||
|
||||
export const CloseButton = styled.div`
|
||||
@ -126,8 +148,6 @@ export const CloseButton = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 40;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
@ -142,14 +162,14 @@ export const LabelIcon = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 20px;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
width: 20px;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(9, 30, 66, 0.08);
|
||||
background: rgb(115, 103, 240);
|
||||
}
|
||||
`;
|
||||
|
||||
@ -186,19 +206,27 @@ export const FieldLabel = styled.label`
|
||||
export const FieldName = styled.input`
|
||||
margin: 4px 0 12px;
|
||||
width: 100%;
|
||||
background-color: #fafbfc;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 2px #dfe1e6;
|
||||
color: #172b4d;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background: #262c49;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-image: initial;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #c2c6dc;
|
||||
|
||||
&:focus {
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const LabelBox = styled.span<{ color: string }>`
|
||||
@ -208,6 +236,7 @@ export const LabelBox = styled.span<{ color: string }>`
|
||||
padding: 0;
|
||||
width: 48px;
|
||||
|
||||
cursor: pointer;
|
||||
background-color: ${props => props.color};
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
@ -217,6 +246,7 @@ export const LabelBox = styled.span<{ color: string }>`
|
||||
`;
|
||||
|
||||
export const SaveButton = styled.input`
|
||||
cursor: pointer;
|
||||
background-color: #5aac44;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
@ -239,8 +269,7 @@ export const DeleteButton = styled.input`
|
||||
border: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
type="submit"font-weight: 400;
|
||||
line-height: 20px;
|
||||
margin: 8px 4px 0 0;
|
||||
padding: 6px 12px;
|
||||
@ -248,3 +277,53 @@ export const DeleteButton = styled.input`
|
||||
border-radius: 3px;
|
||||
float: right;
|
||||
`;
|
||||
|
||||
export const CreateLabelButton = styled.button`
|
||||
outline: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 12px;
|
||||
background-color: none;
|
||||
text-align: center;
|
||||
color: #c2c6dc;
|
||||
margin: 8px 4px 0 0;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgb(115, 103, 240);
|
||||
}
|
||||
`;
|
||||
|
||||
export const PreviousButton = styled.div`
|
||||
padding: 10px 12px 10px 8px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 40;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const ContainerDiamond = styled.div<{ invert: boolean }>`
|
||||
top: 10px;
|
||||
${props => (props.invert ? 'right: 10px; ' : 'left: 15px;')}
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: block;
|
||||
transform: rotate(45deg) translate(-7px);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
|
||||
background: #262c49;
|
||||
border-color: #414561;
|
||||
`;
|
||||
|
@ -1,30 +1,219 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Cross } from 'shared/icons';
|
||||
import React, { useRef, createContext, RefObject, useState, useContext } from 'react';
|
||||
import { Cross, AngleLeft } from 'shared/icons';
|
||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||
import { Container, Header, HeaderTitle, Content, CloseButton } from './Styles';
|
||||
import { createPortal } from 'react-dom';
|
||||
import produce from 'immer';
|
||||
import {
|
||||
Container,
|
||||
ContainerDiamond,
|
||||
Header,
|
||||
HeaderTitle,
|
||||
Content,
|
||||
CloseButton,
|
||||
PreviousButton,
|
||||
Wrapper,
|
||||
} from './Styles';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
type PopupContextState = {
|
||||
show: (target: RefObject<HTMLElement>, content: JSX.Element) => void;
|
||||
setTab: (newTab: number) => void;
|
||||
getCurrentTab: () => number;
|
||||
};
|
||||
|
||||
type PopupProps = {
|
||||
title: string | null;
|
||||
onClose: () => void;
|
||||
tab: number;
|
||||
};
|
||||
|
||||
type PopupContainerProps = {
|
||||
top: number;
|
||||
left: number;
|
||||
invert: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const PopupMenu: React.FC<Props> = ({ title, top, left, onClose, children }) => {
|
||||
const PopupContainer: React.FC<PopupContainerProps> = ({ top, left, onClose, children, invert }) => {
|
||||
const $containerRef = useRef();
|
||||
useOnOutsideClick($containerRef, true, onClose, null);
|
||||
return (
|
||||
<Container left={left} top={top} ref={$containerRef} invert={invert}>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
const PopupContext = createContext<PopupContextState>({
|
||||
show: () => {},
|
||||
setTab: () => {},
|
||||
getCurrentTab: () => 0,
|
||||
});
|
||||
|
||||
export const usePopup = () => {
|
||||
const ctx = useContext<PopupContextState>(PopupContext);
|
||||
return { showPopup: ctx.show, setTab: ctx.setTab, getCurrentTab: ctx.getCurrentTab };
|
||||
};
|
||||
|
||||
type PopupState = {
|
||||
isOpen: boolean;
|
||||
left: number;
|
||||
top: number;
|
||||
invert: boolean;
|
||||
currentTab: number;
|
||||
previousTab: number;
|
||||
content: JSX.Element | null;
|
||||
};
|
||||
|
||||
const { Provider, Consumer } = PopupContext;
|
||||
|
||||
const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
|
||||
|
||||
const defaultState = {
|
||||
isOpen: false,
|
||||
left: 0,
|
||||
top: 0,
|
||||
invert: false,
|
||||
currentTab: 0,
|
||||
previousTab: 0,
|
||||
content: null,
|
||||
};
|
||||
|
||||
export const PopupProvider: React.FC = ({ children }) => {
|
||||
const [currentState, setState] = useState<PopupState>(defaultState);
|
||||
const show = (target: RefObject<HTMLElement>, content: JSX.Element) => {
|
||||
console.log(target);
|
||||
if (target && target.current) {
|
||||
const bounds = target.current.getBoundingClientRect();
|
||||
if (bounds.left + 304 + 30 > window.innerWidth) {
|
||||
console.log('open!');
|
||||
setState({
|
||||
isOpen: true,
|
||||
left: bounds.left + bounds.width,
|
||||
top: bounds.top + bounds.height,
|
||||
invert: true,
|
||||
currentTab: 0,
|
||||
previousTab: 0,
|
||||
content,
|
||||
});
|
||||
} else {
|
||||
console.log('open NOT INVERT!');
|
||||
setState({
|
||||
isOpen: true,
|
||||
left: bounds.left,
|
||||
top: bounds.top + bounds.height,
|
||||
invert: false,
|
||||
currentTab: 0,
|
||||
previousTab: 0,
|
||||
content,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const portalTarget = canUseDOM ? document.body : null; // appease flow
|
||||
|
||||
const setTab = (newTab: number) => {
|
||||
setState((prevState: PopupState) => {
|
||||
return {
|
||||
...prevState,
|
||||
previousTab: currentState.currentTab,
|
||||
currentTab: newTab,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTab = () => {
|
||||
return currentState.currentTab;
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider value={{ show, setTab, getCurrentTab }}>
|
||||
{portalTarget &&
|
||||
currentState.isOpen &&
|
||||
createPortal(
|
||||
<PopupContainer
|
||||
invert={currentState.invert}
|
||||
top={currentState.top}
|
||||
left={currentState.left}
|
||||
onClose={() => setState(defaultState)}
|
||||
>
|
||||
{currentState.content}
|
||||
<ContainerDiamond invert={currentState.invert} />
|
||||
</PopupContainer>,
|
||||
portalTarget,
|
||||
)}
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
title: string | null;
|
||||
top: number;
|
||||
left: number;
|
||||
onClose: () => void;
|
||||
onPrevious?: () => void | null;
|
||||
noHeader?: boolean | null;
|
||||
};
|
||||
|
||||
const PopupMenu: React.FC<Props> = ({ title, top, left, onClose, noHeader, children, onPrevious }) => {
|
||||
const $containerRef = useRef();
|
||||
useOnOutsideClick($containerRef, true, onClose, null);
|
||||
|
||||
return (
|
||||
<Container left={left} top={top} ref={$containerRef}>
|
||||
<Header>
|
||||
<HeaderTitle>{title}</HeaderTitle>
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<Content>{children}</Content>
|
||||
<Container invert={false} left={left} top={top} ref={$containerRef}>
|
||||
<Wrapper>
|
||||
{onPrevious && (
|
||||
<PreviousButton onClick={onPrevious}>
|
||||
<AngleLeft color="#c2c6dc" />
|
||||
</PreviousButton>
|
||||
)}
|
||||
{noHeader ? (
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross color="#c2c6dc" />
|
||||
</CloseButton>
|
||||
) : (
|
||||
<Header>
|
||||
<HeaderTitle>{title}</HeaderTitle>
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross color="#c2c6dc" />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
)}
|
||||
<Content>{children}</Content>
|
||||
</Wrapper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) => {
|
||||
const { getCurrentTab, setTab } = usePopup();
|
||||
if (getCurrentTab() !== tab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
{tab > 0 && (
|
||||
<PreviousButton
|
||||
onClick={() => {
|
||||
setTab(0);
|
||||
}}
|
||||
>
|
||||
<AngleLeft color="#c2c6dc" />
|
||||
</PreviousButton>
|
||||
)}
|
||||
{title && (
|
||||
<Header>
|
||||
<HeaderTitle>{title}</HeaderTitle>
|
||||
</Header>
|
||||
)}
|
||||
<CloseButton onClick={() => onClose()}>
|
||||
<Cross color="#c2c6dc" />
|
||||
</CloseButton>
|
||||
<Content>{children}</Content>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopupMenu;
|
||||
|
Reference in New Issue
Block a user