Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Table row/column extension buttons #1172

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
7 changes: 2 additions & 5 deletions packages/ariakit/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem.js";
import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel.js";
import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js";
import { TableHandle } from "./tableHandle/TableHandle.js";
import { ExtendButton } from "./tableHandle/ExtendButton.js";
import { Toolbar } from "./toolbar/Toolbar.js";
import { ToolbarButton } from "./toolbar/ToolbarButton.js";
import { ToolbarSelect } from "./toolbar/ToolbarSelect.js";
Expand Down Expand Up @@ -81,11 +82,7 @@ export const components: Components = {
},
TableHandle: {
Root: TableHandle,
ExtendButton: (props) => (
<button style={{ height: "100%", width: "100%" }} {...props}>
+
</button>
),
ExtendButton: ExtendButton,
},
Generic: {
Form: {
Expand Down
25 changes: 24 additions & 1 deletion packages/ariakit/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,33 @@
}

.bn-ariakit .bn-side-menu,
.bn-ariakit .bn-table-handle {
.bn-ariakit .bn-table-handle,
.bn-ariakit .bn-extend-button {
color: gray;
}

.bn-ariakit .bn-extend-button-editing {
background-color: hsl(204 4% 0% / 0.05);
}

.bn-ariakit .bn-extend-button-editing:where(.dark, .dark *) {
background-color: hsl(204 20% 100% / 0.05);
}

.bn-ariakit .bn-extend-button-row {
height: 100%;
width: 18px;
padding: 0;
margin-left: 4px;
}

.bn-ariakit .bn-extend-button-column {
height: 18px;
width: 100%;
padding: 0;
margin-top: 4px;
}

.bn-ak-button:where(.dark, .dark *) {
color: hsl(204 20% 100%);
}
Expand Down
30 changes: 30 additions & 0 deletions packages/ariakit/src/tableHandle/ExtendButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Button as AriakitButton } from "@ariakit/react";

import { forwardRef } from "react";
import { ComponentProps } from "@blocknote/react";
import { assertEmpty, mergeCSSClasses } from "@blocknote/core";

export const ExtendButton = forwardRef<
HTMLButtonElement,
ComponentProps["TableHandle"]["ExtendButton"]
>((props, ref) => {
const { children, className, onDragStart, onMouseDown, ...rest } = props;

// false, because rest props can be added by mantine when button is used as a trigger
// assertEmpty in this case is only used at typescript level, not runtime level
assertEmpty(rest, false);

return (
<AriakitButton
className={mergeCSSClasses(
"bn-ak-button bn-ak-secondary",
className || ""
)}
ref={ref}
onDragStart={onDragStart}
onMouseDown={onMouseDown}
{...rest}>
{children}
</AriakitButton>
);
});
7 changes: 2 additions & 5 deletions packages/mantine/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem.js";
import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel.js";
import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js";
import { TableHandle } from "./tableHandle/TableHandle.js";
import { ExtendButton } from "./tableHandle/ExtendButton.js";
import { Toolbar } from "./toolbar/Toolbar.js";
import { ToolbarButton } from "./toolbar/ToolbarButton.js";
import { ToolbarSelect } from "./toolbar/ToolbarSelect.js";
Expand Down Expand Up @@ -90,11 +91,7 @@ export const components: Components = {
},
TableHandle: {
Root: TableHandle,
ExtendButton: (props) => (
<button style={{ height: "100%", width: "100%" }} {...props}>
+
</button>
),
ExtendButton: ExtendButton,
},
Generic: {
Form: {
Expand Down
19 changes: 17 additions & 2 deletions packages/mantine/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,8 @@
}

/* Table Handle styling */
.bn-mantine .bn-table-handle {
.bn-mantine .bn-table-handle,
.bn-mantine .bn-extend-button {
align-items: center;
background-color: var(--bn-colors-menu-background);
border: var(--bn-border);
Expand All @@ -508,10 +509,24 @@
}

.bn-mantine .bn-table-handle:hover,
.bn-mantine .bn-table-handle-dragging {
.bn-mantine .bn-table-handle-dragging,
.bn-mantine .bn-extend-button:hover,
.bn-mantine .bn-extend-button-editing {
background-color: var(--bn-colors-hovered-background);
}

.bn-mantine .bn-extend-button-row {
height: 100%;
width: 18px;
margin-left: 4px;
}

.bn-mantine .bn-extend-button-column {
height: 18px;
width: 100%;
margin-top: 4px;
}

/* Drag Handle & Table Handle Menu styling */
.bn-mantine .bn-drag-handle-menu {
overflow: visible;
Expand Down
27 changes: 27 additions & 0 deletions packages/mantine/src/tableHandle/ExtendButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Button as MantineButton } from "@mantine/core";

import { forwardRef } from "react";
import { ComponentProps } from "@blocknote/react";
import { assertEmpty } from "@blocknote/core";

export const ExtendButton = forwardRef<
HTMLButtonElement,
ComponentProps["TableHandle"]["ExtendButton"]
>((props, ref) => {
const { children, className, onDragStart, onMouseDown, ...rest } = props;

// false, because rest props can be added by mantine when button is used as a trigger
// assertEmpty in this case is only used at typescript level, not runtime level
assertEmpty(rest, false);

return (
<MantineButton
className={className}
ref={ref}
onDragStart={onDragStart}
onMouseDown={onMouseDown}
{...rest}>
{children}
</MantineButton>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import {
DefaultInlineContentSchema,
DefaultStyleSchema,
InlineContentSchema,
mergeCSSClasses,
PartialTableContent,
StyleSchema,
} from "@blocknote/core";
import { MouseEvent as ReactMouseEvent, useEffect, useState } from "react";
import {
MouseEvent as ReactMouseEvent,
ReactNode,
useEffect,
useState,
} from "react";
import { RiAddFill } from "react-icons/ri";

import { useComponentsContext } from "../../../editor/ComponentsContext.js";
import { TableHandleProps } from "../TableHandleProps.js";
import { ExtendButtonProps } from "./ExtendButtonProps.js";

const getContentWithAddedRows = <
I extends InlineContentSchema,
Expand Down Expand Up @@ -61,10 +68,7 @@ export const ExtendButton = <
I extends InlineContentSchema = DefaultInlineContentSchema,
S extends StyleSchema = DefaultStyleSchema
>(
props: Pick<
TableHandleProps<I, S>,
"block" | "editor" | "orientation" | "freezeHandles" | "unfreezeHandles"
>
props: ExtendButtonProps<I, S> & { children?: ReactNode }
) => {
const Components = useComponentsContext()!;

Expand Down Expand Up @@ -172,10 +176,18 @@ export const ExtendButton = <

return (
<Components.TableHandle.ExtendButton
className={mergeCSSClasses(
"bn-extend-button",
props.orientation === "row"
? "bn-extend-button-row"
: "bn-extend-button-column",
editingState !== null ? "bn-extend-button-editing" : ""
)}
matthewlipski marked this conversation as resolved.
Show resolved Hide resolved
onDragStart={(event) => {
event.preventDefault();
}}
onMouseDown={mouseDownHandler}
/>
onMouseDown={mouseDownHandler}>
{props.children || <RiAddFill size={18} data-test={"extendButton"} />}
</Components.TableHandle.ExtendButton>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
DefaultInlineContentSchema,
DefaultStyleSchema,
InlineContentSchema,
StyleSchema,
} from "@blocknote/core";

import { TableHandleProps } from "../TableHandleProps.js";

export type ExtendButtonProps<
I extends InlineContentSchema = DefaultInlineContentSchema,
S extends StyleSchema = DefaultStyleSchema
> = Pick<
TableHandleProps<I, S>,
"block" | "editor" | "orientation" | "freezeHandles" | "unfreezeHandles"
>;
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const AddRowButton = <
type: "table",
content: {
type: "tableContent",
columnWidths: props.block.content.columnWidths,
rows,
},
});
Expand Down Expand Up @@ -75,6 +76,11 @@ export const AddColumnButton = <
onClick={() => {
const content: PartialTableContent<I, S> = {
type: "tableContent",
columnWidths: props.block.content.columnWidths.toSpliced(
props.index + (props.side === "right" ? 1 : 0),
0,
100
),
rows: props.block.content.rows.map((row) => {
const cells = [...row.cells];
cells.splice(props.index + (props.side === "right" ? 1 : 0), 0, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export const DeleteRowButton = <
onClick={() => {
const content: PartialTableContent<I, S> = {
type: "tableContent",
columnWidths: props.block.content.columnWidths.filter(
(_, index) => index !== props.index
),
rows: props.block.content.rows.filter(
(_, index) => index !== props.index
),
Expand Down Expand Up @@ -70,6 +73,9 @@ export const DeleteColumnButton = <
onClick={() => {
const content: PartialTableContent<I, S> = {
type: "tableContent",
columnWidths: props.block.content.columnWidths.filter(
(_, index) => index !== props.index
),
rows: props.block.content.rows.map((row) => ({
cells: row.cells.filter((_, index) => index !== props.index),
})),
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/editor/ComponentsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export type ComponentProps = {
className?: string;
onDragStart: (e: React.DragEvent) => void;
onMouseDown: (e: React.MouseEvent) => void;
children: ReactNode;
};
};
// TODO: We should try to make everything as generic as we can
Expand Down
7 changes: 2 additions & 5 deletions packages/shadcn/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem.js";
import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel.js";
import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader.js";
import { TableHandle } from "./tableHandle/TableHandle.js";
import { ExtendButton } from "./tableHandle/ExtendButton.js";
import { Toolbar, ToolbarButton, ToolbarSelect } from "./toolbar/Toolbar.js";

import { PanelButton } from "./panel/PanelButton.js";
Expand Down Expand Up @@ -84,11 +85,7 @@ export const components: Components = {
},
TableHandle: {
Root: TableHandle,
ExtendButton: (props) => (
<button style={{ height: "100%", width: "100%" }} {...props}>
+
</button>
),
ExtendButton: ExtendButton,
},
Generic: {
Form: {
Expand Down
38 changes: 38 additions & 0 deletions packages/shadcn/src/tableHandle/ExtendButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { assertEmpty } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";

import { cn } from "../lib/utils.js";
import { useShadCNComponentsContext } from "../ShadCNComponentsContext.js";

export const ExtendButton = forwardRef<
HTMLButtonElement,
ComponentProps["TableHandle"]["ExtendButton"]
>((props, ref) => {
const { className, children, onDragStart, onMouseDown, ...rest } = props;

// false, because rest props can be added by shadcn when button is used as a trigger
// assertEmpty in this case is only used at typescript level, not runtime level
assertEmpty(rest, false);

const ShadCNComponents = useShadCNComponentsContext()!;

return (
<ShadCNComponents.Button.Button
variant={"ghost"}
className={cn(
className,
"bn-p-0 bn-h-full bn-w-full bn-text-gray-400",
className?.includes("bn-extend-button-row") ? "bn-ml-1" : "bn-mt-1",
className?.includes("bn-extend-button-editing")
? "bn-bg-accent bn-text-accent-foreground"
: ""
)}
ref={ref}
onDragStart={onDragStart}
onMouseDown={onMouseDown}
{...rest}>
{children}
</ShadCNComponents.Button.Button>
);
});
Loading