Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
feat(DataTable): add items per page global settings
Browse files Browse the repository at this point in the history
  • Loading branch information
kelsos committed Dec 20, 2023
1 parent fd5c620 commit 18b9a7b
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 6 deletions.
5 changes: 4 additions & 1 deletion .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"useClipboardItems": true,
"useStickyTableHeader": true
"useStickyTableHeader": true,
"TableSymbol": true,
"createTableDefaults": true,
"useTable": true
}
}
18 changes: 17 additions & 1 deletion example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import '@/assets/main.css';
import '@rotki/ui-library-compat/style.css';
import '@fontsource/roboto/latin.css';

import { PiniaVuePlugin, createPinia, setActivePinia } from 'pinia';
import {
PiniaVuePlugin,
createPinia,
setActivePinia,
storeToRefs,
} from 'pinia';
import {
RiAddFill,
RiAlertLine,
Expand Down Expand Up @@ -30,11 +35,14 @@ import {
import Vue from 'vue';
import App from '@/App.vue';
import router from '@/router';
import { useCounterStore } from '@/stores/counter';

Vue.use(PiniaVuePlugin);
const pinia = createPinia();
setActivePinia(pinia);

const { itemsPerPage } = storeToRefs(useCounterStore());

const RuiPlugin = createRui({
theme: {
icons: [
Expand All @@ -61,12 +69,20 @@ const RuiPlugin = createRui({
RiArrowDownCircleLine,
],
},
defaults: {
table: {
itemsPerPage,
},
},
});
Vue.use(RuiPlugin);

new Vue({
// @ts-ignore
pinia,
router,
setup() {
RuiPlugin.setupProvide();
},
render: (h) => h(App),
}).$mount('#app');
4 changes: 3 additions & 1 deletion example/src/stores/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ export const useCounterStore = defineStore('counter', () => {
count.value++;
}

return { count, doubleCount, increment };
const itemsPerPage = ref(15);

return { count, doubleCount, increment, itemsPerPage };
});
163 changes: 161 additions & 2 deletions src/components/tables/DataTable.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
/* eslint-disable max-lines */
import { describe, expect, it } from 'vitest';
import { mount } from '@vue/test-utils';
import DataTable, { type TableColumn } from '@/components/tables/DataTable.vue';

const createWrapper = (options?: any) => mount(DataTable, options);
import TablePagination from '@/components/tables/TablePagination.vue';
import { RuiSimpleSelect } from '~/src';

const createWrapper = (options?: any) =>
mount(DataTable, {
...options,
provide: {
[TableSymbol.valueOf()]: {
itemsPerPage: ref(10),
globalItemsPerPage: get(false),
},
},
});

describe('DataTable', () => {
const data = [
Expand Down Expand Up @@ -303,4 +315,151 @@ describe('DataTable', () => {

expect(wrapper.find('thead[data-id=head-clone]').exists()).toBeFalsy();
});

describe('global settings', () => {
it('should follow global settings', async () => {
const itemsPerPage = ref(25);
const wrapperComponent = {
template:
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :rows='[]' row-attr='id'/></div>",
};

const wrapper = mount(wrapperComponent, {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
globalItemsPerPage: true,
}),
},
});

await nextTick();

const paginate = wrapper.findAllComponents(TablePagination);
expect(paginate).toHaveLength(2);

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);
expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

const select = paginate.at(0).findComponent(RuiSimpleSelect);
select.vm.$emit('input', 10);

await nextTick();

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(get(itemsPerPage)).toBe(10);
});

it('should respect local setting override', async () => {
const itemsPerPage = ref(25);
const wrapperComponent = {
template:
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :globalItemsPerPage='false' :rows='[]' row-attr='id'/></div>",
};

const wrapper = mount(wrapperComponent, {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
globalItemsPerPage: true,
}),
},
});

await nextTick();

const paginate = wrapper.findAllComponents(TablePagination);
expect(paginate).toHaveLength(2);

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);
expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

const globalSelect = paginate.at(0).findComponent(RuiSimpleSelect);
const localSelect = paginate.at(1).findComponent(RuiSimpleSelect);
globalSelect.vm.$emit('input', 10);
localSelect.vm.$emit('input', 25);

await nextTick();

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

expect(get(itemsPerPage)).toBe(10);
});

it('should follow single global setting', async () => {
const itemsPerPage = ref(25);
const wrapperComponent = {
template:
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :globalItemsPerPage='true' :rows='[]' row-attr='id'/></div>",
};

const wrapper = mount(wrapperComponent, {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
}),
},
});

