Skip to content

Commit

Permalink
feat: add clickable and hoverable rows to table
Browse files Browse the repository at this point in the history
  • Loading branch information
etienneburdet committed Nov 12, 2024
1 parent 3ee8452 commit fec030e
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 22 deletions.
30 changes: 29 additions & 1 deletion packages/visualizations-react/stories/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import type { TableData, Async } from '@opendatasoft/visualizations';

Expand Down Expand Up @@ -39,6 +39,28 @@ const ScrollTemplate: ComponentStory<typeof Table> = args => (
</div>
);

const RowHoverTemplate: ComponentStory<typeof Table> = args => {
const { options: argOptions, data: argData } = args;
const [hoveredRecord, setHovered] = useState<Record<string, unknown> | undefined | null>(null);
const [lastClicked, setLastClicked] = useState<Record<string, unknown> | undefined | null>(null);

const onMouseEnter = (record?: Record<string, unknown>) => {setHovered(record);};
const onMouseLeave = () => {setHovered(null);};
const onClick = (record?: Record<string, unknown>) => {setLastClicked(record);};

return (
<>
<h3>Hovered</h3>
<pre>{JSON.stringify(hoveredRecord)}</pre>
<h3>Clicked</h3>
<pre>{JSON.stringify(lastClicked)}</pre>
<div style={{ maxWidth: '800px' }}>
<Table data={argData} options={{...argOptions, rows: { onClick, onMouseEnter, onMouseLeave}}}/>
</div>;
</>
);
};

