Skip to content

Commit cb37705

Browse files
Pollepsjoepio
authored andcommitted
#765 Better table column header
1 parent 2d7882b commit cb37705

File tree

4 files changed

+81
-41
lines changed

4 files changed

+81
-41
lines changed

browser/data-browser/src/components/TableEditor/TableHeader.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,23 @@ import {
1010
DragEndEvent,
1111
DragOverlay,
1212
DragStartEvent,
13+
DraggableAttributes,
1314
useDndMonitor,
1415
} from '@dnd-kit/core';
1516
import { createPortal } from 'react-dom';
1617
import { ColumnReorderHandler } from './types';
1718
import { ReorderDropArea } from './ReorderDropArea';
19+
import { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
1820

19-
export type TableHeadingComponent<T> = ({
20-
column,
21-
}: {
21+
type TableHeadingComponentProps<T> = {
2222
column: T;
23-
}) => JSX.Element;
23+
dragListeners?: SyntheticListenerMap;
24+
dragAttributes?: DraggableAttributes;
25+
};
26+
27+
export type TableHeadingComponent<T> = (
28+
props: TableHeadingComponentProps<T>,
29+
) => JSX.Element;
2430

2531
export interface TableHeaderProps<T> {
2632
columns: T[];
@@ -100,9 +106,9 @@ export function TableHeader<T>({
100106
index={index}
101107
onResize={onResize}
102108
isReordering={activeIndex !== undefined}
103-
>
104-
<HeadingComponent column={column} />
105-
</TableHeading>
109+
column={column}
110+
HeadingComponent={HeadingComponent}
111+
/>
106112
))}
107113
<TableHeadingWrapper aria-colindex={columns.length + 2}>
108114
<ReorderDropArea index={columns.length} />

browser/data-browser/src/components/TableEditor/TableHeading.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ import { useDraggable } from '@dnd-kit/core';
66
import { ReorderDropArea } from './ReorderDropArea';
77
import { transparentize } from 'polished';
88
import { DEFAULT_SIZE_PX } from './hooks/useCellSizes';
9+
import type { TableHeadingComponent } from './TableHeader';
910

10-
interface TableHeadingProps {
11+
interface TableHeadingProps<T> {
1112
index: number;
1213
dragKey: string;
1314
onResize: (index: number, size: string) => void;
1415
isReordering: boolean;
16+
HeadingComponent: TableHeadingComponent<T>;
17+
column: T;
1518
}
1619

1720
/** A single column header, mostly used to render Properties */
18-
export function TableHeading({
19-
children,
21+
export function TableHeading<T>({
2022
dragKey,
2123
index,
2224
isReordering,
25+
column,
2326
onResize,
24-
}: React.PropsWithChildren<TableHeadingProps>): JSX.Element {
27+
HeadingComponent,
28+
}: TableHeadingProps<T>): JSX.Element {
2529
const {
2630
attributes,
2731
listeners,
@@ -57,8 +61,11 @@ export function TableHeading({
5761
role='columnheader'
5862
aria-colindex={index + 2}
5963
>
60-
{children}
61-
<ReorderHandle {...listeners} {...attributes} title='Reorder column' />
64+
<HeadingComponent
65+
column={column}
66+
dragListeners={listeners}
67+
dragAttributes={attributes}
68+
/>
6269
{isReordering && <ReorderDropArea index={index} />}
6370
<ResizeHandle isDragging={isDragging} ref={dragAreaRef} />
6471
</TableHeadingWrapper>
@@ -108,12 +115,3 @@ const ResizeHandle = styled(DragAreaBase)`
108115
z-index: 10;
109116
position: absolute;
110117
`;
111-
112-
const ReorderHandle = styled.button`
113-
border: none;
114-
background: none;
115-
position: absolute;
116-
inset: 0;
117-
cursor: grab;
118-
z-index: -1;
119-
`;

browser/data-browser/src/hooks/useResizable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export const DragAreaBase = styled.div<DragAreaBaseProps>`
152152
153153
backdrop-filter: ${({ isDragging }) => (isDragging ? 'blur(5px)' : 'none')};
154154
155-
:hover {
155+
&:hover {
156156
transition: background-color 0.2s;
157157
background-color: var(--drag-color);
158158
backdrop-filter: blur(5px);

browser/data-browser/src/views/TablePage/TableHeading.tsx

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,62 +7,78 @@ import {
77
} from '@tomic/react';
88

99
import { FaAngleDown, FaAngleUp, FaAtom } from 'react-icons/fa';
10+
import { FaGripVertical } from 'react-icons/fa6';
1011
import { styled } from 'styled-components';
1112
import { dataTypeIconMap } from './dataTypeMaps';
1213
import { TableHeadingMenu } from './TableHeadingMenu';
1314
import { TablePageContext } from './tablePageContext';
1415
import { IconType } from 'react-icons';
1516
import { TableSorting } from './tableSorting';
16-
import { useContext } from 'react';
17-
18-
export interface TableHeadingProps {
19-
column: Property;
20-
}
17+
import { useContext, useState } from 'react';
18+
import { TableHeadingComponent } from '../../components/TableEditor/TableHeader';
2119

2220
function getIcon(
2321
propResource: Resource,
2422
sorting: TableSorting,
23+
hoverOrFocus: boolean,
2524
dataType: Datatype,
2625
): IconType {
2726
if (sorting.prop === propResource.getSubject()) {
2827
return sorting.sortDesc ? FaAngleDown : FaAngleUp;
2928
}
3029

30+
if (hoverOrFocus) {
31+
return FaGripVertical;
32+
}
33+
3134
return dataTypeIconMap.get(dataType) ?? FaAtom;
3235
}
3336

34-
export function TableHeading({ column }: TableHeadingProps): JSX.Element {
37+
export const TableHeading: TableHeadingComponent<Property> = ({
38+
column,
39+
dragListeners,
40+
dragAttributes,
41+
}): JSX.Element => {
42+
const [hoverOrFocus, setHoverOrFocus] = useState(false);
43+
3544
const propResource = useResource(column.subject);
3645
const [title] = useTitle(propResource);
3746
const { setSortBy, sorting } = useContext(TablePageContext);
3847

39-
const Icon = getIcon(propResource, sorting, column.datatype);
48+
const Icon = getIcon(propResource, sorting, hoverOrFocus, column.datatype);
4049
const isSorted = sorting.prop === propResource.getSubject();
4150

51+
const text = title || column.shortname;
52+
4253
return (
4354
<>
44-
<Wrapper>
45-
<Icon />
55+
<Wrapper
56+
onMouseEnter={() => setHoverOrFocus(true)}
57+
onMouseLeave={() => setHoverOrFocus(false)}
58+
onFocus={() => setHoverOrFocus(true)}
59+
onBlur={() => setHoverOrFocus(false)}
60+
>
61+
<DragIconButton {...dragListeners} {...dragAttributes}>
62+
<Icon title='Drag column' />
63+
</DragIconButton>
4664
<NameButton
4765
onClick={() => setSortBy(propResource.getSubject())}
4866
bold={isSorted}
67+
title={text}
4968
>
50-
{title || column.shortname}
69+
<span aria-hidden>{text}</span>
5170
</NameButton>
71+
<TableHeadingMenu resource={propResource} />
5272
</Wrapper>
53-
<TableHeadingMenu resource={propResource} />
5473
</>
5574
);
56-
}
75+
};
5776

5877
const Wrapper = styled.div`
5978
display: flex;
6079
align-items: center;
6180
gap: 0.5rem;
62-
63-
svg {
64-
color: currentColor;
65-
}
81+
width: 100%;
6682
`;
6783

6884
interface NameButtonProps {
@@ -75,8 +91,28 @@ const NameButton = styled.button<NameButtonProps>`
7591
color: currentColor;
7692
cursor: pointer;
7793
font-weight: ${p => (p.bold ? 'bold' : 'normal')};
78-
// TODO: make this dynamic, don't overflow on names, use grid flex?
79-
max-width: 8rem;
8094
overflow: hidden;
8195
text-overflow: ellipsis;
96+
padding: 0;
97+
`;
98+
99+
const DragIconButton = styled.button`
100+
background: none;
101+
color: currentColor;
102+
display: flex;
103+
align-items: center;
104+
border: none;
105+
height: 1rem;
106+
padding: 0;
107+
cursor: grab;
108+
109+
&:active {
110+
cursor: grabbing;
111+
}
112+
svg {
113+
color: currentColor;
114+
max-width: 1rem;
115+
min-width: 1rem;
116+
flex: 1;
117+
}
82118
`;

0 commit comments

Comments
 (0)