feat(components): add initial DataTable component

This commit is contained in:
Eric Liu 2020-01-06 06:06:08 -08:00
commit f7551e4a8f
11 changed files with 359 additions and 0 deletions

View file

@ -0,0 +1,126 @@
<script>
export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte';
import DataTable from './DataTable.svelte';
import Table from './Table.svelte';
import TableBody from './TableBody.svelte';
import TableCell from './TableCell.svelte';
import TableContainer from './TableContainer.svelte';
import TableHead from './TableHead.svelte';
import TableHeader from './TableHeader.svelte';
import TableRow from './TableRow.svelte';
let rows = [
{
id: 'a',
name: 'Load Balancer 3',
protocol: 'HTTP',
port: 3000,
rule: 'Round robin',
attached_groups: 'Kevins VM Groups',
status: 'Disabled'
},
{
id: 'b',
name: 'Load Balancer 1',
protocol: 'HTTP',
port: 443,
rule: 'Round robin',
attached_groups: 'Maureens VM Groups',
status: 'Starting'
},
{
id: 'c',
name: 'Load Balancer 2',
protocol: 'HTTP',
port: 80,
rule: 'DNS delegation',
attached_groups: 'Andrews VM Groups',
status: 'Active'
},
{
id: 'd',
name: 'Load Balancer 6',
protocol: 'HTTP',
port: 3000,
rule: 'Round robin',
attached_groups: 'Marcs VM Groups',
status: 'Disabled'
},
{
id: 'e',
name: 'Load Balancer 4',
protocol: 'HTTP',
port: 443,
rule: 'Round robin',
attached_groups: 'Mels VM Groups',
status: 'Starting'
},
{
id: 'f',
name: 'Load Balancer 5',
protocol: 'HTTP',
port: 80,
rule: 'DNS delegation',
attached_groups: 'Ronjas VM Groups',
status: 'Active'
}
];
let headers = [
{ key: 'name', value: 'Name' },
{ key: 'protocol', value: 'Protocol' },
{ key: 'port', value: 'Port' },
{ key: 'rule', value: 'Rule' },
{ key: 'attached_groups', value: 'Attached Groups' },
{ key: 'status', value: 'Status' }
];
</script>
<Layout>
{#if story === 'composed'}
<DataTable {...$$props} {rows} {headers} let:props>
<TableContainer
title="DataTable"
description="With default options"
{...props.getTableContainerProps()}>
<Table {...props.getTableProps()}>
<TableHead>
<TableRow>
{#each props.headers as header, i (header.key)}
<TableHeader {...props.getHeaderProps({ header })}>{header.header}</TableHeader>
{/each}
</TableRow>
</TableHead>
<TableBody>
{#each props.rows as row, i}
<TableRow {...props.getRowProps({ row })}>
{#each row.cells as cell, j}
<TableCell>{cell.value}</TableCell>
{/each}
</TableRow>
{/each}
</TableBody>
</Table>
</TableContainer>
</DataTable>
{:else}
<DataTable
title={$$props.title}
description={$$props.description}
zebra={$$props.zebra}
size={$$props.size}
stickyHeader={$$props.stickyHeader}
on:click:header={({ detail }) => {
console.log('on:click:header', detail);
}}
on:click:row={({ detail }) => {
console.log('on:click:row', detail);
}}
on:click:cell={({ detail }) => {
console.log('on:click:cell', detail);
}}
{rows}
{headers} />
{/if}
</Layout>

View file

@ -0,0 +1,19 @@
import { withKnobs, boolean, select, text } from '@storybook/addon-knobs';
import Component from './DataTable.Story.svelte';
export default { title: 'DataTable', decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
title: text('Optional DataTable title (title)', ''),
description: text('Optional DataTable description (description)', ''),
zebra: boolean('Zebra row styles (zebra)', false),
size: select(
'Row height (size)',
{ compact: 'compact', short: 'short', tall: 'tall', none: null },
null
),
stickyHeader: boolean('Sticky header (experimental)', false)
}
});

View file

@ -0,0 +1,68 @@
<script>
let className = undefined;
export { className as class };
// refined props
export let title = '';
export let description = '';
export let zebra = false;
export let rows = [];
export let headers = [];
export let stickyHeader = false;
export let size = undefined;
export let style = undefined;
import { createEventDispatcher } from 'svelte';
import Table from './Table.svelte';
import TableBody from './TableBody.svelte';
import TableCell from './TableCell.svelte';
import TableContainer from './TableContainer.svelte';
import TableHead from './TableHead.svelte';
import TableHeader from './TableHeader.svelte';
import TableRow from './TableRow.svelte';
const dispatch = createEventDispatcher();
$: headerKeys = headers.map(({ key }) => key);
$: rows = rows.map(row => ({
...row,
cells: headerKeys.map(key => ({ key, value: row[key] }))
}));
$: props = {};
</script>
<slot {props}>
<TableContainer class={className} {title} {description} {style}>
<Table {zebra} {size} {stickyHeader}>
<TableHead>
<TableRow>
{#each headers as header, i (header.key)}
<TableHeader
on:click={() => {
dispatch('click:header', { header });
}}>
{header.value}
</TableHeader>
{/each}
</TableRow>
</TableHead>
<TableBody>
{#each rows as row, i (row.id)}
<TableRow
on:click={() => {
dispatch('click:row', { row });
}}>
{#each row.cells as cell, j (cell.key)}
<TableCell
on:click={() => {
dispatch('click:cell', { row, cell });
}}>
{cell.value}
</TableCell>
{/each}
</TableRow>
{/each}
</TableBody>
</Table>
</TableContainer>
</slot>

View file

@ -0,0 +1,28 @@
<script>
let className = undefined;
export { className as class };
export let zebra = false;
export let size = undefined;
export let useStaticWidth = false;
export let shouldShowBorder = false;
export let isSortable = false;
export let stickyHeader = false;
export let style = undefined;
import { cx } from '../../lib';
</script>
{#if stickyHeader}
<section class={cx('--data-table_inner-container', className)} {style}>
<table
class={cx('--data-table', size === 'compact' && '--data-table--compact', size === 'short' && '--data-table--short', size === 'tall' && '--data-table--tall', isSortable && '--data-table--sort', zebra && '--data-table--zebra', useStaticWidth && '--data-table--static', !shouldShowBorder && '--data-table--no-border', stickyHeader && '--data-table--sticky-header')}>
<slot />
</table>
</section>
{:else}
<table
class={cx('--data-table', size === 'compact' && '--data-table--compact', size === 'short' && '--data-table--short', size === 'tall' && '--data-table--tall', isSortable && '--data-table--sort', zebra && '--data-table--zebra', useStaticWidth && '--data-table--static', !shouldShowBorder && '--data-table--no-border', stickyHeader && '--data-table--sticky-header', className)}
{style}>
<slot />
</table>
{/if}

View file

@ -0,0 +1,9 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<tbody aria-live={$$props['aria-live'] || 'polite'} class={className} {style}>
<slot />
</tbody>

View file

@ -0,0 +1,9 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<td on:click on:mouseover on:mouseenter on:mouseleave class={className} {style}>
<slot />
</td>

View file

@ -0,0 +1,22 @@
<script>
let className = undefined;
export { className as class };
export let stickyHeader = false;
export let title = '';
export let description = '';
export let style = undefined;
import { cx } from '../../lib';
</script>
<div
class={cx('--data-table-container', stickyHeader && '--data-table--max-width', className)}
{style}>
{#if title}
<div class={cx('--data-table-header')}>
<h4 class={cx('--data-table-header__title')}>{title}</h4>
<p class={cx('--data-table-header__description')}>{description}</p>
</div>
{/if}
<slot />
</div>

View file

@ -0,0 +1,9 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
</script>
<thead on:click on:mouseover on:mouseenter on:mouseleave class={className} {style}>
<slot />
</thead>

View file

@ -0,0 +1,49 @@
<script>
let className = undefined;
export { className as class };
export let isSortHeader = false;
export let isSortable = false;
export let scope = 'col';
export let sortDirection = undefined;
export let translateWithId = () => '';
export let style = undefined;
import ArrowUp20 from 'carbon-icons-svelte/lib/ArrowUp20';
import ArrowsVertical20 from 'carbon-icons-svelte/lib/ArrowsVertical20';
import { cx } from '../../lib';
const sortDirections = {
NONE: 'none',
ASC: 'ascending',
DESC: 'descending'
};
$: ariaSort = isSortHeader ? sortDirections[sortDirection] : 'none';
// TODO: translate with id
$: ariaLabel = translateWithId();
</script>
{#if !isSortable}
<th on:click on:mouseover on:mouseenter on:mouseleave class={className} {style} {scope}>
<span class={cx('--table-header-label')}>
<slot />
</span>
</th>
{:else}
<th
{scope}
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={cx('--table-sort', isSortHeader && sortDirection !== 'NONE' && '--table-sort--active', isSortHeader && sortDirection === 'DESC' && '--table-sort--ascending', className)}
aria-sort={ariaSort}>
<button on:click>
<span class={cx('--table-header-label')}>
<slot />
</span>
<ArrowUp20 class={cx('--table-sort__icon')} aria-label={ariaLabel} />
<ArrowsVertical20 class={cx('--table-sort__icon-unsorted')} aria-label={ariaLabel} />
</button>
</th>
{/if}

View file

@ -0,0 +1,20 @@
<script>
let className = undefined;
export { className as class };
export let isSelected = false;
export let style = undefined;
import { cx } from '../../lib';
// TODO: include ariaLabel, onExpand, isExpanded, isSelected
</script>
<tr
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={cx(isSelected && '--data-table--selected', className)}
{style}>
<slot />
</tr>

View file