Skip to content

Commit 47585e0

Browse files
authored
Merge pull request #152 from ownego/component/data-table
Component/data table
2 parents 08350c1 + 707eb76 commit 47585e0

File tree

16 files changed

+1464
-5
lines changed

16 files changed

+1464
-5
lines changed

src/classes/DataTable.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"DataTable":"Polaris-DataTable","condensed":"Polaris-DataTable--condensed","Navigation":"Polaris-DataTable__Navigation","IncreasedTableDensity":"Polaris-DataTable__IncreasedTableDensity","Pip":"Polaris-DataTable__Pip","Pip-visible":"Polaris-DataTable__Pip--visible","ScrollContainer":"Polaris-DataTable__ScrollContainer","Table":"Polaris-DataTable__Table","TableRow":"Polaris-DataTable__TableRow","Cell":"Polaris-DataTable__Cell","ZebraStripingOnData":"Polaris-DataTable__ZebraStripingOnData","RowCountIsEven":"Polaris-DataTable__RowCountIsEven","ShowTotalsInFooter":"Polaris-DataTable__ShowTotalsInFooter","Cell-firstColumn":"Polaris-DataTable__Cell--firstColumn","Cell-numeric":"Polaris-DataTable__Cell--numeric","Cell-truncated":"Polaris-DataTable__Cell--truncated","Cell-header":"Polaris-DataTable__Cell--header","Cell-sortable":"Polaris-DataTable__Cell--sortable","Heading-left":"Polaris-DataTable__Heading--left","Cell-verticalAlignTop":"Polaris-DataTable__Cell--verticalAlignTop","Cell-verticalAlignBottom":"Polaris-DataTable__Cell--verticalAlignBottom","Cell-verticalAlignMiddle":"Polaris-DataTable__Cell--verticalAlignMiddle","Cell-verticalAlignBaseline":"Polaris-DataTable__Cell--verticalAlignBaseline","hoverable":"Polaris-DataTable--hoverable","Cell-hovered":"Polaris-DataTable__Cell--hovered","Icon":"Polaris-DataTable__Icon","Heading":"Polaris-DataTable__Heading","StickyHeaderEnabled":"Polaris-DataTable__StickyHeaderEnabled","StickyTable":"Polaris-DataTable__StickyTable","Cell-sorted":"Polaris-DataTable__Cell--sorted","Cell-total":"Polaris-DataTable__Cell--total","ShowTotals":"Polaris-DataTable__ShowTotals","Cell-total-footer":"Polaris-DataTable--cellTotalFooter","Footer":"Polaris-DataTable__Footer","StickyTableHeader":"Polaris-DataTable__StickyTableHeader","StickyTableHeader-isSticky":"Polaris-DataTable__StickyTableHeader--isSticky","StickyTableColumnHeader-isScrolling":"Polaris-DataTable__StickyTableColumnHeader--isScrolling","StickyTableHeadingsRow":"Polaris-DataTable__StickyTableHeadingsRow","FixedFirstColumn":"Polaris-DataTable__FixedFirstColumn","separate":"Polaris-DataTable--separate","TooltipContent":"Polaris-DataTable__TooltipContent"}

src/components/DataTable/DataTable.vue

