diff --git a/src/components/SearchInput/SearchInput.mdx b/src/components/SearchInput/SearchInput.mdx index cd743181..6c347cd5 100644 --- a/src/components/SearchInput/SearchInput.mdx +++ b/src/components/SearchInput/SearchInput.mdx @@ -12,16 +12,18 @@ The `SearchInput` component creates a search bar. - + ``` - \ No newline at end of file + diff --git a/src/components/SearchInput/SearchInput.stories.svelte b/src/components/SearchInput/SearchInput.stories.svelte index bca718e7..ae596d55 100644 --- a/src/components/SearchInput/SearchInput.stories.svelte +++ b/src/components/SearchInput/SearchInput.stories.svelte @@ -8,4 +8,17 @@ }); - \ No newline at end of file + + + + + diff --git a/src/components/SearchInput/SearchInput.svelte b/src/components/SearchInput/SearchInput.svelte index 929a0e85..01e78d05 100644 --- a/src/components/SearchInput/SearchInput.svelte +++ b/src/components/SearchInput/SearchInput.svelte @@ -6,7 +6,7 @@ interface Props { /** The placeholder text that appears in the search box.*/ searchPlaceholder?: string; - + /** Optional function that runs when the input value changes. */ onsearch?: (newValue: string) => void; } diff --git a/src/components/Table/Table.mdx b/src/components/Table/Table.mdx index 66c4ad8c..1af6c21b 100644 --- a/src/components/Table/Table.mdx +++ b/src/components/Table/Table.mdx @@ -6,14 +6,172 @@ import * as TableStories from './Table.stories.svelte'; # Table -The `Table` component TKTK +The `Table` component presents data as a table that you can make searchable, filtereable, sortable, or paginated. ```svelte - +
``` + +## Text elements + +Set the `title`, `dek`, `notes` and `source` options to add supporting metadata above and below the table. + +```svelte +
+``` + + + +## Truncated + +When your table has 10 or more rows, consider clipping it by setting the `truncated` option. When it is enabled, the table is clipped and readers must click a button below the table to see all rows. + +By default, this configuration will limit the table to 5 records. Change the cutoff point by adjusting the `truncateLength` option. + +This is a good option for simple tables with between 10 and 30 rows. It works best when the table doesn't require interactivity. + +```svelte +
+``` + + + +## Paginated + +When your table has many rows, you should consider breaking it up into pages by setting `paginated` to `true`. When it is enabled, readers can leaf through the data using a set of buttons below the table. + +By default, there are 25 records per page. Change the number by adjusting the `pageSize` option. + +This is a good option when publishing large tables for readers to explore. It works well with interactive features like searching and filters. + +```svelte +
+``` + + + +## Search bar + +Allow users to search the table by setting the optional `searchable` option to `true`. Modify the default text that appears in the box by setting `searchPlaceholder` to a different placeholder text. + +```svelte +
+``` + + + +## Filter + +Allow users to filter the table by providing one of the attributes as the `filterField`. This works best with categorical columns. + +Set `filterLabel` to make the category name more readable. For example, if the column is `Region`, set `filterLabel` to `regions` or `regions of the world`. + +```svelte +
+``` + + + +## Search and filter + +Feel free to both search and filter. + +```svelte +
+``` + + +``` + +## Sort + +Allow users to sort the table by setting `sortable` to `true`. Specify the starting order by setting `sortField` to a column name and `sortDirection` to `ascending` or `descending`. + +By default, all fields are sortable. If you'd like to limit the columns where sorting is allowed, provide a list to the `sortableFields` option. + +```svelte +
+``` + + + +## Format + +Format column values by supplying functions keyed to field names with the `fieldFormatters` option. Columns are still sorted using the raw, underlying values. + +Among other things, this feature can be used to provide a unit of measurement, such as `$` or `%`, with numeric fields. + +```svelte + + +
+``` + + diff --git a/src/components/Table/Table.stories.svelte b/src/components/Table/Table.stories.svelte index 6f766425..7b230b9a 100644 --- a/src/components/Table/Table.stories.svelte +++ b/src/components/Table/Table.stories.svelte @@ -18,8 +18,9 @@ import pressFreedom from './demo/pressFreedom.json'; import homeRuns from './demo/homeRuns.json'; import richestWomen from './demo/richestWomen.json'; + import type { Formatter } from './utils'; - const currencyFormat = (v: number) => '$' + v.toFixed(1); + const currencyFormat: Formatter = (v: number) => '$' + v.toFixed(1); , + }, sortable: true, sortField: 'Net worth (in billions)', sortDirection: 'descending', - fieldFormatters: { 'Net worth (in billions)': currencyFormat }, - }} -/> - -> { /** Data for the table as an array. */ @@ -47,7 +57,7 @@ /** 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; + fieldFormatters?: FieldFormatters; /** Width of the component within the text well. */ width?: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid'; /** Add an ID to target with SCSS. */ @@ -75,33 +85,12 @@ sortField = Object.keys(data[0])[0], sortableFields = Object.keys(data[0]).filter((f) => f !== 'searchStr'), sortDirection = $bindable('ascending'), - fieldFormatters = {}, + fieldFormatters, width = 'normal', id = '', class: cls = '', }: Props> = $props(); - /** Derived variables */ - // let includedFieldsDerived = $derived.by(() => { - // if (includedFields) return includedFields; - // if (data.length > 0) - // return Object.keys(data[0]).filter((f) => f !== 'searchStr'); - // return []; - // }); - - // let sortableFieldsDerived = $derived.by(() => { - // if (sortableFields) return sortableFields; - // if (data.length > 0) - // return Object.keys(data[0]).filter((f) => f !== 'searchStr'); - // return []; - // }); - - // let sortFieldDerived = $derived.by(() => { - // if (sortField) return sortField; - // if (data.length > 0) return Object.keys(data[0])[0]; - // return ''; - // }); - /** Set truncate, filtering and pagination configuration */ let showAll = $state(false); let pageNumber = $state(1); @@ -109,61 +98,31 @@ let filterList = $derived( filterField ? getOptions(data, filterField) : undefined ); - let filterValue = ''; + let filterValue = $state(''); + /** Helper functions that modify variables within this component */ //* * Handle show all, search, filter, sort and pagination events */ - function toggleTruncate(_event) { + function toggleTruncate() { showAll = !showAll; } - // function handleSearchInput(event: CustomEvent) { - // searchText = event.detail; - - // console.log('searchText', searchText); - // pageNumber = 1; - // } - /** Filters table data based on the input value in the search bar */ function handleSearchInput(newSearchText: string) { searchText = newSearchText; pageNumber = 1; } - function handleFilterInput(event) { - const value = event.detail.value; - filterValue = value === 'All' ? '' : value; + function handleFilterInput(newSearchText: string) { + filterValue = newSearchText === 'All' ? '' : newSearchText; pageNumber = 1; } - function handleSort(event) { + function handleSort(event: MouseEvent) { if (!sortable) return; - sortField = event.target.getAttribute('data-field'); + sortField = (event.target as HTMLElement).getAttribute('data-field') || ''; sortDirection = sortDirection === 'ascending' ? 'descending' : 'ascending'; } - function sortArray(array, column, direction) { - if (!sortable) return array; - return array.sort((a, b) => { - if (a[column] < b[column]) { - return direction === 'ascending' ? -1 : 1; - } else if (a[column] > b[column]) { - return direction === 'ascending' ? 1 : -1; - } else { - return 0; - } - }); - } - - function formatValue(item, field) { - const value = item[field]; - if (field in fieldFormatters) { - const func = fieldFormatters[field]; - return func(value); - } else { - return value; - } - } - /** Add the `searchStr` field to data */ let searchableData = $derived.by(() => { return data.map((d) => { @@ -182,7 +141,7 @@ ); let sortedData = $derived.by(() => - sortArray(filteredData, sortField, sortDirection) + sortArray(filteredData, sortField, sortDirection, sortable) ); let currentPageData = $derived.by(() => { @@ -212,8 +171,8 @@
{#each options as obj} - + {/each}
diff --git a/src/components/Table/components/SortArrow.svelte b/src/components/Table/components/SortArrow.svelte index 4fb751bf..7eb22c7d 100644 --- a/src/components/Table/components/SortArrow.svelte +++ b/src/components/Table/components/SortArrow.svelte @@ -26,11 +26,11 @@ class="avoid-clicks" > diff --git a/src/components/Table/utils.ts b/src/components/Table/utils.ts index be9357e5..51b36a7a 100644 --- a/src/components/Table/utils.ts +++ b/src/components/Table/utils.ts @@ -63,3 +63,60 @@ export function getOptions(data: T[], attr: keyof T) { // Convert the list into Option typed objects ready for our Select component return attrList.map((a) => ({ text: a, value: a })); } +interface SortableItem { + [key: string]: unknown; // Or more specific types if known +} + +/** + * Sorts an array of objects based on a specified column and direction. + */ +export function sortArray( + /** The array to sort. */ + array: T[], + /** The column to sort by. */ + column: keyof T, + /** The sorting direction ('ascending' or 'descending'). */ + direction: 'ascending' | 'descending', + /** Whether or not sorting is turned on */ + sortable: boolean +) { + if (!sortable) return array; + + const sorted = [...array].sort((a, b) => { + if (a[column] < b[column]) { + return direction === 'ascending' ? -1 : 1; + } else if (a[column] > b[column]) { + return direction === 'ascending' ? 1 : -1; + } else { + return 0; + } + }); + + return sorted; +} + +export type Formatter = (value: T) => string; + +export type FieldFormatters = { + [K in keyof T]?: Formatter; +}; +/** + * Formats a value based on a field and a dictionary of formatters. + */ +export function formatValue>( + /** The object containing the field. */ + item: FilterableDatum, + /** The field to format. */ + field: keyof T, + /** An optional dictionary of formatters. */ + fieldFormatters?: FieldFormatters +) { + const value = item[field]; + + if (fieldFormatters && field in fieldFormatters && fieldFormatters[field]) { + const formatter = fieldFormatters[field]; + return formatter(value); + } else { + return value; + } +}