Skip to content

Instantly share code, notes, and snippets.

@humphreybc
Last active October 13, 2023 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save humphreybc/f9548baa8611ad3fe6ecf28fe2dc7b0a to your computer and use it in GitHub Desktop.
Save humphreybc/f9548baa8611ad3fe6ecf28fe2dc7b0a to your computer and use it in GitHub Desktop.
Dovetail NoteGroup component
import * as csx from "csx";
import { types } from "dovetail/graphql";
import { NoteGroupColor } from "dovetail/types";
import { NoteSortDropdownChoice } from "dovetail/ui/dropdown/NoteSortDropdown";
import { OutsideClick } from "dovetail/ui/layout/OutsideClick";
import { NewNoteItem } from "dovetail/ui/note";
import { CreateNoteItem } from "dovetail/ui/note/CreateNoteItem";
import { Cache } from "dovetail/ui/util/Cache";
import { makeStyleSafe } from "dovetail/util/reactBeautifulDnd";
import * as text from "dovetail/util/text";
import { BORDER_RADIUS, BOX_SHADOW_FOCUS, COLORS } from "dovetail/variables";
import React from "react";
import { Draggable, Droppable } from "react-beautiful-dnd";
import { styled } from "typestyle-react";
import { NoteGroupHeader } from "./NoteGroupHeader";
const enum DragType {
NOTE = "NOTE"
}
interface Props {
collapsed: boolean;
color: NoteGroupColor;
focusTitle?: boolean;
isLifted: boolean;
items: Array<{
id: string;
render: () => React.ReactElement<{}>;
}>;
layout: "LIST" | "BOARD";
noteGroupId: string;
noteType: types.NoteType;
onCollapsedChange: (newCollapsed: boolean) => void;
onColorChange: (newColor: NoteGroupColor) => void;
onDelete: () => void;
onNoteCreate: (title: string, positionAtTop: boolean) => void;
onSort: (type: NoteSortDropdownChoice) => void;
onTitleSave: (newValue: string) => void;
title: string;
}
interface State {
creating: boolean;
newNoteTitle: string;
positionNewNoteAtTop: boolean;
}
export class NoteGroup extends React.PureComponent<Props, State> {
public state: State = {
creating: false,
newNoteTitle: "",
positionNewNoteAtTop: false
};
public render() {
const {
collapsed,
color,
focusTitle,
isLifted,
items,
layout,
noteGroupId,
noteType,
onColorChange,
onDelete,
onSort,
onTitleSave,
title
} = this.props;
const { creating, positionNewNoteAtTop } = this.state;
const createNoteItem = (
<NoteGroupItem>
<OutsideClick onOutsideClick={this.toggleCreating}>
<CreateNoteItem
layout={layout}
onChange={e => {
this.setState({ newNoteTitle: e.target.value });
}}
onDismiss={this.toggleCreating}
onPositionChange={() => {
this.setState({ positionNewNoteAtTop: !this.state.positionNewNoteAtTop });
}}
onSave={this.createNote}
placeholder={text.smartNoteTitle("", noteType)}
positionAtTop={this.state.positionNewNoteAtTop}
title={this.state.newNoteTitle}
/>
</OutsideClick>
</NoteGroupItem>
);
return (
<GroupContainer styled={{ color }} tabIndex={0}>
<NoteGroupHeader
collapsed={collapsed}
color={color}
focusTitle={focusTitle}
layout={layout}
onCollapseExpandClick={this.handleCollapseExpandClick}
onColorChange={onColorChange}
onCreateClick={() => {
this.setState({
creating: true,
positionNewNoteAtTop: true
});
}}
onDelete={onDelete}
onSort={onSort}
onTitleSave={onTitleSave}
title={title}
totalCount={items.length}
/>
{!collapsed ? (
<>
<Content>
{creating && positionNewNoteAtTop === true ? createNoteItem : null}
<Droppable droppableId={noteGroupId} type={DragType.NOTE}>
{({ innerRef }, { isDraggingOver }) => (
<div ref={innerRef}>
<Cache
shouldUpdate={isLifted === false}
render={() => {
if (collapsed) {
return null;
}
if (items.length > 0) {
return items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{({ draggableProps, dragHandleProps, innerRef }) => {
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/22902/files#r162243798
const { style, ...nonStyleDraggableProps } = draggableProps;
const customDragHandleProps = { ...dragHandleProps, tabIndex: -1 };
return (
<NoteGroupItem
innerRef={innerRef}
style={makeStyleSafe(style)}
{...nonStyleDraggableProps}
{...customDragHandleProps}
>
{item.render()}
</NoteGroupItem>
);
}}
</Draggable>
));
} else if (creating === false) {
return <MinHeight styled={{ isDraggingOver }} />;
} else {
return null;
}
}}
/>
</div>
)}
</Droppable>
{creating && positionNewNoteAtTop === false ? createNoteItem : null}
</Content>
<NewNoteItem onClick={this.toggleCreating} color={color} type={noteType} />
</>
) : null}
</GroupContainer>
);
}
private readonly handleCollapseExpandClick = () => {
this.props.onCollapsedChange(!this.props.collapsed);
};
private readonly toggleCreating = () => {
this.setState({ creating: !this.state.creating });
};
private readonly createNote = () => {
this.props.onNoteCreate(this.state.newNoteTitle, this.state.positionNewNoteAtTop);
this.setState({ newNoteTitle: "" });
};
}
const NoteGroupItem = styled("div", {
marginTop: "8px",
outline: "none"
});
const GroupContainer = styled("div", ({ color }: { color: NoteGroupColor }) => ({
backgroundColor:
color !== null
? csx
.color(color)
.tint(0.9)
.toString()
: COLORS.i04,
borderRadius: BORDER_RADIUS,
color: color !== null ? COLORS.white : COLORS.i60,
display: "flex",
flexDirection: "column",
maxHeight: "100%",
outline: "none",
overflow: "hidden",
$nest: {
"&:focus": {
boxShadow: BOX_SHADOW_FOCUS
}
}
}));
const Content = styled("div", {
overflowY: "auto",
overflowX: "hidden",
padding: "0 8px 8px"
});
const MinHeight = styled("div", ({ isDraggingOver }: { isDraggingOver: boolean }) => ({
height: isDraggingOver ? "84px" : "48px"
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment