Compare commits

..
Author SHA1 Message Date
Ken b7c70dbcc9 feat: attachment WIP 2026-10-22 16:08:36 +02:00
3 changed files with 211 additions and 69 deletions

No files matched your search

@@ -137,6 +137,26 @@
border-radius: var(--ax-radius-12);
z-index: 3;
}
[data-attachment-before="true"] & {
border-top-left-radius: 0;
border-top-right-radius: 0;
&::after {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
[data-attachment-after="true"] & {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
&::after {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
}
.aksel-data-table {
@@ -710,3 +730,23 @@ html[data-dragging-cursor="true"] *::after {
}
/* ------------------------ DataGrid.Preferences End ------------------------ */
/* WIP ATTACHMENT TEST */
.aksel-data-table__attachment {
z-index: 10;
padding: var(--ax-space-12) var(--ax-space-16);
border: 1px solid var(--ax-border-neutral-subtle);
background-color: var(--ax-bg-raised);
&[data-attachment="before"] {
border-bottom: none;
border-top-left-radius: var(--ax-radius-12);
border-top-right-radius: var(--ax-radius-12);
}
&[data-attachment="after"] {
border-top: none;
border-bottom-left-radius: var(--ax-radius-12);
border-bottom-right-radius: var(--ax-radius-12);
}
}
@@ -0,0 +1,68 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import React, { useCallback } from "react";
import { Button } from "../../button";
import { DataGrid } from "../../data-grid";
import { HStack, VStack } from "../../primitives/stack";
import { TEST_DATA, columnDef_TEST_DATA } from "./Data.test-data";
const meta: Meta<typeof DataGrid> = {
title: "ds-react/Data/Attachments",
component: DataGrid,
parameters: {
chromatic: { disable: true },
layout: "fullscreen",
a11y: { disable: true },
docs: { disable: true },
},
};
export default meta;
const getColDefs = (overflow: string) =>
overflow === "none" || overflow === "y"
? columnDef_TEST_DATA.slice(0, 5)
: columnDef_TEST_DATA;
const getData = (overflow: string) =>
overflow === "none" || overflow === "x" ? TEST_DATA.slice(0, 5) : TEST_DATA;
type Story = StoryObj<{ overflow: "none" | "x" | "y" | "both" }>;
export const Attachments: Story = {
render: ({ overflow }) => {
const Before = useCallback(
() => (
<HStack gap="space-8">
<Button size="xsmall" variant="secondary" data-color="neutral">
Button
</Button>
<Button size="xsmall" variant="secondary" data-color="neutral">
Button
</Button>
</HStack>
),
[],
);
return (
<VStack padding="space-16" gap="space-16" height="100vh">
<div style={{ overflow: "hidden", display: "flex" }}>
<DataGrid columns={getColDefs(overflow)} data={getData(overflow)}>
<DataGrid.Table
attachment={{
before: <Before />,
after: <Before />,
}}
/>
</DataGrid>
</div>
</VStack>
);
},
argTypes: {
overflow: {
control: { type: "radio" },
options: ["none", "x", "y", "both"],
},
},
};
@@ -138,6 +138,14 @@ interface DataGridTableProps<T> extends React.HTMLAttributes<HTMLTableElement> {
* @default "row"
*/
selectionTrigger?: "row" | "control";
/**
* Content to render before or after the table.
* `before` will be rendered above the table, and `after` will be rendered below the table.
*/
attachment?: {
before?: React.ReactNode;
after?: React.ReactNode;
};
}
const DataGridTableInternal = forwardRef<
@@ -161,6 +169,7 @@ const DataGridTableInternal = forwardRef<
subRows,
sorting,
selectionTrigger = "row",
attachment,
...rest
}: DataGridTableProps<unknown>,
forwardedRef,
@@ -205,78 +214,103 @@ const DataGridTableInternal = forwardRef<
const truncateContent = tableSettings?.truncateContent ?? layout !== "auto";
return (
<DataTableContextProvider
layout={layout}
withKeyboardNav={true}
selectionState={tableSelectionState}
stickyStart={stickyStart}
stickyHeader={stickyHeader}
tableId={tableId}
loading={loadingContent}
onRowAction={onRowAction}
columns={columns}
totalColSpan={totalColSpan}
tableItems={tableItems}
sortingState={sortingState}
<div
style={{ maxHeight: "100%", display: "grid" }}
data-attachment-before={attachment?.before ? true : undefined}
data-attachment-after={attachment?.after ? true : undefined}
>
<TableElementWrapper
enabled={true}
hasStickyColumns={
!!(
tableSettings?.stickyColumns?.start ||
tableSettings?.stickyColumns?.end
)
}
>
<table
{...rest}
ref={forwardedRef}
className={cl("aksel-data-table", className)}
data-zebra-stripes={tableSettings?.zebraStripes}
data-column-dividers={tableSettings?.columnDividers}
data-truncate-content={truncateContent}
data-density={tableSettings?.rowDensity}
data-text-size={tableSettings?.textSize}
data-layout={layout}
data-loading={isLoading || undefined}
aria-busy={isLoading || undefined}
{attachment?.before && (
<div
className="aksel-data-table__attachment"
data-attachment="before"
>
<DataTableDetailsPanelProvider detailsPanel={detailsPanel}>
<DataTableThead data-sticky={stickyHeader || undefined}>
<DataTableTr>
{columns.map(
({ isSticky, isStickyLast, stickyLeftOffset, colDef }) => {
return (
<DataTableColumnHeader
id={colDef.id}
width={colDef.width}
align={colDef.align ?? "left"}
key={colDef.id}
isSticky={isSticky}
sortable={colDef.isSortable}
label={colDef.header}
style={
stickyLeftOffset
? { left: stickyLeftOffset }
: undefined
}
data-sticky-last={isStickyLast || undefined}
>
{colDef.headerCell ?? colDef.header}
</DataTableColumnHeader>
);
},
)}
</DataTableTr>
</DataTableThead>
{attachment.before}
</div>
)}
<DataTableTbody>
<DataTableTBodyContent emptyContent={emptyContent} />
</DataTableTbody>
</DataTableDetailsPanelProvider>
</table>
</TableElementWrapper>
</DataTableContextProvider>
<DataTableContextProvider
layout={layout}
withKeyboardNav={true}
selectionState={tableSelectionState}
stickyStart={stickyStart}
stickyHeader={stickyHeader}
tableId={tableId}
loading={loadingContent}
onRowAction={onRowAction}
columns={columns}
totalColSpan={totalColSpan}
tableItems={tableItems}
sortingState={sortingState}
>
<TableElementWrapper
enabled={true}
hasStickyColumns={
!!(
tableSettings?.stickyColumns?.start ||
tableSettings?.stickyColumns?.end
)
}
>
<table
{...rest}
ref={forwardedRef}
className={cl("aksel-data-table", className)}
data-zebra-stripes={tableSettings?.zebraStripes}
data-column-dividers={tableSettings?.columnDividers}
data-truncate-content={truncateContent}
data-density={tableSettings?.rowDensity}
data-text-size={tableSettings?.textSize}
data-layout={layout}
data-loading={isLoading || undefined}
aria-busy={isLoading || undefined}
>
<DataTableDetailsPanelProvider detailsPanel={detailsPanel}>
<DataTableThead data-sticky={stickyHeader || undefined}>
<DataTableTr>
{columns.map(
({
isSticky,
isStickyLast,
stickyLeftOffset,
colDef,
}) => {
return (
<DataTableColumnHeader
id={colDef.id}
width={colDef.width}
align={colDef.align ?? "left"}
key={colDef.id}
isSticky={isSticky}
sortable={colDef.isSortable}
label={colDef.header}
style={
stickyLeftOffset
? { left: stickyLeftOffset }
: undefined
}
data-sticky-last={isStickyLast || undefined}
>
{colDef.headerCell ?? colDef.header}
</DataTableColumnHeader>
);
},
)}
</DataTableTr>
</DataTableThead>
<DataTableTbody>
<DataTableTBodyContent emptyContent={emptyContent} />
</DataTableTbody>
</DataTableDetailsPanelProvider>
</table>
</TableElementWrapper>
</DataTableContextProvider>
{attachment?.after && (
<div className="aksel-data-table__attachment" data-attachment="after">
{attachment.after}
</div>
)}
</div>
);
},
);