-
Notifications
You must be signed in to change notification settings - Fork 8.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add basic Datatable and Pagination components (#5652)
* feat: add Datatable component * feat: migrate to n8n-pagination and add datatable tests * chore: fix linting issue
- Loading branch information
1 parent
b4e60c3
commit 29f2629
Showing
16 changed files
with
523 additions
and
0 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
packages/design-system/src/components/N8nDatatable/Datatable.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import N8nDatatable from './Datatable.vue'; | ||
import type { StoryFn } from '@storybook/vue'; | ||
import { rows, columns } from './__tests__/data'; | ||
|
||
export default { | ||
title: 'Atoms/Datatable', | ||
component: N8nDatatable, | ||
}; | ||
|
||
export const Default: StoryFn = (args, { argTypes }) => ({ | ||
props: Object.keys(argTypes), | ||
components: { | ||
N8nDatatable, | ||
}, | ||
template: '<n8n-datatable v-bind="$props"></n8n-datatable>', | ||
}); | ||
|
||
Default.args = { | ||
columns, | ||
rows, | ||
}; |
200 changes: 200 additions & 0 deletions
200
packages/design-system/src/components/N8nDatatable/Datatable.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
<script lang="ts"> | ||
import { computed, defineComponent, PropType, ref, useCssModule } from 'vue'; | ||
import { | ||
DatatableColumn, | ||
DatatableRow, | ||
DatatableRowDataType, | ||
} from '@/components/N8nDatatable/mixins'; | ||
import { getValueByPath } from '../../utils'; | ||
import { useI18n } from '../../composables'; | ||
import N8nSelect from '../N8nSelect'; | ||
import N8nOption from '../N8nOption'; | ||
import N8nPagination from '../N8nPagination'; | ||
export default defineComponent({ | ||
name: 'n8n-datatable', | ||
components: { | ||
N8nSelect, | ||
N8nOption, | ||
N8nPagination, | ||
}, | ||
props: { | ||
columns: { | ||
type: Array as PropType<DatatableColumn[]>, | ||
required: true, | ||
}, | ||
rows: { | ||
type: Array as PropType<DatatableRow[]>, | ||
required: true, | ||
}, | ||
pagination: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
rowsPerPage: { | ||
type: Number, | ||
default: 10, | ||
}, | ||
}, | ||
setup(props) { | ||
const { t } = useI18n(); | ||
const rowsPerPageOptions = ref([10, 25, 50, 100]); | ||
const style = useCssModule(); | ||
const currentPage = ref(1); | ||
const currentRowsPerPage = ref(props.rowsPerPage); | ||
const totalPages = computed(() => { | ||
return Math.ceil(props.rows.length / currentRowsPerPage.value); | ||
}); | ||
const totalRows = computed(() => { | ||
return props.rows.length; | ||
}); | ||
const visibleRows = computed(() => { | ||
const start = (currentPage.value - 1) * currentRowsPerPage.value; | ||
const end = start + currentRowsPerPage.value; | ||
return props.rows.slice(start, end); | ||
}); | ||
const classes = computed(() => { | ||
return { | ||
datatable: true, | ||
[style.datatableWrapper]: true, | ||
}; | ||
}); | ||
function getTrClass() { | ||
return { | ||
[style.datatableRow]: true, | ||
}; | ||
} | ||
function onRowsPerPageChange(value: number) { | ||
currentRowsPerPage.value = value; | ||
const maxPage = Math.ceil(totalRows.value / currentRowsPerPage.value); | ||
if (maxPage < currentPage.value) { | ||
currentPage.value = maxPage; | ||
} | ||
} | ||
function getTdValue(row: DatatableRow, column: DatatableColumn) { | ||
return getValueByPath<DatatableRowDataType>(row, column.path); | ||
} | ||
return { | ||
t, | ||
classes, | ||
currentPage, | ||
totalPages, | ||
totalRows, | ||
visibleRows, | ||
currentRowsPerPage, | ||
rowsPerPageOptions, | ||
getTdValue, | ||
getTrClass, | ||
onRowsPerPageChange, | ||
}; | ||
}, | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div :class="classes" v-on="$listeners"> | ||
<table :class="$style.datatable"> | ||
<thead :class="$style.datatableHeader"> | ||
<tr> | ||
<th v-for="column in columns" :key="column.id"> | ||
{{ column.label }} | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr v-for="row in visibleRows" :key="row.id" :class="getTrClass(row)"> | ||
<td v-for="column in columns" :key="column.id"> | ||
<component v-if="column.render" :is="column.render" :row="row" :column="column" /> | ||
<span v-else>{{ getTdValue(row, column) }}</span> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
<div :class="$style.pagination"> | ||
<n8n-pagination | ||
v-if="totalPages > 1" | ||
background | ||
:current-page.sync="currentPage" | ||
:pager-count="5" | ||
:page-size="currentRowsPerPage" | ||
layout="prev, pager, next" | ||
:total="totalRows" | ||
/> | ||
|
||
<div :class="$style.pageSizeSelector"> | ||
<n8n-select | ||
size="mini" | ||
:value="currentRowsPerPage" | ||
@input="onRowsPerPageChange" | ||
popper-append-to-body | ||
> | ||
<template #prepend>{{ t('datatable.pageSize') }}</template> | ||
<n8n-option v-for="size in rowsPerPageOptions" :key="size" :label="size" :value="size" /> | ||
<n8n-option :label="`All`" :value="totalRows"> </n8n-option> | ||
</n8n-select> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style lang="scss" module> | ||
.datatableWrapper { | ||
display: block; | ||
width: 100%; | ||
} | ||
.datatable { | ||
width: 100%; | ||
} | ||
.datatableHeader { | ||
background: var(--color-background-base); | ||
th { | ||
text-align: left; | ||
padding: var(--spacing-s) var(--spacing-2xs); | ||
} | ||
} | ||
.datatableRow { | ||
td { | ||
color: var(--color-text-base); | ||
padding: var(--spacing-s) var(--spacing-2xs); | ||
} | ||
&:nth-of-type(even) { | ||
background: var(--color-background-xlight); | ||
} | ||
&:nth-of-type(odd) { | ||
background: var(--color-background-light); | ||
} | ||
} | ||
.pagination { | ||
width: 100%; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
bottom: 0; | ||
overflow: auto; | ||
margin-top: var(--spacing-s); | ||
} | ||
.pageSizeSelector { | ||
text-transform: capitalize; | ||
max-width: 150px; | ||
flex: 0 1 auto; | ||
} | ||
</style> |
23 changes: 23 additions & 0 deletions
23
packages/design-system/src/components/N8nDatatable/__tests__/Datatable.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { render } from '@testing-library/vue'; | ||
import N8nDatatable from '../Datatable.vue'; | ||
import { rows, columns } from './data'; | ||
|
||
const stubs = ['n8n-select', 'n8n-option', 'n8n-button', 'n8n-pagination']; | ||
|
||
describe('components', () => { | ||
describe('N8nDatatable', () => { | ||
it('should render correctly', () => { | ||
const wrapper = render(N8nDatatable, { | ||
propsData: { | ||
columns, | ||
rows, | ||
}, | ||
stubs, | ||
}); | ||
|
||
expect(wrapper.container.querySelectorAll('tbody tr').length).toEqual(10); | ||
expect(wrapper.container.querySelectorAll('thead tr').length).toEqual(1); | ||
expect(wrapper.html()).toMatchSnapshot(); | ||
}); | ||
}); | ||
}); |
100 changes: 100 additions & 0 deletions
100
.../design-system/src/components/N8nDatatable/__tests__/__snapshots__/Datatable.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Vitest Snapshot v1 | ||
|
||
exports[`components > N8nDatatable > should render correctly 1`] = ` | ||
"<div class=\\"datatable datatableWrapper\\"> | ||
<table class=\\"datatable\\"> | ||
<thead class=\\"datatableHeader\\"> | ||
<tr> | ||
<th> ID </th> | ||
<th> Name </th> | ||
<th> Age </th> | ||
<th> Action </th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>1</span></td> | ||
<td><span>Richard Hendricks</span></td> | ||
<td><span>29</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 1</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>2</span></td> | ||
<td><span>Bertram Gilfoyle</span></td> | ||
<td><span>44</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 2</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>3</span></td> | ||
<td><span>Dinesh Chugtai</span></td> | ||
<td><span>31</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 3</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>4</span></td> | ||
<td><span>Jared Dunn </span></td> | ||
<td><span>38</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 4</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>5</span></td> | ||
<td><span>Richard Hendricks</span></td> | ||
<td><span>29</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 5</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>6</span></td> | ||
<td><span>Bertram Gilfoyle</span></td> | ||
<td><span>44</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 6</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>7</span></td> | ||
<td><span>Dinesh Chugtai</span></td> | ||
<td><span>31</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 7</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>8</span></td> | ||
<td><span>Jared Dunn </span></td> | ||
<td><span>38</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 8</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>9</span></td> | ||
<td><span>Richard Hendricks</span></td> | ||
<td><span>29</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 9</span></button></td> | ||
</tr> | ||
<tr class=\\"datatableRow\\"> | ||
<td><span>10</span></td> | ||
<td><span>Bertram Gilfoyle</span></td> | ||
<td><span>44</span></td> | ||
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\"> | ||
<!----><span>Button 10</span></button></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<div class=\\"pagination\\"> | ||
<n8n-pagination-stub pagesize=\\"10\\" total=\\"15\\" pagercount=\\"5\\" currentpage=\\"1\\" layout=\\"prev, pager, next\\" pagesizes=\\"10,20,30,40,50,100\\" background=\\"true\\"></n8n-pagination-stub> | ||
<div class=\\"pageSizeSelector\\"> | ||
<n8n-select-stub value=\\"10\\" size=\\"mini\\" popperappendtobody=\\"true\\"> | ||
<n8n-option-stub value=\\"10\\" label=\\"10\\"></n8n-option-stub> | ||
<n8n-option-stub value=\\"25\\" label=\\"25\\"></n8n-option-stub> | ||
<n8n-option-stub value=\\"50\\" label=\\"50\\"></n8n-option-stub> | ||
<n8n-option-stub value=\\"100\\" label=\\"100\\"></n8n-option-stub> | ||
<n8n-option-stub value=\\"15\\" label=\\"All\\"></n8n-option-stub> | ||
</n8n-select-stub> | ||
</div> | ||
</div> | ||
</div>" | ||
`; |
45 changes: 45 additions & 0 deletions
45
packages/design-system/src/components/N8nDatatable/__tests__/data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { defineComponent, h, PropType } from 'vue'; | ||
import { DatatableRow } from '../mixins'; | ||
import N8nButton from '../../N8nButton'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
export const ActionComponent = defineComponent({ | ||
props: { | ||
row: { | ||
type: Object as PropType<DatatableRow>, | ||
default: () => ({}), | ||
}, | ||
}, | ||
setup(props) { | ||
return () => h(N8nButton, {}, [`Button ${props.row.id}`]); | ||
}, | ||
}); | ||
|
||
export const columns = [ | ||
{ id: 'id', path: 'id', label: 'ID' }, | ||
{ id: 'name', path: 'name', label: 'Name' }, | ||
{ id: 'age', path: 'meta.age', label: 'Age' }, | ||
{ | ||
id: 'action', | ||
label: 'Action', | ||
render: ActionComponent, | ||
}, | ||
]; | ||
|
||
export const rows = [ | ||
{ id: 1, name: 'Richard Hendricks', meta: { age: 29 } }, | ||
{ id: 2, name: 'Bertram Gilfoyle', meta: { age: 44 } }, | ||
{ id: 3, name: 'Dinesh Chugtai', meta: { age: 31 } }, | ||
{ id: 4, name: 'Jared Dunn ', meta: { age: 38 } }, | ||
{ id: 5, name: 'Richard Hendricks', meta: { age: 29 } }, | ||
{ id: 6, name: 'Bertram Gilfoyle', meta: { age: 44 } }, | ||
{ id: 7, name: 'Dinesh Chugtai', meta: { age: 31 } }, | ||
{ id: 8, name: 'Jared Dunn ', meta: { age: 38 } }, | ||
{ id: 9, name: 'Richard Hendricks', meta: { age: 29 } }, | ||
{ id: 10, name: 'Bertram Gilfoyle', meta: { age: 44 } }, | ||
{ id: 11, name: 'Dinesh Chugtai', meta: { age: 31 } }, | ||
{ id: 12, name: 'Jared Dunn ', meta: { age: 38 } }, | ||
{ id: 13, name: 'Richard Hendricks', meta: { age: 29 } }, | ||
{ id: 14, name: 'Bertram Gilfoyle', meta: { age: 44 } }, | ||
{ id: 15, name: 'Dinesh Chugtai', meta: { age: 31 } }, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import N8nDatatable from './Datatable.vue'; | ||
|
||
export default N8nDatatable; |
Oops, something went wrong.