mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
330 lines
10 KiB
TypeScript
330 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
|
|
import type * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
import type { TTableElement } from '@udecode/plate-table';
|
|
|
|
import { PopoverAnchor } from '@radix-ui/react-popover';
|
|
import { cn, withRef } from '@udecode/cn';
|
|
import { isSelectionExpanded } from '@udecode/plate-common';
|
|
import {
|
|
useEditorRef,
|
|
useEditorSelector,
|
|
useElement,
|
|
useRemoveNodeButton,
|
|
withHOC,
|
|
} from '@udecode/plate-common/react';
|
|
import {
|
|
TableProvider,
|
|
mergeTableCells,
|
|
unmergeTableCells,
|
|
useTableBordersDropdownMenuContentState,
|
|
useTableElement,
|
|
useTableElementState,
|
|
useTableMergeState,
|
|
} from '@udecode/plate-table/react';
|
|
import { type LucideProps, Combine, Trash2Icon, Ungroup } from 'lucide-react';
|
|
import { useReadOnly, useSelected } from 'slate-react';
|
|
|
|
import { Button } from './button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuCheckboxItem,
|
|
DropdownMenuContent,
|
|
DropdownMenuGroup,
|
|
DropdownMenuPortal,
|
|
DropdownMenuTrigger,
|
|
} from './dropdown-menu';
|
|
import { PlateElement } from './plate-element';
|
|
import { Popover, PopoverContent, popoverVariants } from './popover';
|
|
|
|
export const TableBordersDropdownMenuContent = withRef<
|
|
typeof DropdownMenuPrimitive.Content
|
|
>((props, ref) => {
|
|
const {
|
|
getOnSelectTableBorder,
|
|
hasBottomBorder,
|
|
hasLeftBorder,
|
|
hasNoBorders,
|
|
hasOuterBorders,
|
|
hasRightBorder,
|
|
hasTopBorder,
|
|
} = useTableBordersDropdownMenuContentState();
|
|
|
|
return (
|
|
<DropdownMenuContent
|
|
ref={ref}
|
|
className={cn('min-w-[220px]')}
|
|
align="start"
|
|
side="right"
|
|
sideOffset={0}
|
|
{...props}
|
|
>
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasBottomBorder}
|
|
onCheckedChange={getOnSelectTableBorder('bottom')}
|
|
>
|
|
<BorderBottom />
|
|
<div>Bottom Border</div>
|
|
</DropdownMenuCheckboxItem>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasTopBorder}
|
|
onCheckedChange={getOnSelectTableBorder('top')}
|
|
>
|
|
<BorderTop />
|
|
<div>Top Border</div>
|
|
</DropdownMenuCheckboxItem>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasLeftBorder}
|
|
onCheckedChange={getOnSelectTableBorder('left')}
|
|
>
|
|
<BorderLeft />
|
|
<div>Left Border</div>
|
|
</DropdownMenuCheckboxItem>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasRightBorder}
|
|
onCheckedChange={getOnSelectTableBorder('right')}
|
|
>
|
|
<BorderRight />
|
|
<div>Right Border</div>
|
|
</DropdownMenuCheckboxItem>
|
|
</DropdownMenuGroup>
|
|
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasNoBorders}
|
|
onCheckedChange={getOnSelectTableBorder('none')}
|
|
>
|
|
<BorderNone />
|
|
<div>No Border</div>
|
|
</DropdownMenuCheckboxItem>
|
|
<DropdownMenuCheckboxItem
|
|
checked={hasOuterBorders}
|
|
onCheckedChange={getOnSelectTableBorder('outer')}
|
|
>
|
|
<BorderAll />
|
|
<div>Outside Borders</div>
|
|
</DropdownMenuCheckboxItem>
|
|
</DropdownMenuGroup>
|
|
</DropdownMenuContent>
|
|
);
|
|
});
|
|
|
|
export const TableFloatingToolbar = withRef<typeof PopoverContent>(
|
|
({ children, ...props }, ref) => {
|
|
const element = useElement<TTableElement>();
|
|
const { props: buttonProps } = useRemoveNodeButton({ element });
|
|
|
|
const selectionCollapsed = useEditorSelector(
|
|
(editor) => !isSelectionExpanded(editor),
|
|
[]
|
|
);
|
|
|
|
const readOnly = useReadOnly();
|
|
const selected = useSelected();
|
|
const editor = useEditorRef();
|
|
|
|
const collapsed = !readOnly && selected && selectionCollapsed;
|
|
const open = !readOnly && selected;
|
|
|
|
const { canMerge, canUnmerge } = useTableMergeState();
|
|
|
|
const mergeContent = canMerge && (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => mergeTableCells(editor)}
|
|
contentEditable={false}
|
|
isMenu
|
|
>
|
|
<Combine />
|
|
Merge
|
|
</Button>
|
|
);
|
|
|
|
const unmergeButton = canUnmerge && (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => unmergeTableCells(editor)}
|
|
contentEditable={false}
|
|
isMenu
|
|
>
|
|
<Ungroup />
|
|
Unmerge
|
|
</Button>
|
|
);
|
|
|
|
const bordersContent = collapsed && (
|
|
<>
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" isMenu>
|
|
<BorderAll />
|
|
Borders
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
|
|
<DropdownMenuPortal>
|
|
<TableBordersDropdownMenuContent />
|
|
</DropdownMenuPortal>
|
|
</DropdownMenu>
|
|
|
|
<Button variant="ghost" contentEditable={false} isMenu {...buttonProps}>
|
|
<Trash2Icon />
|
|
Delete
|
|
</Button>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<Popover open={open} modal={false}>
|
|
<PopoverAnchor asChild>{children}</PopoverAnchor>
|
|
{(canMerge || canUnmerge || collapsed) && (
|
|
<PopoverContent
|
|
ref={ref}
|
|
className={cn(popoverVariants(), 'flex w-[220px] flex-col p-1')}
|
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
{...props}
|
|
>
|
|
{unmergeButton}
|
|
{mergeContent}
|
|
{bordersContent}
|
|
</PopoverContent>
|
|
)}
|
|
</Popover>
|
|
);
|
|
}
|
|
);
|
|
|
|
export const TableElement = withHOC(
|
|
TableProvider,
|
|
withRef<typeof PlateElement>(({ children, className, ...props }, ref) => {
|
|
const { colSizes, isSelectingCell, marginLeft, minColumnWidth } =
|
|
useTableElementState();
|
|
const { colGroupProps, props: tableProps } = useTableElement();
|
|
|
|
return (
|
|
<TableFloatingToolbar>
|
|
<PlateElement
|
|
className={cn('overflow-x-auto', className)}
|
|
style={{ paddingLeft: marginLeft }}
|
|
{...props}
|
|
>
|
|
<table
|
|
ref={ref}
|
|
className={cn(
|
|
'my-4 ml-px mr-0 table h-px w-[calc(100%-6px)] table-fixed border-collapse',
|
|
isSelectingCell && '[&_*::selection]:bg-none'
|
|
)}
|
|
{...tableProps}
|
|
>
|
|
<colgroup {...colGroupProps}>
|
|
{colSizes.map((width, index) => (
|
|
<col
|
|
key={index}
|
|
style={{
|
|
minWidth: minColumnWidth,
|
|
width: width || undefined,
|
|
}}
|
|
/>
|
|
))}
|
|
</colgroup>
|
|
|
|
<tbody className="min-w-full">{children}</tbody>
|
|
</table>
|
|
</PlateElement>
|
|
</TableFloatingToolbar>
|
|
);
|
|
})
|
|
);
|
|
|
|
const BorderAll = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M3 6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm10 13h5a1 1 0 0 0 1-1v-5h-6v6zm-2-6H5v5a1 1 0 0 0 1 1h5v-6zm2-2h6V6a1 1 0 0 0-1-1h-5v6zm-2-6H6a1 1 0 0 0-1 1v5h6V5z" />
|
|
</svg>
|
|
);
|
|
|
|
const BorderBottom = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M13 5a1 1 0 1 0 0-2h-2a1 1 0 1 0 0 2h2zm-8 6a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2zm-2 7a1 1 0 1 1 2 0 1 1 0 0 0 1 1h12a1 1 0 0 0 1-1 1 1 0 1 1 2 0 3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm17-8a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1zM7 4a1 1 0 0 0-1-1 3 3 0 0 0-3 3 1 1 0 0 0 2 0 1 1 0 0 1 1-1 1 1 0 0 0 1-1zm11-1a1 1 0 1 0 0 2 1 1 0 0 1 1 1 1 1 0 1 0 2 0 3 3 0 0 0-3-3z" />
|
|
</svg>
|
|
);
|
|
|
|
const BorderLeft = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M6 21a1 1 0 1 0 0-2 1 1 0 0 1-1-1V6a1 1 0 0 1 1-1 1 1 0 0 0 0-2 3 3 0 0 0-3 3v12a3 3 0 0 0 3 3zm7-16a1 1 0 1 0 0-2h-2a1 1 0 1 0 0 2h2zm6 6a1 1 0 1 1 2 0v2a1 1 0 1 1-2 0v-2zm-5 9a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zm4-17a1 1 0 1 0 0 2 1 1 0 0 1 1 1 1 1 0 1 0 2 0 3 3 0 0 0-3-3zm-1 17a1 1 0 0 0 1 1 3 3 0 0 0 3-3 1 1 0 1 0-2 0 1 1 0 0 1-1 1 1 1 0 0 0-1 1z" />
|
|
</svg>
|
|
);
|
|
|
|
const BorderNone = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M14 4a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zm-9 7a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2zm14 0a1 1 0 1 1 2 0v2a1 1 0 1 1-2 0v-2zm-6 10a1 1 0 1 0 0-2h-2a1 1 0 1 0 0 2h2zM7 4a1 1 0 0 0-1-1 3 3 0 0 0-3 3 1 1 0 0 0 2 0 1 1 0 0 1 1-1 1 1 0 0 0 1-1zm11-1a1 1 0 1 0 0 2 1 1 0 0 1 1 1 1 1 0 1 0 2 0 3 3 0 0 0-3-3zM7 20a1 1 0 0 1-1 1 3 3 0 0 1-3-3 1 1 0 1 1 2 0 1 1 0 0 0 1 1 1 1 0 0 1 1 1zm11 1a1 1 0 1 1 0-2 1 1 0 0 0 1-1 1 1 0 1 1 2 0 3 3 0 0 1-3 3z" />
|
|
</svg>
|
|
);
|
|
|
|
const BorderRight = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M13 5a1 1 0 1 0 0-2h-2a1 1 0 1 0 0 2h2zm-8 6a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2zm9 9a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zM6 3a1 1 0 0 1 0 2 1 1 0 0 0-1 1 1 1 0 0 1-2 0 3 3 0 0 1 3-3zm1 17a1 1 0 0 1-1 1 3 3 0 0 1-3-3 1 1 0 1 1 2 0 1 1 0 0 0 1 1 1 1 0 0 1 1 1zm11 1a1 1 0 1 1 0-2 1 1 0 0 0 1-1V6a1 1 0 0 0-1-1 1 1 0 1 1 0-2 3 3 0 0 1 3 3v12a3 3 0 0 1-3 3z" />
|
|
</svg>
|
|
);
|
|
|
|
const BorderTop = (props: LucideProps) => (
|
|
<svg
|
|
fill="currentColor"
|
|
focusable="false"
|
|
height="48"
|
|
role="img"
|
|
viewBox="0 0 24 24"
|
|
width="48"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M3 6a1 1 0 0 0 2 0 1 1 0 0 1 1-1h12a1 1 0 0 1 1 1 1 1 0 1 0 2 0 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3zm2 5a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2zm14 0a1 1 0 1 1 2 0v2a1 1 0 1 1-2 0v-2zm-5 9a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zm-8 1a1 1 0 1 0 0-2 1 1 0 0 1-1-1 1 1 0 1 0-2 0 3 3 0 0 0 3 3zm11-1a1 1 0 0 0 1 1 3 3 0 0 0 3-3 1 1 0 1 0-2 0 1 1 0 0 1-1 1 1 1 0 0 0-1 1z" />
|
|
</svg>
|
|
);
|