setting up table
This commit is contained in:
parent
3c4582bb6a
commit
7cfd93f901
17 changed files with 143 additions and 225 deletions
19
src/components/Table/Table.mdx
Normal file
19
src/components/Table/Table.mdx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Meta, Canvas } from '@storybook/blocks';
|
||||
|
||||
import * as TableTextStories from './Table.stories.svelte';
|
||||
|
||||
<Meta of={TableTextStories} />
|
||||
|
||||
# Table
|
||||
|
||||
The `Table` component TKTK
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Table } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<Table />
|
||||
```
|
||||
|
||||
<Canvas of={TableTextStories.Demo} />
|
||||
|
|
@ -1,128 +1,91 @@
|
|||
<script module lang="ts">
|
||||
// @ts-ignore raw
|
||||
import componentDocs from './stories/docs/component.md?raw';
|
||||
// @ts-ignore raw
|
||||
import metadataDocs from './stories/docs/metadata.md?raw';
|
||||
// @ts-ignore raw
|
||||
import truncateDocs from './stories/docs/truncate.md?raw';
|
||||
// @ts-ignore raw
|
||||
import paginateDocs from './stories/docs/paginate.md?raw';
|
||||
// @ts-ignore raw
|
||||
import searchDocs from './stories/docs/search.md?raw';
|
||||
// @ts-ignore raw
|
||||
import filterDocs from './stories/docs/filter.md?raw';
|
||||
// @ts-ignore raw
|
||||
import bothDocs from './stories/docs/both.md?raw';
|
||||
// @ts-ignore raw
|
||||
import sortDocs from './stories/docs/sort.md?raw';
|
||||
// @ts-ignore raw
|
||||
import formatDocs from './stories/docs/format.md?raw';
|
||||
// @ts-ignore raw
|
||||
import styleDocs from './stories/docs/style.md?raw';
|
||||
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Table from './Table.svelte';
|
||||
|
||||
import { withComponentDocs, withStoryDocs } from '$docs/utils/withParams.js';
|
||||
|
||||
export const meta = {
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/Text elements/Table',
|
||||
component: Table,
|
||||
...withComponentDocs(componentDocs),
|
||||
argTypes: {
|
||||
width: {
|
||||
control: 'select',
|
||||
options: ['normal', 'wide', 'wider', 'widest', 'fluid'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
||||
|
||||
import pressFreedom from './stories/pressFreedom.json';
|
||||
import homeRuns from './stories/homeRuns.json';
|
||||
import richestWomen from './stories/richestWomen.json';
|
||||
import pressFreedom from './demo/pressFreedom.json';
|
||||
import homeRuns from './demo/homeRuns.json';
|
||||
import richestWomen from './demo/richestWomen.json';
|
||||
|
||||
const currencyFormat = (v: number) => '$' + v.toFixed(1);
|
||||
</script>
|
||||
|
||||
<Template >
|
||||
{#snippet children({ args })}
|
||||
<Table {...args} />
|
||||
{/snippet}
|
||||
</Template>
|
||||
|
||||
<Story
|
||||
name="Default"
|
||||
args="{{
|
||||
name="Demo"
|
||||
args={{
|
||||
width: 'normal',
|
||||
data: homeRuns,
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
<!-- <Story
|
||||
name="Metadata"
|
||||
{...withStoryDocs(metadataDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
width: 'normal',
|
||||
data: homeRuns,
|
||||
title: 'Career home run leaders',
|
||||
dek: 'In baseball, a home run (also known as a "dinger" or "tater") occurs when a batter hits the ball over the outfield fence. When a home run is hit, the batter and any runners on base are able to score.',
|
||||
notes: 'Note: As of Opening Day 2023',
|
||||
source: 'Source: Baseball Reference',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Truncate"
|
||||
{...withStoryDocs(truncateDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: homeRuns,
|
||||
truncated: true,
|
||||
source: 'Source: Baseball Reference',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Paginate"
|
||||
{...withStoryDocs(paginateDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
title: 'Press Freedom Index',
|
||||
paginated: true,
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Search"
|
||||
{...withStoryDocs(searchDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
searchable: true,
|
||||
paginated: true,
|
||||
title: 'Press Freedom Index',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Filter"
|
||||
{...withStoryDocs(filterDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
paginated: true,
|
||||
filterField: 'Region',
|
||||
title: 'Press Freedom Index',
|
||||
notes: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Search and filter"
|
||||
{...withStoryDocs(bothDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
searchable: true,
|
||||
filterField: 'Region',
|
||||
|
|
@ -130,13 +93,12 @@
|
|||
title: 'Press Freedom Index',
|
||||
dek: 'Reporters Without Borders ranks countries based on their level of press freedom using criteria such as the degree of media pluralism and violence against journalists.',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Sort"
|
||||
{...withStoryDocs(sortDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: pressFreedom,
|
||||
sortable: true,
|
||||
sortField: 'Score',
|
||||
|
|
@ -145,13 +107,12 @@
|
|||
title: 'Press Freedom Index',
|
||||
notes: 'Note: data as of 2018',
|
||||
source: 'Source: Reporters Without Borders',
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Format"
|
||||
{...withStoryDocs(formatDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
data: richestWomen,
|
||||
title: 'The Richest Women in the World',
|
||||
source: 'Source: Forbes',
|
||||
|
|
@ -159,16 +120,15 @@
|
|||
sortField: 'Net worth (in billions)',
|
||||
sortDirection: 'descending',
|
||||
fieldFormatters: { 'Net worth (in billions)': currencyFormat },
|
||||
}}"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Style"
|
||||
{...withStoryDocs(styleDocs)}
|
||||
args="{{
|
||||
args={{
|
||||
id: 'custom-table',
|
||||
data: richestWomen,
|
||||
title: 'The Richest Women in the World',
|
||||
source: 'Source: Forbes',
|
||||
}}"
|
||||
/>
|
||||
}}
|
||||
/> -->
|
||||
|
|
|
|||
|
|
@ -1,138 +1,79 @@
|
|||
<!-- @migration-task Error while migrating Svelte code: Cannot set properties of undefined (setting 'next') -->
|
||||
<!-- @component `Table` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-table--docs) -->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
/**
|
||||
* A source for the data.
|
||||
* @type []
|
||||
* @required
|
||||
*/
|
||||
export let data: [];
|
||||
interface Props {
|
||||
/** Data for the table. */
|
||||
data: [];
|
||||
/** A title that runs above the table. */
|
||||
title?: string;
|
||||
/** A block of text that runs above the table. */
|
||||
dek?: string;
|
||||
/** A footnote that runs below the table. */
|
||||
notes?: string;
|
||||
/** A source line that runs below the table. */
|
||||
source?: string;
|
||||
/** List of the fields to include in the table. By default everything goes. */
|
||||
includedFields?: string[];
|
||||
/** Whether or not the table is cutoff after a set number of rows. */
|
||||
truncated?: boolean;
|
||||
/** If the table is truncated, how many rows to allow before the cutoff. */
|
||||
truncateLength?: number;
|
||||
/** Whether or not the table is paginated after a set number of rows. */
|
||||
paginated?: boolean;
|
||||
/** The default page size. */
|
||||
pageSize?: number;
|
||||
/** Whether or not searches are allowed. */
|
||||
searchable?: boolean;
|
||||
/** The placeholder text that appears in the search box. */
|
||||
searchPlaceholder?: string;
|
||||
/** A field to offer uses as an interactive filter. */
|
||||
filterField?: string;
|
||||
/** The label to place above the filter box. */
|
||||
filterLabel?: string;
|
||||
/** Whether or not sorts are allowed. */
|
||||
sortable?: boolean;
|
||||
/** The column to sort by. By default it's the first header. */
|
||||
sortField?: string;
|
||||
/** The columns that are allowed to sort. It's all of them by default. */
|
||||
sortableFields?: string[];
|
||||
/** The direction of the sort. By default it's ascending. */
|
||||
sortDirection?: 'ascending' | 'descending';
|
||||
/** Custom field formatting functions. Should be keyed to the name of the field. */
|
||||
fieldFormatters?: object;
|
||||
/** Width of the component within the text well. */
|
||||
width?: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
|
||||
/** Add an ID to target with SCSS. */
|
||||
id?: string;
|
||||
/** Add a class to target with SCSS. */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A title that runs above the table.
|
||||
* @type {string}
|
||||
*/
|
||||
export let title: string | null = null;
|
||||
|
||||
/**
|
||||
* A block of text that runs above the table.
|
||||
* @type {string}
|
||||
*/
|
||||
export let dek: string | null = null;
|
||||
|
||||
/**
|
||||
* A footnote that runs below the table.
|
||||
* @type {string}
|
||||
*/
|
||||
export let notes: string | null = null;
|
||||
|
||||
/**
|
||||
* A source line that runs below the table.
|
||||
* @type {string}
|
||||
*/
|
||||
export let source: string | null = null;
|
||||
|
||||
/**
|
||||
* list of the fields to include in the table. By default everything goes.
|
||||
* @type []
|
||||
*/
|
||||
export let includedFields: string[] = Object.keys(data[0]).filter(
|
||||
(f) => f !== 'searchStr'
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether or not the table is cutoff after a set number of rows.
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let truncated: boolean = false;
|
||||
|
||||
/**
|
||||
* If the table is truncated, how many rows to allow before the cutoff.
|
||||
* @type {number}
|
||||
*/
|
||||
export let truncateLength: number = 5;
|
||||
|
||||
/**
|
||||
* Whether or not the table is paginated after a set number of rows.
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let paginated: boolean = false;
|
||||
|
||||
/**
|
||||
* The default page size.
|
||||
* @type {number}
|
||||
*/
|
||||
export let pageSize: number = 25;
|
||||
|
||||
/**
|
||||
* Whether or not searches are allowed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let searchable: boolean = false;
|
||||
|
||||
/**
|
||||
* The placeholder text that appears in the search box.
|
||||
* @type {string}
|
||||
*/
|
||||
export let searchPlaceholder: string = 'Search in table';
|
||||
|
||||
/**
|
||||
* A field to offer uses as an interactive filter.
|
||||
* @type {string}
|
||||
*/
|
||||
export let filterField: string;
|
||||
|
||||
/**
|
||||
* The label to place above the filter box
|
||||
* @type {string}
|
||||
*/
|
||||
export let filterLabel: string;
|
||||
|
||||
/**
|
||||
* Whether or not sorts are allowed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
export let sortable: boolean = false;
|
||||
|
||||
/**
|
||||
* The column to sort by. By default it's the first header.
|
||||
* @type {string}
|
||||
*/
|
||||
export let sortField: string = Object.keys(data[0])[0];
|
||||
|
||||
/**
|
||||
* The columns that are allowed to sort. It's all of them by default.
|
||||
* @type {string}
|
||||
*/
|
||||
export let sortableFields: string[] = Object.keys(data[0]).filter(
|
||||
(f) => f !== 'searchStr'
|
||||
);
|
||||
|
||||
/**
|
||||
* The direction of the sort. By default it's ascending.
|
||||
* @type {SortDirection}
|
||||
*/
|
||||
type SortDirection = 'ascending' | 'descending';
|
||||
export let sortDirection: SortDirection = 'ascending';
|
||||
|
||||
/**
|
||||
* Custom field formatting functions. Should be keyed to the name of the field.
|
||||
* @type {object}
|
||||
*/
|
||||
export let fieldFormatters: object = {};
|
||||
|
||||
/** Width of the component within the text well. */
|
||||
type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';
|
||||
export let width: ContainerWidth = 'normal';
|
||||
|
||||
/** Add an ID to target with SCSS. */
|
||||
export let id: string = '';
|
||||
|
||||
/** Add a class to target with SCSS. */
|
||||
let cls: string = '';
|
||||
export { cls as class };
|
||||
let {
|
||||
data,
|
||||
title,
|
||||
dek,
|
||||
notes,
|
||||
source,
|
||||
includedFields = Object.keys(data?[0]).filter((f) => f !== 'searchStr'),
|
||||
truncated = false,
|
||||
truncateLength = 5,
|
||||
paginated= false,
|
||||
pageSize = 25,
|
||||
searchable = false,
|
||||
searchPlaceholder = 'Search in table',
|
||||
filterField,
|
||||
filterLabel,
|
||||
sortable = false,
|
||||
sortField = Object.keys(data[0])[0],
|
||||
sortableFields = Object.keys(data[0]).filter((f) => f !== 'searchStr'),
|
||||
sortDirection = 'ascending',
|
||||
fieldFormatters = {},
|
||||
width = 'normal',
|
||||
id = '',
|
||||
class: cls = '',
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
/** Import local helpers */
|
||||
import Block from '../Block/Block.svelte';
|
||||
|
|
@ -140,7 +81,7 @@
|
|||
import SearchInput from '../SearchInput/SearchInput.svelte';
|
||||
import Select from './Select.svelte';
|
||||
import SortArrow from './SortArrow.svelte';
|
||||
import { filterArray, paginateArray, getOptions } from './utils.js';
|
||||
import { filterArray, paginateArray, getOptions } from './utils.ts';
|
||||
|
||||
/** Set truncate, filtering and pagination configuration */
|
||||
let showAll = false;
|
||||
|
|
@ -230,9 +171,9 @@
|
|||
{#if filterList}
|
||||
<div class="table--header--filter">
|
||||
<Select
|
||||
label="{filterLabel || filterField}"
|
||||
options="{filterList}"
|
||||
on:select="{handleFilterInput}"
|
||||
label={filterLabel || filterField}
|
||||
options={filterList}
|
||||
on:select={handleFilterInput}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -240,7 +181,7 @@
|
|||
<div class="table--header--search">
|
||||
<SearchInput
|
||||
bind:searchPlaceholder
|
||||
on:search="{handleSearchInput}"
|
||||
on:search={handleSearchInput}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -252,9 +193,7 @@
|
|||
<table
|
||||
class="w-full"
|
||||
class:paginated
|
||||
class:truncated="{truncated &&
|
||||
!showAll &&
|
||||
data.length > truncateLength}"
|
||||
class:truncated={truncated && !showAll && data.length > truncateLength}
|
||||
>
|
||||
<thead class="table--thead">
|
||||
<tr>
|
||||
|
|
@ -262,22 +201,22 @@
|
|||
<th
|
||||
scope="col"
|
||||
class="table--thead--th h4 pl-0 py-2 pr-2"
|
||||
class:sortable="{sortable && sortableFields.includes(field)}"
|
||||
class:sort-ascending="{sortable &&
|
||||
class:sortable={sortable && sortableFields.includes(field)}
|
||||
class:sort-ascending={sortable &&
|
||||
sortField === field &&
|
||||
sortDirection === 'ascending'}"
|
||||
class:sort-descending="{sortable &&
|
||||
sortDirection === 'ascending'}
|
||||
class:sort-descending={sortable &&
|
||||
sortField === field &&
|
||||
sortDirection === 'descending'}"
|
||||
data-field="{field}"
|
||||
on:click="{handleSort}"
|
||||
sortDirection === 'descending'}
|
||||
data-field={field}
|
||||
on:click={handleSort}
|
||||
>
|
||||
{field}
|
||||
{#if sortable && sortableFields.includes(field)}
|
||||
<div class="table--thead--sortarrow fml-1 avoid-clicks">
|
||||
<SortArrow
|
||||
bind:sortDirection
|
||||
active="{sortField === field}"
|
||||
active={sortField === field}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -287,13 +226,13 @@
|
|||
</thead>
|
||||
<tbody class="table--tbody">
|
||||
{#each currentPageData as item, idx}
|
||||
<tr data-row-index="{idx}">
|
||||
<tr data-row-index={idx}>
|
||||
{#each includedFields as field}
|
||||
<td
|
||||
class="body-note pl-0 py-2 pr-2"
|
||||
data-row-index="{idx}"
|
||||
data-field="{field}"
|
||||
data-value="{item[field]}"
|
||||
data-row-index={idx}
|
||||
data-field={field}
|
||||
data-value={item[field]}
|
||||
>
|
||||
{@html formatValue(item, field)}
|
||||
</td>
|
||||
|
|
@ -302,7 +241,7 @@
|
|||
{/each}
|
||||
{#if searchable && searchText && currentPageData.length === 0}
|
||||
<tr>
|
||||
<td class="no-results" colspan="{includedFields.length}">
|
||||
<td class="no-results" colspan={includedFields.length}>
|
||||
No results found for "{searchText}"
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -312,7 +251,7 @@
|
|||
<tfoot class="table--tfoot">
|
||||
{#if notes}
|
||||
<tr>
|
||||
<td class="" colspan="{includedFields.length}">
|
||||
<td class="" colspan={includedFields.length}>
|
||||
<div class="fmt-2">
|
||||
{@html notes}
|
||||
</div>
|
||||
|
|
@ -321,7 +260,7 @@
|
|||
{/if}
|
||||
{#if source}
|
||||
<tr>
|
||||
<td class="" colspan="{includedFields.length}">
|
||||
<td class="" colspan={includedFields.length}>
|
||||
<div class="fmt-1">
|
||||
{@html source}
|
||||
</div>
|
||||
|
|
@ -337,7 +276,7 @@
|
|||
aria-label="Show all button"
|
||||
class="show-all flex items-center justify-center fmt-2"
|
||||
>
|
||||
<button class="body-caption" on:click="{toggleTruncate}"
|
||||
<button class="body-caption" on:click={toggleTruncate}
|
||||
>{#if showAll}Show fewer rows{:else}Show {data.length -
|
||||
truncateLength} more rows{/if}</button
|
||||
>
|
||||
|
|
@ -347,8 +286,8 @@
|
|||
<Pagination
|
||||
bind:pageNumber
|
||||
bind:pageSize
|
||||
bind:pageLength="{currentPageData.length}"
|
||||
bind:n="{sortedData.length}"
|
||||
bind:pageLength={currentPageData.length}
|
||||
bind:n={sortedData.length}
|
||||
/>{/if}
|
||||
</article>
|
||||
</Block>
|
||||
|
|
|
|||
Loading…
Reference in a new issue