react-beautiful-dnd#Draggable TypeScript Examples
The following examples show how to use
react-beautiful-dnd#Draggable.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: StatOptionItem.tsx From slippi-stats with MIT License | 6 votes |
StatOptionItem: React.FC<{
checked: boolean;
index: number;
id: string;
onChange: (checked: boolean) => void;
}> = (props) => {
const stat = STAT_DEFINITIONS.get(props.id);
if (!stat) {
return null;
}
return (
<Draggable draggableId={props.id} index={props.index}>
{(provided, snapshot) => (
<div ref={provided.innerRef}>
<Option
{...provided.draggableProps}
isDragging={snapshot.isDragging}
checked={props.checked}
onChange={props.onChange}
draggable={true}
name={stat.name}
handleProps={provided.dragHandleProps}
/>
</div>
)}
</Draggable>
);
}
Example #2
Source File: SelectedDrag.tsx From crust-apps with Apache License 2.0 | 6 votes |
function Selected ({ address, index, onDeselect }: Props): React.ReactElement<Props> {
return (
<Draggable
draggableId={address}
index={index}
key={address}
>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot): React.ReactElement => {
const element = (
<div
// eslint-disable-next-line @typescript-eslint/unbound-method
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<AddressToggle
address={address}
className={snapshot.isDragging ? 'isDragging' : ''}
noToggle
onChange={onDeselect}
/>
</div>
);
return snapshot.isDragging
? ReactDOM.createPortal(element, portal)
: element;
}}
</Draggable>
);
}
Example #3
Source File: Tree.tsx From react-beautiful-tree with Apache License 2.0 | 6 votes |
renderItem = (flatItem: FlattenedItem, index: number): ReactNode => {
const { isDragEnabled } = this.props
const isDragDisabled =
typeof isDragEnabled === 'function'
? !isDragEnabled(flatItem.item)
: !isDragEnabled
return (
<Draggable
key={flatItem.item.id}
draggableId={flatItem.item.id.toString()}
index={index}
isDragDisabled={isDragDisabled}
>
{this.renderDraggableItem(flatItem)}
</Draggable>
)
}
Example #4
Source File: AddButtonMappingDialog.tsx From Pi-Tool with GNU General Public License v3.0 | 6 votes |
ButtonActionCreator: React.FC<{ buttonDuration: ButtonPress }> = ({ buttonDuration }) => {
const draggableId = uuidv4();
return (
<Droppable droppableId="PALETTE" direction="horizontal" isDropDisabled={true}>
{(provided, _snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
<Box width={1} style={{ display: 'inline-flex', verticalAlign: 'middle', alignItems: 'center' }}>
<Draggable key={draggableId} draggableId={draggableId} index={buttonDuration as number}>
{(provided, snapshot) => (
<Box style={{ width: 50, height: 50 }}>
<div
style={{ width: 50, height: 50 }}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
<ButtonPressIcon press={buttonDuration} isButton={true} />
</div>
{snapshot.isDragging &&
<ButtonPressIcon press={buttonDuration} isButton={true} />
}
</Box>
)}
</Draggable>
<Typography>
{buttonDuration === ButtonPress.Short ? "Short button press" : "Long button press"}
</Typography>
</Box>
</div>
)}
</Droppable>
);
}
Example #5
Source File: Tree.tsx From react-beautiful-tree with Apache License 2.0 | 6 votes |
renderVirtualRow = React.memo((props: VirtualRowProps) => {
const { data: items, index, style, isDragging } = props
const { isDragEnabled } = this.props
const flatItem = items[index]
const isDragDisabled =
typeof isDragEnabled === 'function'
? !isDragEnabled(flatItem.item)
: !isDragEnabled
return (
<Draggable
draggableId={flatItem.item.id.toString()}
index={index}
isDragDisabled={isDragDisabled}
key={flatItem.item.id}
>
{(provided, snapshot) =>
this.renderVirtualItem({
snapshot,
provided,
flatItem,
style,
isDragging,
})
}
</Draggable>
)
}, areEqual)
Example #6
Source File: SelectedDrag.tsx From subscan-multisig-react with Apache License 2.0 | 6 votes |
function Selected({ address, index, onDeselect }: Props): React.ReactElement<Props> {
return (
<Draggable draggableId={address} index={index} key={address}>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot): React.ReactElement => {
const element = (
<div
// eslint-disable-next-line @typescript-eslint/unbound-method
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<AddressToggle
address={address}
className={snapshot.isDragging ? 'isDragging' : ''}
noToggle
onChange={onDeselect}
/>
</div>
);
return snapshot.isDragging ? ReactDOM.createPortal(element, portal) : element;
}}
</Draggable>
);
}
Example #7
Source File: MediaMenuDraggable.tsx From sync-party with GNU General Public License v3.0 | 6 votes |
export default function MediaMenuDraggable({
children,
mediaItemId,
index
}: Props): ReactElement {
return (
<Draggable draggableId={mediaItemId} index={index}>
{(provided: DraggableProvided): JSX.Element => (
<>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{children}
</div>
</>
)}
</Draggable>
);
}
Example #8
Source File: Task.tsx From knboard with MIT License | 6 votes |
Task = ({ task: task, style, index }: Props) => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(setEditDialogOpen(task.id));
};
return (
<Draggable key={task.id} draggableId={`task-${task.id}`} index={index}>
{(
dragProvided: DraggableProvided,
dragSnapshot: DraggableStateSnapshot
) => (
<Container
isDragging={dragSnapshot.isDragging}
isGroupedOver={Boolean(dragSnapshot.combineTargetFor)}
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
style={getStyle(dragProvided, style)}
data-is-dragging={dragSnapshot.isDragging}
data-testid={`task-${task.id}`}
data-index={index}
aria-label={`task ${task.title}`}
onClick={handleClick}
css={taskContainerStyles}
>
<Content>
<TextContent>{task.title}</TextContent>
<TaskId>id: {task.id}</TaskId>
<TaskLabels task={task} />
<TaskFooter task={task} />
</Content>
</Container>
)}
</Draggable>
);
}
Example #9
Source File: index.tsx From nextjs-hasura-fullstack with MIT License | 5 votes |
CardTask: React.FC<CardTaskProps> = (props) => {
const { card, index } = props
const [deleteCard, { loading }] = useDeleteCardMutation()
const handleDeleteCard = React.useCallback(async () => {
try {
await deleteCard({
variables: { id: card.id },
update: (cache, { data }) => {
if (data?.delete_cards_by_pk) {
const cacheId = cache.identify(data?.delete_cards_by_pk)
if (cacheId) {
cache.modify({
id: cache.identify({ __typename: 'lists', id: card.list_id }),
fields: {
cards(existingCardRefs = [], { readField, toReference }) {
return existingCardRefs.filter(
//@ts-ignore
(ref) => ref.__ref !== cacheId,
)
},
},
})
cache.evict({ id: cacheId })
}
}
},
optimisticResponse: {
__typename: 'mutation_root',
delete_cards_by_pk: {
...card,
},
},
})
} catch (err) {
console.log(`?? [LOG]: removeAction -> err`, err)
}
}, [])
return (
<Draggable key={card.id} draggableId={`${card.id}`} index={index}>
{(
{ draggableProps, dragHandleProps, innerRef },
{ isDragging, isDropAnimating },
) => (
<Card
ref={innerRef}
{...draggableProps}
className={`CardTask group ${
isDragging && `border-purple-600`
} px-2 py-3 mb-4`}
>
<CardBody {...dragHandleProps}>
<div className={`flex items-center justify-between h-16`}>
<div>{card.title}</div>
<div className={`hidden group-hover:block`}>
<ButtonIcon
size="sm"
color="danger"
variant="light"
icon={<XSolid />}
onClick={(e) => {
e.preventDefault()
handleDeleteCard()
}}
/>
</div>
</div>
<div>
<p>{card.description}</p>
</div>
</CardBody>
</Card>
)}
</Draggable>
)
}
Example #10
Source File: Column.tsx From gant-design with MIT License | 5 votes |
Column = (props) => {
const {
column,
tasks,
index,
hideQuickAdd,
isColumnDragDisabled,
isTaskDropDisabled,
...nextProps
} = props
const { prefixCls, renderHeader, renderExtra, handleAddBtn, idKey, titleKey } = useContext(TaskBoardContext)
return <Draggable
index={index}
draggableId={column[idKey]}
isDragDisabled={isColumnDragDisabled}
>
{(provided, snapshot) => (
<div
className={prefixCls + '-column-wrapper'}
ref={provided.innerRef}
{...provided.draggableProps}
>
<div
className={prefixCls + '-column-container'}
style={{
boxShadow: `${snapshot.isDragging ? 'rgba(0, 0, 0, 0.2) 2px 2px 1px' : ''}`,
}}
>
<div {...provided.dragHandleProps}>
{renderHeader === null ? null :
renderHeader ? renderHeader(column) :
<div className={prefixCls + '-column-header-wrapper'}>
<Tooltip title={column[titleKey]} mouseEnterDelay={0.3} placement="topLeft">
<div className={prefixCls + '-column-header-title'}>
{column[titleKey]}{tasks && tasks.length > 0 ? `(${tasks.length})` : null}
</div>
</Tooltip>
<div className={prefixCls + '-column-header-extra'}>
{renderExtra && renderExtra(column)}
</div>
</div>
}
</div>
<Droppable
droppableId={column[idKey]}
type='task'
isDropDisabled={isTaskDropDisabled}
>
{(provided, snapshot) => (
<div
className={prefixCls + '-task-drop-inner'}
ref={provided.innerRef}
{...provided.droppableProps}
>
<TaskList tasks={tasks} column={column} {...nextProps} />
{provided.placeholder}
{!hideQuickAdd && <div
className={prefixCls + '-quick-add'}
onClick={(e) => {
e.stopPropagation()
handleAddBtn && handleAddBtn(column)
}}>
<Icon type="plus" />
</div>}
</div>
)}
</Droppable>
</div>
</div>
)}
</Draggable>
}
Example #11
Source File: CourseHitItem.tsx From peterportal-client with MIT License | 5 votes |
CourseHitItem: FC<CourseHitItemProps> = (props: CourseHitItemProps) => {
const dispatch = useAppDispatch();
// do not make course draggable on mobile
if (isMobile) {
return <div onMouseDown={() => {
dispatch(setActiveCourse(props));
dispatch(setShowAddCourse(true));
// also hide the search bar to view the roadmap
dispatch(setShowSearch(false));
}}
// use inline style here so dnd can calculate size
style={{ margin: ' 0rem 2rem 1rem 2rem' }}>
<Course {...props} />
</div>
}
// course is draggable on desktop
else {
return <Draggable
key={`search-course-${props.index}`}
draggableId={`search-${props.id}-${props.index}`}
index={props.index}
>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
// use inline style here so dnd can calculate size
margin: ' 0rem 2rem 1rem 2rem',
...provided.draggableProps.style
}}
onMouseDown={() => { dispatch(setActiveCourse(props)) }}
>
<Course {...props} />
</div>
);
}}
</Draggable>
}
}
Example #12
Source File: SidePanel.tsx From TabMerger with GNU General Public License v3.0 | 5 votes |
export default function SidePanel(): JSX.Element {
const { available } = useSelector((state) => state.groups.present);
const { inputValue, filterChoice } = useSelector((state) => state.header);
const { dragType, isDragging } = useSelector((state) => state.dnd);
const { filteredGroups, nonEmptyGroups } = useFilter();
const groupSearch = inputValue !== "" && filterChoice === "group";
const tabSearch = inputValue !== "" && filterChoice === "tab";
const currentGroups = groupSearch ? filteredGroups : tabSearch ? nonEmptyGroups : available;
const groupDrag = isGroupDrag(dragType);
const groupsContainerRef = useRef<HTMLDivElement | null>(null);
const containerHeight = useContainerHeight(groupsContainerRef);
return (
<Container>
{currentGroups.map((group) => group.id).includes(available[0].id) && <Group {...available[0]} />}
<div ref={groupsContainerRef}>
<Droppable droppableId="sidePanel" isCombineEnabled={!groupDrag}>
{(provider) => (
<DraggableContainer
ref={provider.innerRef}
{...provider.droppableProps}
$height={containerHeight}
$dragging={isDragging && groupDrag}
>
{currentGroups
.filter((group) => group.id !== available[0].id)
.map((data, i) => (
<Draggable
key={data.id + i + 1}
draggableId={`group-${i + 1}`}
index={i + 1}
isDragDisabled={groupSearch}
>
{(provided, dragSnapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<Group {...data} snapshot={dragSnapshot} dragHandleProps={provided.dragHandleProps} />
</div>
)}
</Draggable>
))}
{provider.placeholder}
</DraggableContainer>
)}
</Droppable>
</div>
</Container>
);
}
Example #13
Source File: TabContainer.tsx From yana with MIT License | 5 votes |
TabContainer: React.FC<{}> = props => {
const theme = useTheme();
const mainContent = useMainContentContext();
return (
<DragDropContext
onDragEnd={(result, provided) => {
if (result.destination?.index !== undefined) {
mainContent.reorderTab(result.source.index, result.destination.index);
}
}}
>
<Droppable droppableId="tabs-droppable" direction="horizontal">
{(provided, snapshot) => (
<div className={styles.tabsContainer} ref={provided.innerRef} {...provided.droppableProps}>
{mainContent.tabs.map((tab, idx) => {
const id = tab.dataItem?.id ?? tab.page ?? 'unknown';
const name = tab.dataItem?.name ?? (tab.page ? pages[tab.page]?.title : 'Unknown name');
return (
<Draggable key={id} draggableId={id} index={idx} disableInteractiveElementBlocking={true}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={cx(
styles.tab,
snapshot.isDragging && styles.draggingTab,
cxs({
color:
mainContent.openTabId === idx
? 'white'
: Color(theme.topBarColor).mix(Color('#ffffff'), 0.5).toString(),
borderBottom: mainContent.openTabId === idx ? `4px solid ${theme.primaryColor}` : undefined,
fontWeight: mainContent.openTabId === idx ? 'bold' : 'normal',
backgroundColor: theme.topBarColor,
':hover': {
borderBottom: mainContent.openTabId !== idx ? `4px solid white` : undefined,
},
})
)}
onClick={() => mainContent.activateTab(idx)}
>
{name.substr(0, 12)}
{name.length > 12 ? '...' : ''}
<div
className={cx(
styles.closeContainer,
cxs({
backgroundColor: theme.topBarColor,
})
)}
onClick={e => {
e.stopPropagation();
mainContent.closeTab(idx);
}}
>
<Icon icon={'cross'} />
</div>
</div>
)}
</Draggable>
);
})}
</div>
)}
</Droppable>
</DragDropContext>
);
}
Example #14
Source File: Form.demo.dnd.tsx From mantine with MIT License | 5 votes |
function Demo() {
const form = useForm({
initialValues: {
employees: formList([
{ name: 'John Doe', email: '[email protected]' },
{ name: 'Bill Love', email: '[email protected]' },
{ name: 'Nancy Eagle', email: '[email protected]' },
{ name: 'Lim Notch', email: '[email protected]' },
{ name: 'Susan Seven', email: '[email protected]' },
]),
},
});
const fields = form.values.employees.map((_, index) => (
<Draggable key={index} index={index} draggableId={index.toString()}>
{(provided) => (
<Group ref={provided.innerRef} mt="xs" {...provided.draggableProps}>
<Center {...provided.dragHandleProps}>
<GripVertical size={18} />
</Center>
<TextInput
placeholder="John Doe"
{...form.getListInputProps('employees', index, 'name')}
/>
<TextInput
placeholder="[email protected]"
{...form.getListInputProps('employees', index, 'email')}
/>
</Group>
)}
</Draggable>
));
return (
<Box sx={{ maxWidth: 500 }} mx="auto">
<DragDropContext
onDragEnd={({ destination, source }) =>
form.reorderListItem('employees', { from: source.index, to: destination.index })
}
>
<Droppable droppableId="dnd-list" direction="vertical">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{fields}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<Group position="center" mt="md">
<Button onClick={() => form.addListItem('employees', { name: '', email: '' })}>
Add employee
</Button>
</Group>
<Text size="sm" weight={500} mt="md">
Form values:
</Text>
<Code block>{JSON.stringify(form.values, null, 2)}</Code>
</Box>
);
}
Example #15
Source File: Windows.tsx From TabMerger with GNU General Public License v3.0 | 5 votes |
export default function Windows(): JSX.Element {
const { inputValue, filterChoice } = useSelector((state) => state.header);
const { dragType } = useSelector((state) => state.dnd);
const {
active: { id: activeId, index: groupIndex },
available
} = useSelector((state) => state.groups.present);
const typing = inputValue !== "";
const { filteredTabs } = useFilter();
const { windows, updatedAt } = available[groupIndex];
const windowContainerRef = useRef<HTMLDivElement | null>(null);
const containerHeight = useContainerHeight(windowContainerRef, updatedAt, filterChoice, filteredTabs);
const isTabSearch = filterChoice === "tab";
return (
<div>
<Information />
{typing && isTabSearch && <SearchResult />}
<div ref={windowContainerRef}>
<Droppable droppableId={"group-" + groupIndex} isDropDisabled={!isWindowDrag(dragType) || groupIndex === 0}>
{(provider, dropSnapshot) => (
<WindowsContainer
ref={provider.innerRef}
{...provider.droppableProps}
$draggedOver={dropSnapshot.isDraggingOver}
$height={containerHeight}
>
{windows.map(
(window, i) =>
(!typing || !isTabSearch || filteredTabs[activeId][i]?.length > 0) && (
<Draggable
key={i}
draggableId={`window-${i}-group-${groupIndex}`}
index={i}
isDragDisabled={typing && isTabSearch}
>
{(provided, dragSnapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<Window
{...window}
windowIndex={i}
snapshot={dragSnapshot}
dragHandleProps={provided.dragHandleProps}
/>
</div>
)}
</Draggable>
)
)}
{provider.placeholder}
</WindowsContainer>
)}
</Droppable>
</div>
</div>
);
}
Example #16
Source File: index.tsx From nextjs-hasura-fullstack with MIT License | 5 votes |
ListBoard: React.FC<ListBoardProps> = ({ list, index }) => {
return (
<Draggable draggableId={`${list.id}`} index={index} key={list.id}>
{({ innerRef, dragHandleProps, draggableProps }, { isDragging }) => {
return (
<div
ref={innerRef}
{...draggableProps}
className={`flex flex-col mx-4`}
>
<Card
className={`w-72 bg-white flex flex-col ${
isDragging && `border-purple-600`
}`}
>
<CardHeader
action={<ActionDropdown list={list} />}
{...dragHandleProps}
className={`hover:bg-purple-50`}
>
<ListHeader list={list} />
</CardHeader>
<Droppable
type="CARD"
direction="vertical"
key={list.id}
droppableId={`${list.id}`}
>
{(
{ innerRef, droppableProps, placeholder },
{ isDraggingOver },
) => (
<CardBody
ref={innerRef}
{...droppableProps}
className={`${isDraggingOver && `bg-purple-400 border-2`}`}
>
{list.cards.map((card, index) => (
<CardTask key={card.id} card={card} index={index} />
))}
{placeholder}
</CardBody>
)}
</Droppable>
<CardFooter className={`h-14`}>
<NewCard
lastCard={list.cards[list.cards.length - 1]}
list={list}
/>
</CardFooter>
</Card>
</div>
)
}}
</Draggable>
)
}
Example #17
Source File: FlowList.tsx From Protoman with MIT License | 5 votes |
FlowCell: React.FC<CellProps> = ({ flowName, emphasize, handleSelection, handleDelete, handleClone, idx }) => {
const [menuVisible, setMenuVisible] = React.useState(false);
function showMenu(): void {
setMenuVisible(true);
}
function hideMenu(): void {
setMenuVisible(false);
}
function deleteFlow(): void {
handleDelete(flowName);
hideMenu();
}
function cloneFlow(): void {
handleClone(flowName);
hideMenu();
}
const menu = (
<>
<Button type="link" danger onClick={prevent(deleteFlow)}>
<DeleteOutlined />
Delete Request
</Button>
<Separator />
<Button type="link" onClick={prevent(cloneFlow)}>
<SubnodeOutlined />
Clone Request
</Button>
</>
);
return (
<Popover
placement="rightTop"
content={menu}
visible={menuVisible}
trigger="contextMenu"
onVisibleChange={setMenuVisible}
>
<ClickableItem onClick={(): void => handleSelection(flowName)} onContextMenu={prevent(showMenu)}>
<Draggable draggableId={flowName} index={idx}>
{(provided): React.ReactElement => {
const style: React.CSSProperties = {
width: '100%',
height: '100%',
padding: 8,
boxSizing: 'border-box',
};
const { style: draggableStyle, ...draggableRest } = provided.draggableProps;
return (
<div
ref={provided.innerRef}
{...provided.dragHandleProps}
{...draggableRest}
style={{ ...style, ...draggableStyle }}
>
<Typography.Text
strong={emphasize}
style={{ userSelect: 'none', color: emphasize ? 'rgb(47, 93, 232)' : undefined }}
>
{flowName}
</Typography.Text>
</div>
);
}}
</Draggable>
</ClickableItem>
</Popover>
);
}
Example #18
Source File: DraggableTags.tsx From nebula-studio with Apache License 2.0 | 5 votes |
render() {
const list = this.props.data.map(item => ({
id: `field-${item}`,
content: (
<Tag className={styles.dragItem} closable={true} onClose={() => this.props.removeData(item)}>
{item}
</Tag>
),
}));
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Droppable droppableId="droppable" direction="horizontal">
{(provided, _snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{
display: 'flex',
overflow: 'auto',
flexWrap: 'wrap',
}}
>
{list.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style,
)}
>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
Example #19
Source File: BoardItem.tsx From projectboard with MIT License | 5 votes |
BoardItem = ({ task, index }: Props) => {
let priorityIcon = (
<span
className='inline-block m-0.5 rounded-sm border border-gray-100 hover:border-gray-200 p-0.5'
>
<PriorityIcon priority={task.priority} />
</span>
);
// const dispatch = useDispatch<AppDispatch>();
const updatePriority = (priority: string) => {
// dispatch(updatePriority(task, priority));
};
return (
<Draggable draggableId={task._id || 'id'} index={index} key={task._id}>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => {
let isDragging = snapshot.isDragging && !snapshot.isDropAnimating;
return (
<div
ref={provided.innerRef}
className={classNames(
'cursor-default flex flex-col w-full px-4 py-3 mb-2 bg-white rounded focus:outline-none',
{
'shadow-modal': isDragging,
}
)}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className='flex justify-between w-full cursor-default'>
<div className='flex flex-col'>
<span className='text-xs font-normal text-gray-500 uppercase'>{task._id}</span>
<span className='mt-1 text-sm font-medium text-gray-700 line-clamp-2 overflow-ellipsis'>{task.title}</span>
</div>
<div className='flex-shrink-0'>
{task.assignee ?
<Avatar name={`${task?.assignee?.user?.firstName} ${task?.assignee?.user?.lastName}`} /> :
<Avatar />}
</div>
</div>
<div className='mt-2.5 flex items-center'>
<PriorityMenu
button={priorityIcon}
disabled
// id={`r-priority-${task._id}`}
filterKeyword={true}
onSelect={(p: any) => updatePriority(p)}
/>
</div>
</div>
);
}}
</Draggable >
);
}
Example #20
Source File: DragndropPostList.tsx From clearflask with Apache License 2.0 | 5 votes |
DragndropPostListPostListInner = React.memo((props: {
PostListProps: React.ComponentProps<typeof PostList>;
dragSelectedTourAnchorProps?: React.ComponentProps<typeof TourAnchor>;
}) => {
const theme = useTheme();
const store = useStore();
return (
<PostList
{...props.PostListProps}
PanelPostProps={{
margins: theme.spacing(DashboardListPostSpacing, DashboardListPostSpacing, DashboardListPostSpacing, DashboardListPostSpacing + 1),
...props.PostListProps.PanelPostProps,
wrapPost: (post, content, index) => (
<Draggable
draggableId={post.ideaId}
index={index}
>
{(providedDraggable, snapshotDraggable) => {
var inner = content;
if (props.dragSelectedTourAnchorProps) {
if (!!props.PostListProps.selected && lastSelectedId !== props.PostListProps.selected) {
lastSelectedId = props.PostListProps.selected;
}
if (lastSelectedId === post.ideaId) {
inner = (
<Provider store={ServerAdmin.get().getStore()}>
<TourAnchor placement='top' {...props.dragSelectedTourAnchorProps} zIndex={zb => zb.modal - 1}>
<Provider store={store}>
{inner}
</Provider>
</TourAnchor>
</Provider>
);
}
}
inner = (
<DragndropPostListDraggableInner
providedDraggable={providedDraggable}
isDragging={snapshotDraggable.isDragging}
draggingOver={snapshotDraggable.draggingOver}
dropAnimation={snapshotDraggable.dropAnimation}
content={inner}
/>
);
return inner as any;
}}
</Draggable>
),
}}
/>
);
}, customReactMemoEquals({ nested: new Set(['dragSelectedTourAnchorProps', 'PostListProps']) }))
Example #21
Source File: NetworksDnd.tsx From devex with GNU General Public License v3.0 | 5 votes |
NetworksDnd: React.FC<IProps> = ({ cards, setCards, deleteNode, editNode }) => {
const onDragEnd = (result: any) => {
if (!result.destination) {
return
}
const reorderedCards = reorder(
cards,
result.source.index,
result.destination.index
)
setCards(reorderedCards)
}
return <Container>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
className='dnd'
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{cards.map((card, index) => (
<Draggable key={card.id} draggableId={card.id.toString()} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<NetworkCard
key={card.url}
url={card.url}
name={card.name}
deleteNode={deleteNode}
editNode={editNode}
/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</Container>
}
Example #22
Source File: index.tsx From S2 with MIT License | 5 votes |
DimensionItem: FC<DimensionItemProps> = ({
fieldType,
item: { id, displayName, checked = true, children = [] },
expandable,
expandChildren,
isDragDisabled,
selectable,
index,
draggingItemId,
onVisibleItemChange,
}) => {
return (
<Draggable draggableId={id} index={index} isDragDisabled={isDragDisabled}>
{(provided, snapshot) => (
<div
{...provided.draggableProps}
ref={provided.innerRef}
className={cx(
getSwitcherClassName(selectable ? 'checkable-list' : 'normal-list'),
{
dragging: snapshot.isDragging,
'disable-dragging': isDragDisabled,
},
)}
>
<SingleItem
dragHandleProps={provided.dragHandleProps}
fieldType={fieldType}
id={id}
displayName={displayName}
checked={checked}
onVisibleItemChange={onVisibleItemChange}
selectable={selectable}
className={cx(selectable ? 'checkable-item' : 'normal-item', {
'item-collapse': !expandChildren,
})}
/>
{expandable &&
expandChildren &&
!isEmpty(children) &&
draggingItemId !== id && (
<div
className={cx('child-items', {
'item-hidden': !expandChildren,
})}
>
{children.map((item) => (
<SingleItem
key={item.id}
id={item.id}
fieldType={fieldType}
displayName={item.displayName}
disabled={!checked}
checked={item.checked}
parentId={id}
selectable={selectable}
onVisibleItemChange={onVisibleItemChange}
className="checkable-item"
/>
))}
</div>
)}
</div>
)}
</Draggable>
);
}
Example #23
Source File: index.tsx From Tiquet with MIT License | 5 votes |
DraggableTask = ({
title,
id,
index,
fetchTask,
deleteTask,
taskInfo,
taskInfoLoading,
priority,
}: DraggableTaskProps): JSX.Element => {
const handleFetch = (): void => {
// Check if the task to fetch isn't already fetched.
const alreadyFetched = id === taskInfo?.id;
if (!alreadyFetched && !taskInfoLoading) {
fetchTask(id);
}
}
const handleDeleteTask = (): void => {
deleteTask(id);
}
return (
<Draggable index={index} draggableId={new Number(id).toString()} key={id}>
{provided => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{ ...provided.draggableProps.style }}
>
<Task
id={id}
title={title}
priority={priority}
handleDeleteTask={handleDeleteTask}
handleInfoClick={handleFetch}
/>
</div>
)}
</Draggable>
)
}
Example #24
Source File: AddButtonMappingDialog.tsx From Pi-Tool with GNU General Public License v3.0 | 5 votes |
ButtonActionGrid: React.FC<{ buttonPresses: ButtonPressItems }> = ({ buttonPresses }) => {
const classes = useStyles();
return (
<Paper className={classes.buttonActionGrid} variant="outlined">
<Droppable
droppableId="GRID"
direction="horizontal"
isDropDisabled={buttonPresses.length >= 8}>
{(provided, snapshot) => (
<div
className={classes.buttonActionDroppable}
ref={provided.innerRef}
style={{ display: 'inline-flex', verticalAlign: 'middle', alignItems: 'center' }}
{...provided.droppableProps}>
<Box display="flex" flexDirection="row">
{buttonPresses.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
style={getStyle(provided.draggableProps.style, snapshot)}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
<ButtonPressIcon press={item.press} isButton={true} />
</div>
)}
</Draggable>
))}
</Box>
{provided.placeholder}
{/* && (buttonPresses.length == 0 && <Typography> Drag your your sequence here.</Typography>)} */}
</div>
)}
</Droppable>
</Paper>
);
}
Example #25
Source File: designers.tsx From ali-react-table with MIT License | 5 votes |
export function CheckedDimList({ style, className, pivot }: CheckDimListProps) {
const dimMap = new Map(pivot.allDimensions.map((dim) => [dim.code, dim]))
return (
<CheckedDimListDiv style={style} className={className}>
<DragDropContext
onDragEnd={(result) => {
if (!result.destination) {
return
}
const i = result.source.index
const j = result.destination.index
const nextCodes = pivot.dimCodes.slice()
const [code] = nextCodes.splice(i, 1)
nextCodes.splice(j, 0, code)
pivot.changeDimCodes(nextCodes)
}}
>
<Droppable droppableId="dimension-list" direction="horizontal">
{(dropProvided, snapshot) => (
<div className="tag-list" ref={dropProvided.innerRef}>
{pivot.dimCodes.map((dimCode, index) => {
const allValues = pivot.dimValues[dimCode]
const values = pivot.filters[dimCode]
return (
<Draggable key={dimCode} draggableId={dimCode} index={index}>
{(dragProvided, snapshot) => (
<Overlay.Popup
key={dimCode}
trigger={
<div
key={dimCode}
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
className={cx('tag', {
active: values.length < allValues.length,
empty: allValues.length > 0 && values.length === 0,
})}
>
{dimMap.get(dimCode).name}
<Filter16 className="filter-icon" />
</div>
}
triggerType="click"
>
<CheckedDimFilterPopup pivot={pivot} allValues={allValues} values={values} dimCode={dimCode} />
</Overlay.Popup>
)}
</Draggable>
)
})}
{dropProvided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</CheckedDimListDiv>
)
}
Example #26
Source File: AccountManagementPage.tsx From clarity with Apache License 2.0 | 4 votes |
AccountManagementPage = observer((props: Props) => {
const [openDialog, setOpenDialog] = React.useState(false);
const [openKeyDialog, setOpenKeyDialog] = React.useState(false);
const [
selectedAccount,
setSelectedAccount
] = React.useState<SignKeyPairWithAlias | null>(null);
const [name, setName] = React.useState('');
const [publicKey64, setPublicKey64] = React.useState('');
const [publicKeyHex, setPublicKeyHex] = React.useState('');
/* Note: 01 prefix denotes algorithm used in key generation */
const address = '01' + publicKeyHex;
const [copyStatus, setCopyStatus] = React.useState(false);
const handleClickOpen = (account: SignKeyPairWithAlias) => {
setOpenDialog(true);
setSelectedAccount(account);
setName(account.name);
};
const handleViewKey = async (accountName: string) => {
let publicKey64 = await props.authContainer.getSelectedAccountKey(
accountName
);
let publicKeyHex = await props.authContainer.getPublicKeyHex(accountName);
setName(accountName);
setPublicKey64(publicKey64);
setPublicKeyHex(publicKeyHex);
setOpenKeyDialog(true);
};
const handleCopyMessage = (event?: React.SyntheticEvent, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setCopyStatus(false);
};
const handleClose = () => {
setOpenDialog(false);
setOpenKeyDialog(false);
setSelectedAccount(null);
};
const handleUpdateName = () => {
if (selectedAccount) {
props.authContainer.renameUserAccount(selectedAccount.name, name);
handleClose();
}
};
const onDragEnd = (result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
props.authContainer.reorderAccount(
result.source.index,
result.destination.index
);
};
const handleClickRemove = (name: string) => {
confirm(
<div className="text-danger">Remove account</div>,
'Are you sure you want to remove this account?'
).then(() => props.authContainer.removeUserAccount(name));
};
return (
<React.Fragment>
<DragDropContext onDragEnd={result => onDragEnd(result)}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<Observer>
{() => (
<RootRef rootRef={provided.innerRef}>
<List>
{props.authContainer.userAccounts.map((item, index) => (
<Draggable
key={item.name}
draggableId={item.name}
index={index}
>
{(provided, snapshot) => (
<ListItem
innerRef={provided.innerRef}
ContainerProps={{
...provided.draggableProps,
...provided.dragHandleProps,
style: getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
}}
>
<ListItemText primary={item.name} />
<ListItemSecondaryAction>
<IconButton
edge={'end'}
onClick={() => {
handleClickOpen(item);
}}
>
<EditIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleClickRemove(item.name);
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleViewKey(item.name);
}}
>
<VpnKeyIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
</RootRef>
)}
</Observer>
)}
</Droppable>
</DragDropContext>
<Dialog
open={openDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Rename</DialogTitle>
<DialogContent>
<Input
autoFocus
margin="dense"
id="name"
type="text"
fullWidth
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleUpdateName} color="primary">
Update
</Button>
</DialogActions>
</Dialog>
<Dialog
fullScreen
open={openKeyDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Account Details</DialogTitle>
<DialogContent>
<List>
<ListSubheader>
<Typography variant={'h6'}>{name}</Typography>
</ListSubheader>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(address);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Address: ' + address}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(publicKey64);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Public Key: ' + publicKey64}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
</List>
<Snackbar
open={copyStatus}
message="Copied!"
autoHideDuration={1500}
onClose={handleCopyMessage}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
})
Example #27
Source File: DataModelBranch.tsx From datart with Apache License 2.0 | 4 votes |
DataModelBranch: FC<{
node: Column;
onNodeTypeChange: (type: any, name: string) => void;
onMoveToHierarchy: (node: Column) => void;
onEditBranch;
onDelete: (node: Column) => void;
onDeleteFromHierarchy: (parent: Column) => (node: Column) => void;
}> = memo(
({
node,
onNodeTypeChange,
onMoveToHierarchy,
onEditBranch,
onDelete,
onDeleteFromHierarchy,
}) => {
const t = useI18NPrefix('view.model');
const [isHover, setIsHover] = useState(false);
const renderNode = (node, isDragging) => {
let icon = (
<FolderOpenOutlined style={{ alignSelf: 'center', color: YELLOW }} />
);
return (
<>
<div
className="content"
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
<IW fontSize={FONT_SIZE_HEADING}>{icon}</IW>
<span>{node.name}</span>
<div className="action">
{isHover && !isDragging && (
<Tooltip title={t('rename')}>
<Button
type="link"
onClick={() => onEditBranch(node)}
icon={<EditOutlined />}
/>
</Tooltip>
)}
{isHover && !isDragging && (
<Tooltip title={t('delete')}>
<Button
type="link"
onClick={() => onDelete(node)}
icon={<DeleteOutlined />}
/>
</Tooltip>
)}
</div>
</div>
<div className="children">
{node?.children?.map(childNode => (
<DataModelNode
className="in-hierarchy"
node={childNode}
key={childNode.name}
onMoveToHierarchy={onMoveToHierarchy}
onNodeTypeChange={onNodeTypeChange}
onDeleteFromHierarchy={onDeleteFromHierarchy(node)}
/>
))}
</div>
</>
);
};
return (
<Draggable
key={node?.name}
draggableId={node?.name}
index={node?.index}
isDragDisabled={true}
>
{(draggableProvided, draggableSnapshot) => {
return (
<StyledDataModelBranch
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>
<Droppable
droppableId={node?.name}
type={TreeNodeHierarchy.Branch}
isCombineEnabled={false}
>
{(droppableProvided, droppableSnapshot) => (
<div ref={droppableProvided.innerRef}>
{renderNode(node, draggableSnapshot.isDragging)}
{droppableProvided.placeholder}
</div>
)}
</Droppable>
</StyledDataModelBranch>
);
}}
</Draggable>
);
},
)
Example #28
Source File: DownloadQueue.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 4 votes |
export default function DownloadQueue() {
const [, setWsClient] = useState<WebSocket>();
const [queueState, setQueueState] = useState<IQueue>(initialQueue);
const { queue, status } = queueState;
const history = useHistory();
const theme = useTheme();
const { setTitle, setAction } = useContext(NavbarContext);
const toggleQueueStatus = () => {
if (status === 'Stopped') {
client.get('/api/v1/downloads/start');
} else {
client.get('/api/v1/downloads/stop');
}
};
useEffect(() => {
setTitle('Download Queue');
setAction(() => {
if (status === 'Stopped') {
return (
<IconButton onClick={toggleQueueStatus} size="large">
<PlayArrowIcon />
</IconButton>
);
}
return (
<IconButton onClick={toggleQueueStatus} size="large">
<PauseIcon />
</IconButton>
);
});
}, [status]);
useEffect(() => {
const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`);
wsc.onmessage = (e) => {
setQueueState(JSON.parse(e.data));
};
setWsClient(wsc);
}, []);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onDragEnd = (result: DropResult) => {
};
if (queue.length === 0) {
return <EmptyView message="No downloads" />;
}
return (
<>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<List ref={provided.innerRef}>
{queue.map((item, index) => (
<Draggable
key={`${item.mangaId}-${item.chapterIndex}`}
draggableId={`${item.mangaId}-${item.chapterIndex}`}
index={index}
>
{(provided, snapshot) => (
<ListItem
ContainerProps={{ ref: provided.innerRef } as any}
sx={{
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'flex-start',
padding: 2,
margin: '10px',
'&:hover': {
backgroundColor: 'action.hover',
transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
},
'&:active': {
backgroundColor: 'action.selected',
transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
},
}}
onClick={() => history.push(`/manga/${item.chapter.mangaId}`)}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style,
theme.palette,
)}
ref={provided.innerRef}
>
<ListItemIcon sx={{ margin: 'auto 0' }}>
<DragHandleIcon />
</ListItemIcon>
<Box sx={{ display: 'flex' }}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant="h5" component="h2">
{item.manga.title}
</Typography>
<Typography variant="caption" display="block" gutterBottom>
{`${item.chapter.name} `
+ `(${(item.progress * 100).toFixed(2)}%)`
+ ` => state: ${item.state}`}
</Typography>
</Box>
</Box>
<IconButton
sx={{ marginLeft: 'auto' }}
onClick={(e) => {
// deleteCategory(index);
// prevent parent tags from getting the event
e.stopPropagation();
}}
size="large"
>
<DeleteIcon />
</IconButton>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
</>
);
}
Example #29
Source File: index.tsx From taskcafe with MIT License | 4 votes |
TaskDetails: React.FC<TaskDetailsProps> = ({
me,
onCancelCommentEdit,
task,
editableComment = null,
onDeleteChecklist,
onToggleTaskWatch,
onTaskNameChange,
onCommentShowActions,
onOpenAddChecklistPopup,
onChangeChecklistName,
onCreateComment,
onChecklistDrop,
onChecklistItemDrop,
onToggleTaskComplete,
onTaskDescriptionChange,
onChangeItemName,
onDeleteItem,
onDeleteTask,
onCloseModal,
onUpdateComment,
onOpenAddMemberPopup,
onOpenAddLabelPopup,
onOpenDueDatePopop,
onAddItem,
onToggleChecklistItem,
onMemberProfile,
}) => {
const { user } = useCurrentUser();
const [taskName, setTaskName] = useState(task.name);
const [editTaskDescription, setEditTaskDescription] = useState(() => {
if (task.description) {
if (task.description.trim() === '' || task.description.trim() === '\\') {
return true;
}
return false;
}
return true;
});
const [saveTimeout, setSaveTimeout] = useState<any>(null);
const [showRaw, setShowRaw] = useState(false);
const taskDescriptionRef = useRef(task.description ?? '');
const $noMemberBtn = useRef<HTMLDivElement>(null);
const $addMemberBtn = useRef<HTMLDivElement>(null);
const $dueDateBtn = useRef<HTMLDivElement>(null);
const $detailsTitle = useRef<HTMLTextAreaElement>(null);
const activityStream: Array<{ id: string; data: { time: string; type: 'comment' | 'activity' } }> = [];
if (task.activity) {
task.activity.forEach((activity) => {
activityStream.push({
id: activity.id,
data: {
time: activity.createdAt,
type: 'activity',
},
});
});
}
if (task.comments) {
task.comments.forEach((comment) => {
activityStream.push({
id: comment.id,
data: {
time: comment.createdAt,
type: 'comment',
},
});
});
}
activityStream.sort((a, b) => (dayjs(a.data.time).isAfter(dayjs(b.data.time)) ? 1 : -1));
const saveDescription = () => {
onTaskDescriptionChange(task, taskDescriptionRef.current);
};
return (
<Container>
<LeftSidebar>
<LeftSidebarContent>
<LeftSidebarSection>
<SidebarTitle>TASK GROUP</SidebarTitle>
<SidebarButton>
<SidebarButtonText>{task.taskGroup.name}</SidebarButtonText>
</SidebarButton>
<DueDateTitle>DUE DATE</DueDateTitle>
<SidebarButton
ref={$dueDateBtn}
onClick={() => {
if (user) {
onOpenDueDatePopop(task, $dueDateBtn);
}
}}
>
{task.dueDate.at ? (
<SidebarButtonText>
{dayjs(task.dueDate.at).format(task.hasTime ? 'MMM D [at] h:mm A' : 'MMMM D')}
</SidebarButtonText>
) : (
<SidebarButtonText>No due date</SidebarButtonText>
)}
</SidebarButton>
</LeftSidebarSection>
<AssignedUsersSection>
<DueDateTitle>MEMBERS</DueDateTitle>
{task.assigned && task.assigned.length !== 0 ? (
<MemberList>
{task.assigned.map((m) => (
<TaskMember
key={m.id}
member={m}
size={32}
onMemberProfile={($target) => {
if (user) {
onMemberProfile($target, m.id);
}
}}
/>
))}
<AssignUserIcon
ref={$addMemberBtn}
onClick={() => {
if (user) {
onOpenAddMemberPopup(task, $addMemberBtn);
}
}}
>
<Plus width={16} height={16} />
</AssignUserIcon>
</MemberList>
) : (
<AssignUsersButton
ref={$noMemberBtn}
onClick={() => {
if (user) {
onOpenAddMemberPopup(task, $noMemberBtn);
}
}}
>
<AssignUserIcon>
<User width={16} height={16} />
</AssignUserIcon>
<AssignUserLabel>No members</AssignUserLabel>
</AssignUsersButton>
)}
</AssignedUsersSection>
{user && (
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton
onClick={($target) => {
onOpenAddLabelPopup(task, $target);
}}
icon={<Tags width={12} height={12} />}
>
Labels
</ActionButton>
<ActionButton
onClick={($target) => {
onOpenAddChecklistPopup(task, $target);
}}
icon={<CheckSquareOutline width={12} height={12} />}
>
Checklist
</ActionButton>
<ActionButton>Cover</ActionButton>
<ActionButton
onClick={() => {
onToggleTaskWatch(task, !task.watched);
}}
icon={<Eye width={12} height={12} />}
>
Watch {task.watched && <WatchedCheckmark width={18} height={18} />}
</ActionButton>
</ExtraActionsSection>
)}
</LeftSidebarContent>
</LeftSidebar>
<ContentContainer>
<HeaderContainer>
<HeaderInnerContainer>
<HeaderLeft>
<MarkCompleteButton
disabled={user === null}
invert={task.complete ?? false}
onClick={() => {
if (user) {
onToggleTaskComplete(task);
}
}}
>
<Checkmark width={8} height={8} />
<span>{task.complete ? 'Completed' : 'Mark complete'}</span>
</MarkCompleteButton>
</HeaderLeft>
{user && (
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Clone width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Share width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon onClick={() => onDeleteTask(task)}>
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
)}
</HeaderInnerContainer>
<TaskDetailsTitleWrapper>
<TaskDetailsTitle
value={taskName}
ref={$detailsTitle}
disabled={user === null}
onKeyDown={(e) => {
if (e.keyCode === 13) {
e.preventDefault();
if ($detailsTitle && $detailsTitle.current) {
$detailsTitle.current.blur();
}
}
}}
onChange={(e) => {
setTaskName(e.currentTarget.value);
}}
onBlur={() => {
if (taskName !== task.name) {
onTaskNameChange(task, taskName);
}
}}
/>
</TaskDetailsTitleWrapper>
<Labels>
{task.labels.length !== 0 && (
<MetaDetailContent>
{task.labels.map((label) => {
return (
<TaskLabelItem
key={label.projectLabel.id}
label={label}
onClick={($target) => {
onOpenAddLabelPopup(task, $target);
}}
/>
);
})}
<TaskDetailsAddLabelIcon>
<Plus width={12} height={12} />
</TaskDetailsAddLabelIcon>
</MetaDetailContent>
)}
</Labels>
</HeaderContainer>
<InnerContentContainer>
<DescriptionContainer>
{showRaw ? (
<TaskDetailsEditor value={taskDescriptionRef.current} />
) : (
<EditorContainer
onClick={(e) => {
if (!editTaskDescription) {
setEditTaskDescription(true);
}
}}
>
<Editor
defaultValue={task.description ?? ''}
readOnly={user === null || !editTaskDescription}
theme={dark}
autoFocus
onChange={(value) => {
setSaveTimeout(() => {
clearTimeout(saveTimeout);
return setTimeout(saveDescription, 2000);
});
const text = value();
taskDescriptionRef.current = text;
}}
/>
</EditorContainer>
)}
<ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton>
</DescriptionContainer>
<ChecklistSection>
<DragDropContext onDragEnd={(result) => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>
<Droppable direction="vertical" type="checklist" droppableId="root">
{(dropProvided) => (
<ChecklistContainer {...dropProvided.droppableProps} ref={dropProvided.innerRef}>
{task.checklists &&
task.checklists
.slice()
.sort((a, b) => a.position - b.position)
.map((checklist, idx) => (
<Draggable key={checklist.id} draggableId={checklist.id} index={idx}>
{(provided) => (
<Checklist
ref={provided.innerRef}
wrapperProps={provided.draggableProps}
handleProps={provided.dragHandleProps}
key={checklist.id}
name={checklist.name}
checklistID={checklist.id}
items={checklist.items}
onDeleteChecklist={onDeleteChecklist}
onChangeName={(newName) => onChangeChecklistName(checklist.id, newName)}
onToggleItem={onToggleChecklistItem}
onDeleteItem={onDeleteItem}
onAddItem={(n) => {
if (task.checklists) {
let position = 65535;
const [lastItem] = checklist.items
.sort((a, b) => a.position - b.position)
.slice(-1);
if (lastItem) {
position = lastItem.position * 2 + 1;
}
onAddItem(checklist.id, n, position);
}
}}
onChangeItemName={onChangeItemName}
>
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
{(checklistDrop) => (
<>
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
{checklist.items
.slice()
.sort((a, b) => a.position - b.position)
.map((item, itemIdx) => (
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
{(itemDrop) => (
<ChecklistItem
key={item.id}
itemID={item.id}
checklistID={item.taskChecklistID}
ref={itemDrop.innerRef}
wrapperProps={itemDrop.draggableProps}
handleProps={itemDrop.dragHandleProps}
name={item.name}
complete={item.complete}
onDeleteItem={onDeleteItem}
onChangeName={onChangeItemName}
onToggleItem={(itemID, complete) => {
onToggleChecklistItem(item.id, complete);
}}
/>
)}
</Draggable>
))}
</ChecklistItems>
{checklistDrop.placeholder}
</>
)}
</Droppable>
</Checklist>
)}
</Draggable>
))}
{dropProvided.placeholder}
</ChecklistContainer>
)}
</Droppable>
</DragDropContext>
</ChecklistSection>
<TabBarSection>
<TabBarItem>Activity</TabBarItem>
</TabBarSection>
<ActivitySection>
{activityStream.map((stream) =>
stream.data.type === 'comment' ? (
<StreamComment
key={stream.id}
onExtraActions={onCommentShowActions}
onCancelCommentEdit={onCancelCommentEdit}
onUpdateComment={(message) => onUpdateComment(stream.id, message)}
editable={stream.id === editableComment}
comment={task.comments && task.comments.find((comment) => comment.id === stream.id)}
/>
) : (
<StreamActivity
key={stream.id}
activity={task.activity && task.activity.find((activity) => activity.id === stream.id)}
/>
),
)}
</ActivitySection>
</InnerContentContainer>
{me && (
<CommentContainer>
<CommentCreator
me={me}
onCreateComment={(message) => onCreateComment(task, message)}
onMemberProfile={onMemberProfile}
/>
</CommentContainer>
)}
</ContentContainer>
</Container>
);
}