Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Southclaws committed Jun 23, 2024
1 parent a87bf2f commit 52c746a
Show file tree
Hide file tree
Showing 82 changed files with 497 additions and 1,318 deletions.
6 changes: 5 additions & 1 deletion web/panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { map } from "lodash/fp";
import { admonition } from "@/recipes/admonition";
import { headingInput } from "@/recipes/heading-input";
import { richCard } from "@/recipes/rich-card";
import { treeView } from "@/recipes/tree-view";
import { typographyHeading } from "@/recipes/typography-heading";

// TODO: Dark mode = 40%
Expand Down Expand Up @@ -113,7 +114,7 @@ const semanticTokens = defineSemanticTokens({
export default defineConfig({
presets: [
"@pandacss/preset-base",
"@park-ui/panda-preset",
// "@park-ui/panda-preset",
createPreset({
// NOTE: This is just for Park-ui's preset, the actual accent colour is
// set by the administrator and is a dynamic runtime value.
Expand Down Expand Up @@ -231,6 +232,9 @@ export default defineConfig({
typographyHeading: typographyHeading,
richCard: richCard,
},
slotRecipes: {
treeView: treeView,
},
extend: {
semanticTokens,
tokens: defineTokens({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { DatagraphNode } from "../DatagraphNode";
export type Props = {
node: DatagraphNode;
onVisibilityChange?: (v: Visibility) => Promise<void>;
onDelete: () => void;
onDelete: (slug: string) => void;
};

export function useDatagraphNodeMenu(props: Props) {
const account = useSession();
const deleteProps = useDeleteAction({
onClick: props.onDelete,
onClick: () => props.onDelete(props.node.slug),
});

const isVisibilityChangeEnabled = props.onVisibilityChange ?? false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import type { Assign } from "@ark-ui/react";
import {
TreeView as ArkTreeView,
type TreeViewRootProps,
} from "@ark-ui/react/tree-view";
import {
DndContext,
DragOverlay,
closestCenter,
closestCorners,
useDraggable,
useDroppable,
} from "@dnd-kit/core";
import { CSS, useCombinedRefs } from "@dnd-kit/utilities";
import Link from "next/link";
import { forwardRef } from "react";

import { NodeWithChildren } from "@/api/openapi/schemas";
import { css, cx } from "@/styled-system/css";
import { splitCssProps } from "@/styled-system/jsx";
import { type TreeViewVariantProps, treeView } from "@/styled-system/recipes";
import { token } from "@/styled-system/tokens";
import type { JsxStyleProps } from "@/styled-system/types";

import { DatagraphNodeMenu } from "../DatagraphNodeMenu/DatagraphNodeMenu";

import { useDatagraphNodeTree } from "./useDatagraphNodeTree";

export interface TreeViewData {
label: string;
children: NodeWithChildren[];
}

export interface TreeViewProps
extends Assign<JsxStyleProps, TreeViewRootProps>,
TreeViewVariantProps {
data: TreeViewData;
}

export const DatagraphNodeTree = forwardRef<HTMLDivElement, TreeViewProps>(
(props, ref) => {
const { sensors, handleDragEnd, handleDelete } = useDatagraphNodeTree();
const [cssProps, localProps] = splitCssProps(props);
const { data, className, ...rootProps } = localProps;
const styles = treeView();

const renderChild = (child: NodeWithChildren) => {
return (
<ArkTreeView.Branch
key={child.id}
value={child.id}
className={styles.branch}
>
<TreeBranch
styles={styles}
child={child}
handleDelete={handleDelete}
/>

<ArkTreeView.BranchContent className={styles.branchContent}>
{child.children?.map((child) =>
child.children ? (
renderChild(child)
) : (
<TreeItem
key={child.id}
styles={styles}
child={child}
handleDelete={handleDelete}
/>
),
)}
</ArkTreeView.BranchContent>
</ArkTreeView.Branch>
);
};

return (
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragEnd={handleDragEnd}
onDragStart={console.log}
>
<ArkTreeView.Root
ref={ref}
aria-label={data.label}
className={cx(styles.root, css(cssProps), className)}
{...rootProps}
>
<ArkTreeView.Tree className={styles.tree}>
{data.children.map(renderChild)}
</ArkTreeView.Tree>
</ArkTreeView.Root>

{/* <DragOverlay>
<p>test</p>
</DragOverlay> */}
</DndContext>
);
},
);

DatagraphNodeTree.displayName = "DatagraphNodeTree";

type BranchProps = {
child: NodeWithChildren;
styles: any;
handleDelete: (slug: string) => void;
};

function TreeBranch({ styles, child, handleDelete }: BranchProps) {
const {
attributes,
listeners,
setNodeRef: setDraggableNodeRef,
transform,
isDragging,
} = useDraggable({ id: child.id });

const { setNodeRef: setDroppableNodeRef } = useDroppable({
id: child.id,
});

const setNodeRef = useCombinedRefs(setDraggableNodeRef, setDroppableNodeRef);

const dragStyle = {
transform: CSS.Transform.toString(transform),
};

const conditionalDragStyles = css({
cursor: isDragging ? "grabbing" : "grab",
...(isDragging && {
pointerEvents: "none",
}),
});

return (
<ArkTreeView.BranchControl
ref={setNodeRef}
style={dragStyle}
className={cx(styles.branchControl, conditionalDragStyles)}
{...attributes}
{...listeners}
>
<ArkTreeView.BranchIndicator className={styles.branchIndicator}>
{child.children?.length ? <ChevronRightIcon /> : <BulletIcon />}
</ArkTreeView.BranchIndicator>

<ArkTreeView.BranchText asChild className={styles.branchText}>
{isDragging ? (
<p>{child.name}</p>
) : (
<Link
href={`/directory/${child.slug}`}
className={conditionalDragStyles}
>
{child.name}
</Link>
)}
</ArkTreeView.BranchText>

<DatagraphNodeMenu node={child} onDelete={handleDelete} />
</ArkTreeView.BranchControl>
);
}

function TreeItem({ styles, child }: BranchProps) {
const {
attributes,
listeners,
setNodeRef: setDraggableNodeRef,
transform,
isDragging,
} = useDraggable({ id: child.id });

const { setNodeRef: setDroppableNodeRef } = useDroppable({
id: child.id,
});

const setNodeRef = useCombinedRefs(setDraggableNodeRef, setDroppableNodeRef);

const dragStyle = {
transform: CSS.Transform.toString(transform),
};

return (
<ArkTreeView.Item
ref={setNodeRef}
style={dragStyle}
value={child.id}
className={cx(
styles.item,
css({
cursor: isDragging ? "grabbing" : "grab",
...(isDragging && {
pointerEvents: "none",
}),
}),
)}
{...attributes}
{...listeners}
>
<ArkTreeView.ItemText className={styles.itemText}>
<Link
href={`/directory/${child.slug}`}
className={conditionalDragStyle}
>
{child.name}
</Link>
</ArkTreeView.ItemText>
</ArkTreeView.Item>
);
}

const ChevronRightIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>Chevron Right Icon</title>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m9 18l6-6l-6-6"
/>
</svg>
);

const BulletIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill={token("colors.fg.muted")}
>
<circle cx="12.1" cy="12.1" r="2.5" />
</svg>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
import { mutate } from "swr";

import { nodeDelete } from "@/api/openapi/nodes";

export function useDatagraphNodeTree() {
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
delay: 50,
tolerance: 5,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);

async function handleDragEnd(event: any) {
console.log(event);
}

async function handleDelete(slug: string) {
await nodeDelete(slug);
await mutate("/api/v1/nodes");
}

return {
handleDelete,
sensors,
handleDragEnd,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { reduce } from "lodash/fp";

import { useNodeList } from "@/api/openapi/nodes";
import { NodeWithChildren } from "@/api/openapi/schemas";
import { DatagraphNodeTree } from "@/components/directory/datagraph/DatagraphNodeTree/DatagraphNodeTree";
import { Child, TreeView, TreeViewData } from "@/components/ui/tree-view";

const recursivelyMapChildren = reduce<NodeWithChildren, Child[]>(
Expand All @@ -25,12 +26,12 @@ export function DatagraphNavTree() {

if (!data) return null;

const children: Child[] = recursivelyMapChildren(data.nodes);

const root = {
label: "Root",
children,
} satisfies TreeViewData;

return <TreeView data={root} />;
return (
<DatagraphNodeTree
data={{
label: "Directory",
children: data.nodes,
}}
/>
);
}
1 change: 1 addition & 0 deletions web/src/components/ui/tree-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface TreeViewProps
extends Assign<JsxStyleProps, TreeViewRootProps>,
TreeViewVariantProps {
data: TreeViewData;
onSelectMenu?: (value: string) => void;
}

export const TreeView = forwardRef<HTMLDivElement, TreeViewProps>(
Expand Down
Loading

0 comments on commit 52c746a

Please sign in to comment.