Broke out a Select component
This commit is contained in:
parent
4b8be6a562
commit
a1085893b8
10 changed files with 155 additions and 75 deletions
|
|
@ -1,5 +1,13 @@
|
|||
import type { ComponentType } from 'svelte';
|
||||
|
||||
/**
|
||||
* Used for the list of <option> tags nested in a <select> input.
|
||||
*/
|
||||
export type Option = {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used for any props that restrict width of a container to one of pre-fab widths.
|
||||
*/
|
||||
|
|
|
|||
78
src/components/Table/Select.svelte
Normal file
78
src/components/Table/Select.svelte
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Option } from '../@types/global';
|
||||
|
||||
/**
|
||||
* The label that appears above the select input.
|
||||
* @type {string}
|
||||
*/
|
||||
export let label: string = '';
|
||||
|
||||
/**
|
||||
* The label that appears above the select input.
|
||||
* @type {Array}
|
||||
*/
|
||||
export let options: Option[] = [];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function input(event) {
|
||||
const value = event.target.value;
|
||||
dispatch('select', { value });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="select">
|
||||
{#if label}
|
||||
<label for="select--input">{label}</label>
|
||||
{/if}
|
||||
<select
|
||||
class="select--input"
|
||||
name="select--input"
|
||||
id="select--input"
|
||||
on:input="{input}"
|
||||
>
|
||||
{#each options as obj}
|
||||
<option value="{obj.value}">{obj.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../scss/colours/thematic/tr';
|
||||
@import '../../scss/fonts/variables';
|
||||
|
||||
.select {
|
||||
width: 256px;
|
||||
font-family: $font-family-display;
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 300;
|
||||
color: $tr-dark-grey;
|
||||
padding: 0 0 0.125rem 0;
|
||||
}
|
||||
.select--input {
|
||||
position: relative;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
height: 33px;
|
||||
border: 1px solid $tr-muted-grey;
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
-moz-appearance: none; /* Firefox */
|
||||
-webkit-appearance: none; /* Safari and Chrome */
|
||||
appearance: none; /* Remove the default arrow */
|
||||
padding-right: 20px; /* Add some padding to make room for a custom arrow */
|
||||
background: transparent;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg width="15" height="9" viewBox="0 0 15 9" xmlns="http://www.w3.org/2000/svg"><path d="M6.76474 8.30466L0.236082 1.54523C-0.0786943 1.21934 -0.0786943 0.69069 0.236082 0.364804C0.550521 0.0392666 1.19794 0.0403099 1.51305 0.364804L7.33483 6.49522L12.9249 0.475171C13.3549 0.0451683 14.1195 0.0396141 14.4339 0.365152C14.7487 0.691037 14.7487 1.21969 14.4339 1.54557L7.90492 8.30466C7.59015 8.63054 7.07952 8.63054 6.76474 8.30466Z" fill="gray"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: 235px;
|
||||
background-position-y: 55%;
|
||||
}
|
||||
.select--input::-ms-expand {
|
||||
display: none; /* Remove the default arrow in Internet Explorer 11 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -129,7 +129,8 @@
|
|||
filterField: 'Region',
|
||||
paginated: true,
|
||||
title: 'Press Freedom Index',
|
||||
notes:
|
||||
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: <a href='https://en.wikipedia.org/wiki/Press_Freedom_Index'>Reporters Without Borders</a>",
|
||||
}}"
|
||||
/>
|
||||
|
|
@ -155,11 +156,11 @@
|
|||
args="{{
|
||||
data: richestWomen,
|
||||
title: 'The Richest Women in the World',
|
||||
notes:
|
||||
source:
|
||||
"Source: <a href='https://www.forbes.com/sites/rachelsandler/2022/04/05/the-top-richest-women-in-the-world-2022/?sh=29c2f69446a8'>Forbes</a>",
|
||||
sortable: true,
|
||||
sortField: 'Net worth (in billions)',
|
||||
sortDirection: 'descending',
|
||||
fieldFormatters: { 'Net worth (in billions)': (v) => '$' + v },
|
||||
fieldFormatters: { 'Net worth (in billions)': (v) => '$' + v.toFixed(1) },
|
||||
}}"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -128,11 +128,12 @@
|
|||
import Block from '../Block/Block.svelte';
|
||||
import Pagination from './Pagination.svelte';
|
||||
import Search from './Search.svelte';
|
||||
import Select from './Select.svelte';
|
||||
import SortArrow from './SortArrow.svelte';
|
||||
import {
|
||||
filterArray,
|
||||
paginateArray,
|
||||
uniqueAttr,
|
||||
getOptions,
|
||||
isNumeric,
|
||||
} from './utils.js';
|
||||
|
||||
|
|
@ -140,9 +141,7 @@
|
|||
let showAll = false;
|
||||
let pageNumber = 1;
|
||||
let searchText = '';
|
||||
const filterList = filterField
|
||||
? uniqueAttr(data, filterField).sort((a, b) => a.localeCompare(b))
|
||||
: undefined;
|
||||
const filterList = filterField ? getOptions(data, filterField) : undefined;
|
||||
let filterValue = '';
|
||||
$: filteredData = filterArray(data, searchText, filterField, filterValue);
|
||||
$: sortedData = sortArray(filteredData, sortField, sortDirection);
|
||||
|
|
@ -174,8 +173,8 @@
|
|||
}
|
||||
|
||||
function handleFilterInput(event) {
|
||||
const value = event.target.value;
|
||||
filterValue = value === 'all' ? '' : value;
|
||||
const value = event.detail.value;
|
||||
filterValue = value === 'All' ? '' : value;
|
||||
pageNumber = 1;
|
||||
}
|
||||
|
||||
|
|
@ -239,31 +238,21 @@
|
|||
{/if}
|
||||
{#if searchable || filterList}
|
||||
<section class="input">
|
||||
{#if searchable}
|
||||
<Search
|
||||
bind:searchPlaceholder
|
||||
on:search="{handleSearchInput}"
|
||||
/>
|
||||
{/if}
|
||||
{#if filterList}
|
||||
{#if searchable}<div
|
||||
style="clear: both; display: block; padding-top: 1rem;"
|
||||
></div>{/if}
|
||||
<div class="filter">
|
||||
<label for="filter--select"
|
||||
>{#if filterLabel}{filterLabel}{:else}Filter by {filterField}{/if}</label
|
||||
>
|
||||
<select
|
||||
class="filter--select"
|
||||
name="filter--select"
|
||||
id="filter--select"
|
||||
on:input="{handleFilterInput}"
|
||||
>
|
||||
<option value="all">All</option>
|
||||
{#each filterList as object}
|
||||
<option value="{object}">{object}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="table--caption--filter">
|
||||
<Select
|
||||
label="{filterLabel || filterField}"
|
||||
options="{filterList}"
|
||||
on:select="{handleFilterInput}"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if searchable}
|
||||
<div class="table--caption--search">
|
||||
<Search
|
||||
bind:searchPlaceholder
|
||||
on:search="{handleSearchInput}"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
@ -455,25 +444,13 @@
|
|||
section.input {
|
||||
margin: 0.5rem 0 0 0;
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
label {
|
||||
line-height: 1.333;
|
||||
display: block;
|
||||
font-size: 1.125rem;
|
||||
font-family: $font-family-display;
|
||||
font-weight: 500;
|
||||
}
|
||||
.filter {
|
||||
.filter--select {
|
||||
padding: 0.33rem;
|
||||
font-size: 1.1rem;
|
||||
height: 2.3rem;
|
||||
border: 1px solid $tr-muted-grey;
|
||||
border-radius: 5px;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
nav.show-all {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ Feel free to both search and filter.
|
|||
|
||||
```svelte
|
||||
<Table
|
||||
data={yourData},
|
||||
searchable=true,
|
||||
filterField='Region',
|
||||
title='Press Freedom Index',
|
||||
notes='Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>',
|
||||
data="{yourData}",
|
||||
searchable="{true}",
|
||||
filterField="{'Region'}",
|
||||
paginated="{true}",
|
||||
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: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>'}",
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ Allow users to filter the table by providing one of the attributes as the `filte
|
|||
|
||||
```svelte
|
||||
<Table
|
||||
data={yourData},
|
||||
filterField='Region',
|
||||
title='Press Freedom Index',
|
||||
notes='Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>',
|
||||
data="{yourData}",
|
||||
filterField="{'Region'}",
|
||||
paginate="{true}"
|
||||
title="{'Press Freedom Index'}",
|
||||
notes="{'Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>'}",
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
Provide custom formatters by providing functions keyed to your field names to the `fieldFormatters` option. Columns are still sorted by the raw, underlying values. This can be used to provide the unit of measurement with numeric fields.
|
||||
Format columns 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 with numeric fields.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
const fieldFormatters = {
|
||||
'Net worth (in billions)': (v) => '$' + v
|
||||
'Net worth (in billions)': (v) => '$' + v.toFixed(1)
|
||||
};
|
||||
</script>
|
||||
|
||||
<Table
|
||||
data={yourData}
|
||||
fieldFormatters={fieldFormatters},
|
||||
sortable=true,
|
||||
sortField='Score',
|
||||
sortDirection='descending',
|
||||
title='Press Freedom Index',
|
||||
notes='Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>',
|
||||
data="{yourData}"
|
||||
fieldFormatters="{fieldFormatters}",
|
||||
sortable="{true}",
|
||||
sortField="{'Score'}",
|
||||
sortDirection="{'descending'}",
|
||||
title:"{'The Richest Women in the World'}",
|
||||
source: "{'Source: Forbes'}",
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ This is a good option when publishing large tables for readers to explore. It wo
|
|||
data="{yourData}"
|
||||
paginated="{true}"
|
||||
title="{'Press Freedom Index'}"
|
||||
source="{'<a href='https://en.wikipedia.org/wiki/Press_Freedom_Index'>Reporters Without Borders</a>'}"
|
||||
source="{'<a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>'}"
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ Allow users to search the table by setting the optional `searchable` variable. M
|
|||
|
||||
```svelte
|
||||
<Table
|
||||
data="{yourData},"
|
||||
searchable="{true},"
|
||||
paginated="{true},"
|
||||
title="{'Press Freedom Index'},"
|
||||
notes="{'Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>'},"
|
||||
data="{yourData}",
|
||||
searchable="{true}",
|
||||
paginated="{true}",
|
||||
title="{'Press Freedom Index'}",
|
||||
notes="{'Source: <a href="https://en.wikipedia.org/wiki/Press_Freedom_Index">Reporters Without Borders</a>'}",
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@ export function unique(value, index, array) {
|
|||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export function getOptions(data, attr) {
|
||||
// Get all the unique values in the provided field. Sort it.
|
||||
const attrList = uniqueAttr(data, attr).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
// Tack 'All' as the front as the first option.
|
||||
attrList.unshift('All');
|
||||
|
||||
// Convert the list into Option typed objects ready for our Select component
|
||||
return attrList.map((a) => ({ text: a, value: a }));
|
||||
}
|
||||
|
||||
export function isNumeric(value) {
|
||||
return (
|
||||
typeof value === 'number' ||
|
||||
|
|
|
|||
Loading…
Reference in a new issue