Skip to content

Commit

Permalink
#765 Better table column header
Browse files Browse the repository at this point in the history
  • Loading branch information
Polleps authored and joepio committed Jan 9, 2024
1 parent 2d7882b commit cb37705
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 41 deletions.
20 changes: 13 additions & 7 deletions browser/data-browser/src/components/TableEditor/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ import {
DragEndEvent,
DragOverlay,
DragStartEvent,
DraggableAttributes,
useDndMonitor,
} from '@dnd-kit/core';
import { createPortal } from 'react-dom';
import { ColumnReorderHandler } from './types';
import { ReorderDropArea } from './ReorderDropArea';
import { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';

export type TableHeadingComponent<T> = ({
column,
}: {
type TableHeadingComponentProps<T> = {
column: T;
}) => JSX.Element;
dragListeners?: SyntheticListenerMap;
dragAttributes?: DraggableAttributes;
};

export type TableHeadingComponent<T> = (
props: TableHeadingComponentProps<T>,
) => JSX.Element;

export interface TableHeaderProps<T> {
columns: T[];
Expand Down Expand Up @@ -100,9 +106,9 @@ export function TableHeader<T>({
index={index}
onResize={onResize}
isReordering={activeIndex !== undefined}
>
<HeadingComponent column={column} />
</TableHeading>
column={column}
HeadingComponent={HeadingComponent}
/>
))}
<TableHeadingWrapper aria-colindex={columns.length + 2}>
<ReorderDropArea index={columns.length} />
Expand Down
28 changes: 13 additions & 15 deletions browser/data-browser/src/components/TableEditor/TableHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import { useDraggable } from '@dnd-kit/core';
import { ReorderDropArea } from './ReorderDropArea';
import { transparentize } from 'polished';
import { DEFAULT_SIZE_PX } from './hooks/useCellSizes';
import type { TableHeadingComponent } from './TableHeader';

interface TableHeadingProps {
interface TableHeadingProps<T> {
index: number;
dragKey: string;
onResize: (index: number, size: string) => void;
isReordering: boolean;
HeadingComponent: TableHeadingComponent<T>;
column: T;
}

/** A single column header, mostly used to render Properties */
export function TableHeading({
children,
export function TableHeading<T>({
dragKey,
index,
isReordering,
column,
onResize,
}: React.PropsWithChildren<TableHeadingProps>): JSX.Element {
HeadingComponent,
}: TableHeadingProps<T>): JSX.Element {
const {
attributes,
listeners,
Expand Down Expand Up @@ -57,8 +61,11 @@ export function TableHeading({
role='columnheader'
aria-colindex={index + 2}
>
{children}
<ReorderHandle {...listeners} {...attributes} title='Reorder column' />
<HeadingComponent
column={column}
dragListeners={listeners}
dragAttributes={attributes}
/>
{isReordering && <ReorderDropArea index={index} />}
<ResizeHandle isDragging={isDragging} ref={dragAreaRef} />
</TableHeadingWrapper>
Expand Down Expand Up @@ -108,12 +115,3 @@ const ResizeHandle = styled(DragAreaBase)`
z-index: 10;
position: absolute;
`;

const ReorderHandle = styled.button`
border: none;
background: none;
position: absolute;
inset: 0;
cursor: grab;
z-index: -1;
`;
2 changes: 1 addition & 1 deletion browser/data-browser/src/hooks/useResizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export const DragAreaBase = styled.div<DragAreaBaseProps>`
backdrop-filter: ${({ isDragging }) => (isDragging ? 'blur(5px)' : 'none')};
:hover {
&:hover {
transition: background-color 0.2s;
background-color: var(--drag-color);
backdrop-filter: blur(5px);
Expand Down
72 changes: 54 additions & 18 deletions browser/data-browser/src/views/TablePage/TableHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,78 @@ import {
} from '@tomic/react';

import { FaAngleDown, FaAngleUp, FaAtom } from 'react-icons/fa';
import { FaGripVertical } from 'react-icons/fa6';
import { styled } from 'styled-components';
import { dataTypeIconMap } from './dataTypeMaps';
import { TableHeadingMenu } from './TableHeadingMenu';
import { TablePageContext } from './tablePageContext';
import { IconType } from 'react-icons';
import { TableSorting } from './tableSorting';
import { useContext } from 'react';

export interface TableHeadingProps {
column: Property;
}
import { useContext, useState } from 'react';
import { TableHeadingComponent } from '../../components/TableEditor/TableHeader';

function getIcon(
propResource: Resource,
sorting: TableSorting,
hoverOrFocus: boolean,
dataType: Datatype,
): IconType {
if (sorting.prop === propResource.getSubject()) {
return sorting.sortDesc ? FaAngleDown : FaAngleUp;
}

if (hoverOrFocus) {
return FaGripVertical;
}

return dataTypeIconMap.get(dataType) ?? FaAtom;
}

export function TableHeading({ column }: TableHeadingProps): JSX.Element {
export const TableHeading: TableHeadingComponent<Property> = ({
column,
dragListeners,
dragAttributes,
}): JSX.Element => {
const [hoverOrFocus, setHoverOrFocus] = useState(false);

const propResource = useResource(column.subject);
const [title] = useTitle(propResource);
const { setSortBy, sorting } = useContext(TablePageContext);

const Icon = getIcon(propResource, sorting, column.datatype);
const Icon = getIcon(propResource, sorting, hoverOrFocus, column.datatype);
const isSorted = sorting.prop === propResource.getSubject();

const text = title || column.shortname;

return (
<>
<Wrapper>
<Icon />
<Wrapper
onMouseEnter={() => setHoverOrFocus(true)}
onMouseLeave={() => setHoverOrFocus(false)}
onFocus={() => setHoverOrFocus(true)}
onBlur={() => setHoverOrFocus(false)}
>
<DragIconButton {...dragListeners} {...dragAttributes}>
<Icon title='Drag column' />
</DragIconButton>
<NameButton
onClick={() => setSortBy(propResource.getSubject())}
bold={isSorted}
title={text}
>
{title || column.shortname}
<span aria-hidden>{text}</span>
</NameButton>
<TableHeadingMenu resource={propResource} />
</Wrapper>
<TableHeadingMenu resource={propResource} />
</>
);
}
};

const Wrapper = styled.div`
display: flex;
align-items: center;
gap: 0.5rem;
svg {
color: currentColor;
}
width: 100%;
`;

interface NameButtonProps {
Expand All @@ -75,8 +91,28 @@ const NameButton = styled.button<NameButtonProps>`
color: currentColor;
cursor: pointer;
font-weight: ${p => (p.bold ? 'bold' : 'normal')};
// TODO: make this dynamic, don't overflow on names, use grid flex?
max-width: 8rem;
overflow: hidden;
text-overflow: ellipsis;
padding: 0;
`;

const DragIconButton = styled.button`
background: none;
color: currentColor;
display: flex;
align-items: center;
border: none;
height: 1rem;
padding: 0;
cursor: grab;
&:active {
cursor: grabbing;
}
svg {
color: currentColor;
max-width: 1rem;
min-width: 1rem;
flex: 1;
}
`;

0 comments on commit cb37705

Please sign in to comment.