import React, { useState, useRef, useEffect } from 'react'; import styled from 'styled-components'; import { CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus } from 'shared/icons'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; import { isPositionChanged, getSortedDraggables, getNewDraggablePosition, getAfterDropDraggableList, } from 'shared/utils/draggables'; import Button from 'shared/components/Button'; import TextareaAutosize from 'react-autosize-textarea'; import Control from 'react-select/src/components/Control'; import useOnOutsideClick from 'shared/hooks/onOutsideClick'; const Wrapper = styled.div` margin-bottom: 24px; `; const WindowTitle = styled.div` padding: 8px 0; position: relative; margin: 0 0 4px 40px; `; const WindowTitleIcon = styled(CheckSquareOutline)` top: 10px; left: -40px; position: absolute; `; const WindowChecklistTitle = styled.div` display: flex; justify-content: space-between; align-items: center; flex-flow: row wrap; `; const WindowTitleText = styled.h3` cursor: pointer; color: rgba(${props => props.theme.colors.text.primary}); margin: 6px 0; display: inline-block; width: auto; min-height: 18px; font-size: 16px; line-height: 20px; min-width: 40px; `; const WindowOptions = styled.div` margin: 0 2px 0 auto; float: right; `; const DeleteButton = styled(Button)` padding: 6px 12px; `; const ChecklistProgress = styled.div` margin-bottom: 6px; position: relative; `; const ChecklistProgressPercent = styled.span` color: #5e6c84; font-size: 11px; line-height: 10px; position: absolute; left: 5px; top: -1px; text-align: center; width: 32px; `; const ChecklistProgressBar = styled.div` background: rgba(${props => props.theme.colors.bg.primary}); border-radius: 4px; clear: both; height: 8px; margin: 0 0 0 40px; overflow: hidden; position: relative; `; const ChecklistProgressBarCurrent = styled.div<{ width: number }>` width: ${props => props.width}%; background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)}); bottom: 0; left: 0; position: absolute; top: 0; transition: width 0.14s ease-in, background 0.14s ease-in; `; export const ChecklistItems = styled.div` min-height: 8px; `; const ChecklistItemUncheckedIcon = styled(Square)``; const ChecklistIcon = styled.div` cursor: pointer; position: absolute; left: 0; top: 0; margin: 10px; text-align: center; &:hover { opacity: 0.8; } `; const ChecklistItemCheckedIcon = styled(CheckSquare)` fill: rgba(${props => props.theme.colors.primary}); `; const ChecklistItemDetails = styled.div` word-break: break-word; word-wrap: break-word; overflow-wrap: break-word; `; const ChecklistItemRow = styled.div` cursor: pointer; display: flex; flex-direction: row; `; const ChecklistItemTextControls = styled.div` padding: 6px 0; width: 100%; display: inline-flex; align-items: center; `; const ChecklistItemText = styled.span<{ complete: boolean }>` color: ${props => (props.complete ? '#5e6c84' : `rgba(${props.theme.colors.text.primary})`)}; ${props => props.complete && 'text-decoration: line-through;'} line-height: 20px; font-size: 16px; min-height: 20px; margin-bottom: 0; align-self: center; flex: 1; `; const ChecklistControls = styled.div` display: inline-flex; flex-direction: row; float: right; `; const ControlButton = styled.div` opacity: 0; margin-left: 4px; padding: 4px 6px; border-radius: 6px; background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8); display: flex; width: 32px; height: 32px; align-items: center; justify-content: center; &:hover { background-color: rgba(${props => props.theme.colors.primary}, 1); } `; const ChecklistNameEditorWrapper = styled.div` display: block; float: left; padding-top: 6px; padding-bottom: 8px; z-index: 50; width: 100%; `; export const ChecklistNameEditor = styled(TextareaAutosize)` overflow: hidden; overflow-wrap: break-word; resize: none; height: 54px; width: 100%; background: none; border: none; box-shadow: none; max-height: 162px; min-height: 54px; padding: 8px 12px; font-size: 16px; line-height: 20px; border: 1px solid rgba(${props => props.theme.colors.primary}); border-radius: 3px; color: rgba(${props => props.theme.colors.text.primary}); border-color: rgba(${props => props.theme.colors.border}); background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4); &:focus { border-color: rgba(${props => props.theme.colors.primary}); } `; const AssignUserButton = styled(AccountPlus)` fill: rgba(${props => props.theme.colors.text.primary}); `; const ClockButton = styled(Clock)` fill: rgba(${props => props.theme.colors.text.primary}); `; const TrashButton = styled(Trash)` fill: rgba(${props => props.theme.colors.text.primary}); `; const ChecklistItemWrapper = styled.div<{ ref: any }>` user-select: none; clear: both; padding-left: 40px; position: relative; border-radius: 6px; & ${ControlButton}:last-child { margin-right: 4px; } &:hover { background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4); } &:hover ${ControlButton} { opacity: 1; } `; const EditControls = styled.div` clear: both; display: flex; padding-bottom: 9px; flex-direction: row; `; const SaveButton = styled(Button)` margin-right: 4px; padding: 6px 12px; `; const CancelButton = styled.div` cursor: pointer; margin: 5px; & svg { fill: rgba(${props => props.theme.colors.text.primary}); } &:hover svg { fill: rgba(${props => props.theme.colors.text.secondary}); } `; const Spacer = styled.div` flex: 1; `; const EditableDeleteButton = styled.button` cursor: pointer; display: flex; margin: 0 2px; padding: 6px 8px; border-radius: 3px; &:hover { background: rgba(${props => props.theme.colors.primary}, 0.8); } `; const NewItemButton = styled(Button)` padding: 6px 8px; `; const ChecklistNewItem = styled.div` margin: 8px 0; margin-left: 40px; `; type ChecklistItemProps = { itemID: string; checklistID: string; complete: boolean; name: string; onChangeName: (itemID: string, currentName: string) => void; wrapperProps: any; handleProps: any; onToggleItem: (itemID: string, complete: boolean) => void; onDeleteItem: (checklistIDID: string, itemID: string) => void; }; export const ChecklistItem = React.forwardRef( ( { itemID, checklistID, complete, name, wrapperProps, handleProps, onChangeName, onToggleItem, onDeleteItem, }: ChecklistItemProps, $item, ) => { const $editor = useRef(null); const [editting, setEditting] = useState(false); const [currentName, setCurrentName] = useState(name); useEffect(() => { if (editting && $editor && $editor.current) { $editor.current.focus(); $editor.current.select(); } }, [editting]); // useOnOutsideClick($item, true, () => setEditting(false), null); return ( { e.stopPropagation(); onToggleItem(itemID, !complete); }} > {complete ? ( ) : ( )} {editting ? ( <> { if (e.key === 'Enter') { onChangeName(itemID, currentName); setEditting(false); } }} onChange={e => { setCurrentName(e.currentTarget.value); }} value={currentName} /> { onChangeName(itemID, currentName); setEditting(false); }} variant="relief" > Save { e.stopPropagation(); setEditting(false); }} > { e.stopPropagation(); setEditting(false); onDeleteItem(checklistID, itemID); }} > ) : ( { setEditting(true); }} > {name} { e.stopPropagation(); onDeleteItem(checklistID, itemID); }} > )} ); }, ); type AddNewItemProps = { onAddItem: (name: string) => void; }; const AddNewItem: React.FC = ({ onAddItem }) => { const $editor = useRef(null); const $wrapper = useRef(null); const [currentName, setCurrentName] = useState(''); const [editting, setEditting] = useState(false); useEffect(() => { if (editting && $editor && $editor.current) { $editor.current.focus(); $editor.current.select(); } }, [editting]); useOnOutsideClick($wrapper, true, () => setEditting(false), null); return ( {editting ? ( <> { if (e.key === 'Enter') { e.preventDefault(); onAddItem(currentName); setCurrentName(''); } }} onChange={e => { setCurrentName(e.currentTarget.value); }} value={currentName} /> { onAddItem(currentName); setCurrentName(''); if (editting && $editor && $editor.current) { $editor.current.focus(); $editor.current.select(); } }} variant="relief" > Save { e.stopPropagation(); setEditting(false); }} > ) : ( setEditting(true)}>Add an item )} ); }; type ChecklistTitleEditorProps = { name: string; onChangeName: (item: string) => void; onCancel: () => void; }; const ChecklistTitleEditor = React.forwardRef( ({ name, onChangeName, onCancel }: ChecklistTitleEditorProps, $name: any) => { const [currentName, setCurrentName] = useState(name); return ( <> { setCurrentName(e.currentTarget.value); }} onKeyDown={e => { if (e.key === 'Enter') { onChangeName(currentName); } }} /> { onChangeName(currentName); }} variant="relief" > Save { e.stopPropagation(); onCancel(); }} > ); }, ); type ChecklistProps = { checklistID: string; onDeleteChecklist: ($target: React.RefObject, checklistID: string) => void; name: string; children: React.ReactNode; onChangeName: (item: string) => void; onToggleItem: (taskID: string, complete: boolean) => void; onChangeItemName: (itemID: string, currentName: string) => void; wrapperProps: any; handleProps: any; onDeleteItem: (checklistID: string, itemID: string) => void; onAddItem: (itemName: string) => void; items: Array; }; const Checklist = React.forwardRef( ( { checklistID, children, onDeleteChecklist, name, items, wrapperProps, handleProps, onToggleItem, onAddItem, onChangeItemName, onChangeName, onDeleteItem, }: ChecklistProps, $container, ) => { const $name = useRef(null); const complete = items.reduce((prev, item) => prev + (item.complete ? 1 : 0), 0); const percent = items.length === 0 ? 0 : Math.floor((complete / items.length) * 100); const [editting, setEditting] = useState(false); // useOnOutsideClick($name, true, () => setEditting(false), null); useEffect(() => { if (editting && $name && $name.current) { $name.current.focus(); $name.current.select(); } }, [editting]); return ( {editting ? ( { onChangeName(currentName); setEditting(false); }} onCancel={() => { setEditting(false); }} /> ) : ( setEditting(true)}>{name} { onDeleteChecklist($target, checklistID); }} color="danger" variant="outline" > Delete )} {`${percent}%`} {children} ); }, ); /* {items .slice() .sort((a, b) => a.position - b.position) .map((item, idx) => ( ))} */ export default Checklist;