await nextTick();

const paginate = wrapper.findAllComponents(TablePagination);
expect(paginate).toHaveLength(2);

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

const globalSelect = paginate.at(0).findComponent(RuiSimpleSelect);
const localSelect = paginate.at(1).findComponent(RuiSimpleSelect);
globalSelect.vm.$emit('input', 25);
localSelect.vm.$emit('input', 10);

await nextTick();

expect(paginate.at(0).vm.value).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

expect(paginate.at(1).vm.value).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(get(itemsPerPage)).toBe(10);
});
});
});
42 changes: 42 additions & 0 deletions src/components/tables/DataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export interface Props {
*/
stickyHeader?: boolean;
stickyOffset?: number;
/**
* Affects how the items per page work across the app.
* When true, changing the items per page setting in one table will affect other tables.
*/
globalItemsPerPage?: boolean;
}
defineOptions({
Expand Down Expand Up @@ -165,6 +170,7 @@ const props = withDefaults(defineProps<Props>(), {
singleExpand: false,
stickyHeader: false,
stickyOffset: 0,
globalItemsPerPage: undefined,
});
const emit = defineEmits<{
Expand Down Expand Up @@ -195,6 +201,13 @@ const {
const css = useCssModule();
const { stick, table, tableScroller } = useStickyTableHeader(stickyOffset);
const tableDefaults = useTable();
const globalItemsPerPageSettings = computed(() => {
if (props.globalItemsPerPage !== undefined) {
return props.globalItemsPerPage;
}
return get(tableDefaults.globalItemsPerPage);
});
/**
* Prepare the columns from props or generate using first item in the list
Expand Down Expand Up @@ -239,6 +252,25 @@ watchImmediate(pagination, (pagination) => {
const expandable = computed(() => get(expanded) && slots['expanded-item']);
/**
* Keeps the global items per page in sync with the internal state.
*/
watch(internalPaginationState, (pagination) => {
if (pagination?.limit && get(globalItemsPerPageSettings)) {
set(tableDefaults.itemsPerPage, pagination.limit);
}
});
watch(tableDefaults.itemsPerPage, (itemsPerPage) => {
if (!get(globalItemsPerPageSettings)) {
return;
}
set(paginationData, {
...get(paginationData),
limit: itemsPerPage,
});
});
/**
* Pagination is different for search
* since search is only used for internal filtering
Expand Down Expand Up @@ -596,6 +628,16 @@ watch(search, () => {
}
});
onMounted(() => {
if (!get(globalItemsPerPageSettings)) {
return;
}
set(paginationData, {
...get(paginationData),
limit: get(tableDefaults.itemsPerPage),
});
});
const slots = useSlots();
</script>

Expand Down
27 changes: 27 additions & 0 deletions src/composables/defaults/table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { type InjectionKey, type Ref } from 'vue';
import { type MaybeRef } from '@vueuse/shared';

export interface TableOptions {
itemsPerPage: Ref<number>;
globalItemsPerPage: MaybeRef<boolean>;
}

export const TableSymbol: InjectionKey<TableOptions> = Symbol.for('rui:table');

export const createTableDefaults = (
options?: Partial<TableOptions>,
): TableOptions => ({
itemsPerPage: ref(10),
globalItemsPerPage: false,
...options,
});

export const useTable = () => {
const options = inject(TableSymbol);

if (!options) {
throw new Error('Could not find rui table options injection');
}

return options;
};
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
/* eslint-disable max-lines,import/max-dependencies */
import Vue, { type VueConstructor } from 'vue';
import Vue, { type VueConstructor, provide } from 'vue';
import { isClient } from '@vueuse/core';
import { StepperState } from '@/types/stepper';
import { createTeleport } from '@/components/overlays/teleport-container';
import {
type TableOptions,
TableSymbol,
createTableDefaults,
} from '@/composables/defaults/table';
import type { InitThemeOptions } from '@/types/theme';
import '@/style.scss';

Expand All @@ -25,6 +30,9 @@ export { StepperState };

export interface RuiOptions {
theme?: InitThemeOptions;
defaults?: {
table?: TableOptions;
};
}

const installTeleport = () => {
Expand All @@ -50,7 +58,13 @@ export function createRui(options: RuiOptions = {}) {
installTeleport();
};

const setupProvide = () => {
const tableDefaults = createTableDefaults(options.defaults?.table);
provide(TableSymbol, tableDefaults);
};

return {
install,
setupProvide,
};
}

0 comments on commit 18b9a7b

Please sign in to comment.