Lines changed: 739 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
2+
import { Card, DataTable, TextStyle, Link } from '@/polaris-vue';
3+
import { computed, ref } from 'vue';
4+
import dedent from 'ts-dedent';
5+
6+
<Meta
7+
title="Components / Lists and tables / Data table"
8+
component={ DataTable }
9+
argTypes={{
10+
default: {
11+
table: { disable: true },
12+
},
13+
defaultSortDirection: {
14+
options: ['ascending', 'descending', 'none'],
15+
control: { type: 'select' },
16+
table: {
17+
type: { summary: 'ascending | descending | none' },
18+
},
19+
},
20+
headings: {
21+
table: {
22+
type: { summary: 'string[]' },
23+
},
24+
},
25+
rows: {
26+
control: { disable: true },
27+
table: {
28+
type: { summary: '<string | number>[][]' },
29+
},
30+
},
31+
sortable: {
32+
table: {
33+
type: { summary: 'boolean[]' },
34+
},
35+
},
36+
verticalAlign: {
37+
options: ['top', 'middle', 'bottom', 'baseline'],
38+
control: { type: 'select' },
39+
table: {
40+
type: { summary: 'top | bottom | middle | baseline' },
41+
},
42+
},
43+
propsFooterContent: {
44+
name: 'footerContent',
45+
description: 'Content centered in the full width cell of the table footer row. This prop will be overridden by the `\#footerContent` slot if it is provided.',
46+
table: {
47+
category: 'props',
48+
type: { summary: 'string' },
49+
},
50+
},
51+
footerContent: {
52+
description: 'Content centered in the full width cell of the table footer row. This slot will override the `footerContent` prop.',
53+
table: {
54+
type: { summary: null },
55+
},
56+
control: { disable: true },
57+
},
58+
'heading-0': {
59+
table: { disable: true },
60+
},
61+
'heading-[index]': {
62+
description: 'Slot to display each header as dynamic content. Example: `heading-0` will be the slot for the first column\'s heading.',
63+
table: { category: 'slots', type: { summary: null }},
64+
},
65+
'cell-[cellIndex]-row-[rowIndex]': {
66+
description: 'Slot to display each cell as dynamic content. Example: `cell-1-row-2` will be the slot for the second column in third row.',
67+
table: { category: 'slots', type: { summary: null }},
68+
},
69+
'totalsName-plural': {
70+
description: 'Custom totals row heading for plural. This slot will override the `totalsName.plural` prop.',
71+
table: { type: { summary: null }},
72+
control: { disable: true },
73+
},
74+
'totalsName-singular': {
75+
description: 'Custom totals row heading for singular. This slot will override the `totalsName.singular` prop.',
76+
table: { type: { summary: null }},
77+
control: { disable: true },
78+
},
79+
sort: {
80+
description: 'Callback fired on click or keypress of a sortable column heading.',
81+
control: { disable: true },
82+
table: {
83+
type: {
84+
summary: '(headingIndex: number, direction: SortDirection) => void',
85+
},
86+
},
87+
},
88+
}}
89+
/>
90+
91+
export const Template = (args) => ({
92+
components: { Card, DataTable, TextStyle, Link },
93+
setup() {
94+
const initiallySortedRows = [
95+
[
96+
{ url: 'https://google.com', key: 'emerald', content: 'Emerald Silk Gown'},
97+
'$875.00', 124689, 140, '$122,500.00',
98+
],
99+
[
100+
{ url: 'https://google.com', key: 'mauve', content: 'Mauve Cashmere Scarf'},
101+
'$230.00', 124533, 83, '$19,090.00',
102+
],
103+
[
104+
{ url: 'https://google.com', key: 'navy', content: 'Navy Merino Wool Blazer with khaki chinos and yellow belt'},
105+
'$445.00', 124518, 32, '$14,240.00',
106+
],
107+
];
108+
const sortedRows = ref(null);
109+
const rows = computed(() => {
110+
return sortedRows.value ? sortedRows.value : initiallySortedRows;
111+
});
112+
const handleSort = (index, direction) => {
113+
sortedRows.value = sortCurrency(rows.value, index, direction);
114+
}
115+
function sortCurrency(trows, index, direction) {
116+
return [...trows].sort((rowA, rowB) => {
117+
const amountA = parseFloat(rowA[index].substring(1));
118+
const amountB = parseFloat(rowB[index].substring(1));
119+
return direction === 'descending' ? amountB - amountA : amountA - amountB;
120+
});
121+
}
122+
return { args, rows, handleSort };
123+
},
124+
template: `<Card>
125+
<DataTable
126+
v-bind="args"
127+
:rows="rows"
128+
:footerContent="\`Showing \${rows.length} of \${rows.length} results\`"
129+
@sort="handleSort"
130+
>
131+
<template #heading-0>
132+
<TextStyle variation="strong">Product</TextStyle>
133+
</template>
134+
<template
135+
v-for="row, index in rows"
136+
:key="index"
137+
#[\`cell-0-row-\${index}\`]
138+
>
139+
<Link remove-underline :url="row[0].url">{{ row[0].content }}</Link>
140+
</template>
141+
<template
142+
v-for="row, index in rows"
143+
:key="index"
144+
#[\`cell-1-row-\${index}\`]
145+
>
146+
<TextStyle variation="positive">{{ row[1] }}</TextStyle>
147+
</template>
148+
</DataTable>
149+
</Card>`,
150+
});
151+
152+
# Data table
153+
154+
Data tables are used to organize and display all information from a data set. While a data visualization represents part of data set, a data table lets merchants view details from the entire set. This helps merchants compare and analyze the data.
155+
156+
<Canvas>
157+
<Story
158+
name="Data table"
159+
height="350px"
160+
args={{
161+
totalsName: { singular: 'Total net sales', plural: 'Total net sales' },
162+
columnContentTypes: [
163+
'text',
164+
'numeric',
165+
'numeric',
166+
'numeric',
167+
'numeric',
168+
],
169+
headings: [
170+
'Product',
171+
'Price',
172+
'SKU Number',
173+
'Net quantity',
174+
'Net sales',
175+
],
176+
totals: ['', '', '', 255, '$155,830.00'],
177+
sortable: [false, true, false, false, true],
178+
initialSortColumnIndex: 4,
179+
defaultSortDirection: 'descending',
180+
truncate: true,
181+
stickyHeader: true,
182+
hasFixedFirstColumn: true,
183+
}}
184+
parameters={{
185+
docs: {
186+
source: {
187+
state: 'close',
188+
code: dedent`
189+
<Card>
190+
<DataTable
191+
:totalsName="{singular: 'Total net sales', plural: 'Total net sales'}"
192+
:columnContentTypes="columnContentTypes"
193+
:headings="tableHeadings"
194+
:rows="rows"
195+
:totals="['', '', '', 255, '$155,830.00']"
196+
:sortable="[false, true, false, false, true]"
197+
:initialSortColumnIndex="4"
198+
defaultSortDirection="descending"
199+
:footerContent="\`Showing \${rows.length} of \${rows.length} results\`"
200+
:truncate="false"
201+
showTotalsInFooter
202+
stickyHeader
203+
hasFixedFirstColumn
204+
@sort="handleSort"
205+
>
206+
<template #heading-0>
207+
<TextStyle variation="strong">Product</TextStyle>
208+
</template>
209+
<template
210+
v-for="row, index in rows"
211+
:key="index"
212+
#[\`cell-0-row-\${index}\`]
213+
>
214+
<Link remove-underline :url="row[0].url">{{ row[0].content }}</Link>
215+
</template>
216+
<template
217+
v-for="row, index in rows"
218+
:key="index"
219+
#[\`cell-1-row-\${index}\`]
220+
>
221+
<TextStyle variation="positive">{{ row[1] }}</TextStyle>
222+
</template>
223+
</DataTable>
224+
</Card>\n
225+
<script setup>
226+
const tableHeadings = [
227+
'Product',
228+
'Price',
229+
'SKU Number',
230+
'Net quantity',
231+
'Net sales',
232+
];\n
233+
const columnContentTypes = [
234+
'text',
235+
'numeric',
236+
'numeric',
237+
'numeric',
238+
'numeric',
239+
];\n
240+
const initiallySortedRows = [
241+
[
242+
{ url: 'https://google.com', key: 'emerald', content: 'Emerald Silk Gown'},
243+
'$875.00', 124689, 140, '$122,500.00',
244+
],
245+
[
246+
{ url: 'https://google.com', key: 'mauve', content: 'Mauve Cashmere Scarf'},
247+
'$230.00', 124533, 83, '$19,090.00',
248+
],
249+
[
250+
{ url: 'https://google.com', key: 'navy', content: 'Navy Merino Wool Blazer with khaki chinos and yellow belt'},
251+
'$445.00', 124518, 32, '$14,240.00',
252+
],
253+
];\n
254+
const sortedRows = ref(null);
255+
const rows = computed(() => {
256+
return sortedRows.value ? sortedRows.value : initiallySortedRows;
257+
});\n
258+
const handleSort = (index, direction) => {
259+
sortedRows.value = sortCurrency(rows.value, index, direction);
260+
}\n
261+
function sortCurrency(trows, index, direction) {
262+
return [...trows].sort((rowA, rowB) => {
263+
const amountA = parseFloat(rowA[index].substring(1));
264+
const amountB = parseFloat(rowB[index].substring(1));
265+
return direction === 'descending' ? amountB - amountA : amountA - amountB;
266+
});
267+
}
268+
</script>
269+
`,
270+
},
271+
},
272+
}}
273+
>
274+
{Template.bind({})}
275+
</Story>
276+
</Canvas>
277+
278+
<ArgsTable story="Data table" />
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<template lang="pug">
2+
Cell(
3+
header,
4+
:setRef="setRef",
5+
:stickyHeadingCell="inStickyHeader",
6+
:contentType="columnContentTypes && columnContentTypes[headingIndex]",
7+
:firstColumn="headingIndex === 0",
8+
:truncate="truncate",
9+
v-bind="sortableHeadingProps",
10+
:verticalAlign="verticalAlign",
11+
:stickyCellWidth="stickyCellWidth",
12+
:fixedCellVisible="!isScrolledFarthestLeft",
13+
:firstColumnMinWidth="firstColumnMinWidth",
14+
:inFixedFirstColumn="inFixedFirstColumn",
15+
)
16+
slot
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { computed, useSlots } from 'vue';
21+
import type { ColumnContentType, SortDirection, VerticalAlign } from '../types';
22+
import { Cell } from '../components';
23+
24+
interface Props {
25+
headingIndex: number;
26+
inFixedFirstColumn: boolean;
27+
inStickyHeader: boolean;
28+
sortable?: boolean[];
29+
truncate: boolean;
30+
columnContentTypes?: ColumnContentType[];
31+
verticalAlign: VerticalAlign;
32+
firstColumnMinWidth?: string;
33+
sortDirection: SortDirection;
34+
defaultSortDirection: SortDirection;
35+
sortedColumnIndex: number;
36+
isScrolledFarthestLeft?: boolean;
37+
hasFixedFirstColumn?: boolean;
38+
stickyCellWidth?: number;
39+
setRef: (ref: any) => void;
40+
}
41+
42+
const props = withDefaults(defineProps<Props>(), {
43+
truncate: false,
44+
initialSortColumnIndex: 0,
45+
});
46+
47+
const slots = useSlots();
48+
49+
// const headingCellId = computed(() => `heading-cell-${props.headingIndex}`);
50+
// const stickyHeaderId = computed(() => `stickyheader-${props.headingIndex}`);
51+
// const id = computed(() => props.inStickyHeader ? stickyHeaderId.value : headingCellId.value);
52+
53+
const isSortable = computed(() => props.sortable && props.sortable[props.headingIndex]);
54+
const isSorted = computed(() => isSortable.value && props.sortedColumnIndex === props.headingIndex);
55+
const direction = computed(() => isSorted.value ? props.sortDirection : 'none');
56+
57+
const sortableHeadingProps = computed(() => {
58+
if (props.sortable) {
59+
return {
60+
defaultSortDirection: props.defaultSortDirection,
61+
sorted: isSorted.value,
62+
sortable: isSortable.value,
63+
sortDirection: direction.value,
64+
hasFixedFirstColumn: props.hasFixedFirstColumn,
65+
inFixedFirstColumn: props.hasFixedFirstColumn && props.inFixedFirstColumn,
66+
};
67+
}
68+
69+
return {};
70+
});
71+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as TableHeading } from './Heading.vue';

0 commit comments

Comments
 (0)