const optionsWithPagination = {
...options,
pagination: {
Expand Down Expand Up @@ -107,3 +129,9 @@ RtlDirection.args = {
data,
options: optionsWithPagination,
};

export const Rows = RowHoverTemplate.bind({});
Rows.args = {
data,
options,
};
19 changes: 5 additions & 14 deletions packages/visualizations/src/components/Table/Body.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script lang="ts">
import type { Column, TableData } from './types';
import Cell, { LoadingCell } from './Cell';
import type { Column, Rows, TableData } from './types';
import { LoadingCell } from './Cell';
import EmptyRow from './EmptyRow.svelte';
import Row from './Row.svelte';
export let loadingRowsNumber: number | null;
export let columns: Column[];
export let rows: Rows | undefined;
export let records: TableData | undefined;
export let emptyStateLabel: string | undefined;
</script>
Expand All @@ -23,21 +25,10 @@
{/each}
{:else if records}
{#each records as record}
<tr>
{#each columns as column}
<Cell rawValue={record[column.key]} {column} />
{/each}
</tr>
<Row {columns} {rows} {record} />
{/each}
{/if}
</tbody>

<style>
:global(.ods-dataviz--default) tr {
border-bottom: 1px solid var(--border-color);
}
:global(.ods-dataviz--default) tr:last-child {
border-bottom: none;
}
</style>
54 changes: 51 additions & 3 deletions packages/visualizations/src/components/Table/Cell/Cell.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
<script lang="ts">
import type { Column } from '../types';
import Format, { isValidRawValue } from './Format';
import ZoomIcon from './ZoomIcon.svelte';
export let rawValue: unknown;
export let column: Column;
export let onClick: (() => void) | null;
export let isRowHovered = false;
$: ({ dataFormat, options = {} } = column);
</script>

<!-- To display a format value, rawValue must be different from undefined or null -->
<td class={`table-data--${dataFormat}`}>
{#if isValidRawValue(rawValue)}
<svelte:component this={Format[dataFormat]} {rawValue} {...options} />
{/if}
<div class="cell-wrapper">
{#if onClick}
<button on:click={onClick} class:visible={isRowHovered}>
<ZoomIcon />
</button>
{/if}
<div class="value-container">
{#if isValidRawValue(rawValue)}
<svelte:component this={Format[dataFormat]} {rawValue} {...options} />
{/if}
</div>
</div>
</td>

<style>
.cell-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 6px;
}
.value-container {
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
}
:global(.ods-dataviz--default td) {
padding: var(--spacing-75);
}
Expand Down Expand Up @@ -43,4 +66,29 @@
:global(.ods-dataviz--default td.table-data--number) {
text-align: right;
}
:global(.ods-dataviz--default) button {
background-color: transparent;
color: #142e7b;
border-radius: 50%;
height: 28px;
width: 28px;
margin-right: 3px;
padding: 6px;
border: none;
box-shadow: none;
display: flex;
justify-content: center;
align-items: center;
visibility: hidden;
}
:global(.ods-dataviz--default) button:hover {
background-color: #e2e6ee;
cursor: pointer;
}
:global(.ods-dataviz--default) button.visible {
visibility: visible;
}
</style>
15 changes: 15 additions & 0 deletions packages/visualizations/src/components/Table/Cell/ZoomIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
</script>

<!-- eslint-disable max-len -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748ZM10 10V7H12V10H15V12H12V15H10V12H7V10H10Z"
/>
</svg>

<style>
path {
color: currentColor;
}
</style>
52 changes: 52 additions & 0 deletions packages/visualizations/src/components/Table/Row.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import Cell from './Cell';
import type { Column, Rows } from './types';
export let columns: Column[];
export let rows: Rows | undefined;
export let record: Record<string, unknown>;
let isRowHovered = false;
$: onMouseEnter = () => {
if (rows?.onMouseEnter) {
rows.onMouseEnter(record);
}
isRowHovered = true;
};
$: onMouseLeave = () => {
if (rows?.onMouseLeave) {
rows.onMouseLeave(record);
}
isRowHovered = false;
};
$: onClick = () => {
if (rows?.onClick) {
rows.onClick(record);
}
};
</script>

<tr on:mouseenter={onMouseEnter} on:mouseleave={onMouseLeave}>
{#each columns as column, index}
<Cell
rawValue={record[column.key]}
{column}
onClick={index === 0 && rows?.onClick ? onClick : null}
{isRowHovered}
/>
{/each}
</tr>

<!-- markup (zero or more items) goes here -->

<style>
:global(.ods-dataviz--default) tr {
border-bottom: 1px solid var(--border-color);
}
:global(.ods-dataviz--default) tr:last-child {
border-bottom: none;
}
</style>
6 changes: 3 additions & 3 deletions packages/visualizations/src/components/Table/Table.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import type { DataFrame } from 'types';
import { generateId } from 'components/utils';
import type { Column } from './types';
import type { Column, Rows } from './types';
import Headers from './Headers';
import Body from './Body.svelte';
Expand All @@ -10,14 +10,14 @@
export let records: DataFrame | undefined;
export let description: string | undefined;
export let emptyStateLabel: string | undefined;
export let rows: Rows | undefined;
const tableId = `table-${generateId()}`;
</script>

<div class="scrollbox">
<table aria-describedby={description ? tableId : undefined}>
<Headers {columns} />
<Body {loadingRowsNumber} {records} {columns} {emptyStateLabel} />
<Body {loadingRowsNumber} {records} {columns} {rows} {emptyStateLabel} />
</table>
</div>
{#if description}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
$: ({ value: records, loading: isLoading } = data);
$: ({
columns,
rows,
title,
subtitle,
description,
Expand All @@ -33,7 +34,7 @@

<Card {title} {subtitle} {source} defaultStyle={!unstyled}>
<div class="table-container">
<Table {loadingRowsNumber} {records} {columns} {description} {emptyStateLabel} />
<Table {loadingRowsNumber} {records} {columns} {description} {emptyStateLabel} {rows} />
{#if pagination}
<Pagination {...pagination} />
{/if}
Expand Down
7 changes: 7 additions & 0 deletions packages/visualizations/src/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export type GeoColumn = BaseColumn & {
}>;
};

export type Rows = {
onClick?: (record?: Record<string, unknown>, index?: number) => void;
onMouseEnter?: (record?: Record<string, unknown>, index?: number) => void;
onMouseLeave?: (record?: Record<string, unknown>, index?: number) => void;
};

export type Column =
| ShortTextColumn
| LongTextColumn
Expand All @@ -106,6 +112,7 @@ export type Column =

export type TableOptions = {
columns: Column[];
rows?: Rows;
title?: string;
subtitle?: string;
description?: string;
Expand Down

0 comments on commit fec030e

Please sign in to comment.