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

Commit 9668cf3

Browse files
committed
feat(DataTable): add items per page global settings
1 parent 4363392 commit 9668cf3

File tree

7 files changed

+267
-5
lines changed

7 files changed

+267
-5
lines changed

.eslintrc-auto-import.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@
334334
"ExtractDefaultPropTypes": true,
335335
"ExtractPropTypes": true,
336336
"ExtractPublicPropTypes": true,
337-
"useClipboardItems": true
337+
"useClipboardItems": true,
338+
"TableSymbol": true,
339+
"createTableDefaults": true,
340+
"useTable": true
338341
}
339342
}

example/src/main.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import '@/assets/main.css';
22
import '@rotki/ui-library-compat/style.css';
33
import '@fontsource/roboto/latin.css';
44

5-
import { PiniaVuePlugin, createPinia, setActivePinia } from 'pinia';
5+
import {
6+
PiniaVuePlugin,
7+
createPinia,
8+
setActivePinia,
9+
storeToRefs,
10+
} from 'pinia';
611
import {
712
RiAddFill,
813
RiAlertLine,
@@ -29,11 +34,14 @@ import {
2934
import Vue from 'vue';
3035
import App from '@/App.vue';
3136
import router from '@/router';
37+
import { useCounterStore } from '@/stores/counter';
3238

3339
Vue.use(PiniaVuePlugin);
3440
const pinia = createPinia();
3541
setActivePinia(pinia);
3642

43+
const { itemsPerPage } = storeToRefs(useCounterStore());
44+
3745
const RuiPlugin = createRui({
3846
theme: {
3947
icons: [
@@ -59,12 +67,20 @@ const RuiPlugin = createRui({
5967
RiArrowDownSLine,
6068
],
6169
},
70+
defaults: {
71+
table: {
72+
itemsPerPage,
73+
},
74+
},
6275
});
6376
Vue.use(RuiPlugin);
6477

6578
new Vue({
6679
// @ts-ignore
6780
pinia,
6881
router,
82+
setup() {
83+
RuiPlugin.setupProvide();
84+
},
6985
render: (h) => h(App),
7086
}).$mount('#app');

example/src/stores/counter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ export const useCounterStore = defineStore('counter', () => {
99
count.value++;
1010
}
1111

12-
return { count, doubleCount, increment };
12+
const itemsPerPage = ref(15);
13+
14+
return { count, doubleCount, increment, itemsPerPage };
1315
});

src/components/tables/DataTable.spec.ts

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import { describe, expect, it } from 'vitest';
22
import { mount } from '@vue/test-utils';
33
import DataTable, { type TableColumn } from '@/components/tables/DataTable.vue';
4+
import TablePagination from '@/components/tables/TablePagination.vue';
5+
import { RuiSimpleSelect } from '~/src';
46

5-
const createWrapper = (options?: any) => mount(DataTable, options);
7+
const createWrapper = (options?: any) =>
8+
mount(DataTable, {
9+
...options,
10+
provide: {
11+
[TableSymbol.valueOf()]: {
12+
itemsPerPage: ref(10),
13+
globalItemsPerPage: get(false),
14+
},
15+
},
16+
});
617

718
describe('DataTable', () => {
819
const data = [
@@ -92,4 +103,151 @@ describe('DataTable', () => {
92103
wrapper.find('div div[class*=_navigation_] button[disabled]').exists(),
93104
).toBeTruthy();
94105
});
106+
107+
describe('global settings', () => {
108+
it('should follow global settings', async () => {
109+
const itemsPerPage = ref(25);
110+
const wrapperComponent = {
111+
template:
112+
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :rows='[]' row-attr='id'/></div>",
113+
};
114+
115+
const wrapper = mount(wrapperComponent, {
116+
components: {
117+
DataTable,
118+
},
119+
provide: {
120+
[TableSymbol.valueOf()]: createTableDefaults({
121+
itemsPerPage,
122+
globalItemsPerPage: true,
123+
}),
124+
},
125+
});
126+
127+
await nextTick();
128+
129+
const paginate = wrapper.findAllComponents(TablePagination);
130+
expect(paginate).toHaveLength(2);
131+
132+
expect(paginate.at(0).vm.value).toMatchObject(
133+
expect.objectContaining({ limit: 25 }),
134+
);
135+
expect(paginate.at(1).vm.value).toMatchObject(
136+
expect.objectContaining({ limit: 25 }),
137+
);
138+
139+
const select = paginate.at(0).findComponent(RuiSimpleSelect);
140+
select.vm.$emit('input', 10);
141+
142+
await nextTick();
143+
144+
expect(paginate.at(0).vm.value).toMatchObject(
145+
expect.objectContaining({ limit: 10 }),
146+
);
147+
148+
expect(paginate.at(1).vm.value).toMatchObject(
149+
expect.objectContaining({ limit: 10 }),
150+
);
151+
152+
expect(get(itemsPerPage)).toBe(10);
153+
});
154+
155+
it('should respect local setting override', async () => {
156+
const itemsPerPage = ref(25);
157+
const wrapperComponent = {
158+
template:
159+
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :globalItemsPerPage='false' :rows='[]' row-attr='id'/></div>",
160+
};
161+
162+
const wrapper = mount(wrapperComponent, {
163+
components: {
164+
DataTable,
165+
},
166+
provide: {
167+
[TableSymbol.valueOf()]: createTableDefaults({
168+
itemsPerPage,
169+
globalItemsPerPage: true,
170+
}),
171+
},
172+
});
173+
174+
await nextTick();
175+
176+
const paginate = wrapper.findAllComponents(TablePagination);
177+
expect(paginate).toHaveLength(2);
178+
179+
expect(paginate.at(0).vm.value).toMatchObject(
180+
expect.objectContaining({ limit: 25 }),
181+
);
182+
expect(paginate.at(1).vm.value).toMatchObject(
183+
expect.objectContaining({ limit: 10 }),
184+
);
185+
186+
const globalSelect = paginate.at(0).findComponent(RuiSimpleSelect);
187+
const localSelect = paginate.at(1).findComponent(RuiSimpleSelect);
188+
globalSelect.vm.$emit('input', 10);
189+
localSelect.vm.$emit('input', 25);
190+
191+
await nextTick();
192+
193+
expect(paginate.at(0).vm.value).toMatchObject(
194+
expect.objectContaining({ limit: 10 }),
195+
);
196+
197+
expect(paginate.at(1).vm.value).toMatchObject(
198+
expect.objectContaining({ limit: 25 }),
199+
);
200+
201+
expect(get(itemsPerPage)).toBe(10);
202+
});
203+
204+
it('should follow single global setting', async () => {
205+
const itemsPerPage = ref(25);
206+
const wrapperComponent = {
207+
template:
208+
"<div><DataTable :rows='[]' row-attr='id'/><DataTable :globalItemsPerPage='true' :rows='[]' row-attr='id'/></div>",
209+
};
210+
211+
const wrapper = mount(wrapperComponent, {
212+
components: {
213+
DataTable,
214+
},
215+
provide: {
216+
[TableSymbol.valueOf()]: createTableDefaults({
217+
itemsPerPage,
218+
}),
219+
},
220+
});
221+
222+
await nextTick();
223+
224+
const paginate = wrapper.findAllComponents(TablePagination);
225+
expect(paginate).toHaveLength(2);
226+
227+
expect(paginate.at(0).vm.value).toMatchObject(
228+
expect.objectContaining({ limit: 10 }),
229+
);
230+
231+
expect(paginate.at(1).vm.value).toMatchObject(
232+
expect.objectContaining({ limit: 25 }),
233+
);
234+
235+
const globalSelect = paginate.at(0).findComponent(RuiSimpleSelect);
236+
const localSelect = paginate.at(1).findComponent(RuiSimpleSelect);
237+
globalSelect.vm.$emit('input', 25);
238+
localSelect.vm.$emit('input', 10);
239+
240+
await nextTick();
241+
242+
expect(paginate.at(0).vm.value).toMatchObject(
243+
expect.objectContaining({ limit: 25 }),
244+
);
245+
246+
expect(paginate.at(1).vm.value).toMatchObject(
247+
expect.objectContaining({ limit: 10 }),
248+
);
249+
250+
expect(get(itemsPerPage)).toBe(10);
251+
});
252+
});
95253
});

src/components/tables/DataTable.vue

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ export interface Props {
122122
* should add zebra-striping to the table row
123123
*/
124124
striped?: boolean;
125+
/**
126+
* Affects how the items per page work across the app.
127+
* When true, changing the items per page setting in one table will affect other tables.
128+
*/
129+
globalItemsPerPage?: boolean;
125130
}
126131
127132
defineOptions({
@@ -145,6 +150,7 @@ const props = withDefaults(defineProps<Props>(), {
145150
rounded: 'md',
146151
hideDefaultFooter: false,
147152
striped: false,
153+
globalItemsPerPage: undefined,
148154
});
149155
150156
const emit = defineEmits<{
@@ -170,6 +176,13 @@ const {
170176
} = toRefs(props);
171177
172178
const css = useCssModule();
179+
const tableDefaults = useTable();
180+
const globalItemsPerPageSettings = computed(() => {
181+
if (props.globalItemsPerPage !== undefined) {
182+
return props.globalItemsPerPage;
183+
}
184+
return get(tableDefaults.globalItemsPerPage);
185+
});
173186
174187
/**
175188
* Prepare the columns from props or generate using first item in the list
@@ -200,6 +213,25 @@ watchImmediate(pagination, (pagination) => {
200213
set(internalPaginationState, pagination);
201214
});
202215
216+
/**
217+
* Keeps the global items per page in sync with the internal state.
218+
*/
219+
watch(internalPaginationState, (pagination) => {
220+
if (pagination?.limit && get(globalItemsPerPageSettings)) {
221+
set(tableDefaults.itemsPerPage, pagination.limit);
222+
}
223+
});
224+
225+
watch(tableDefaults.itemsPerPage, (itemsPerPage) => {
226+
if (!get(globalItemsPerPageSettings)) {
227+
return;
228+
}
229+
set(paginationData, {
230+
...get(paginationData),
231+
limit: itemsPerPage,
232+
});
233+
});
234+
203235
/**
204236
* Pagination is different for search
205237
* since search is only used for internal filtering
@@ -518,6 +550,16 @@ watch(search, () => {
518550
}
519551
});
520552
553+
onMounted(() => {
554+
if (!get(globalItemsPerPageSettings)) {
555+
return;
556+
}
557+
set(paginationData, {
558+
...get(paginationData),
559+
limit: get(tableDefaults.itemsPerPage),
560+
});
561+
});
562+
521563
const slots = useSlots();
522564
</script>
523565

src/composables/defaults/table.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type InjectionKey, type Ref } from 'vue';
2+
import { type MaybeRef } from '@vueuse/shared';
3+
4+
export interface TableOptions {
5+
itemsPerPage: Ref<number>;
6+
globalItemsPerPage: MaybeRef<boolean>;
7+
}
8+
9+
export const TableSymbol: InjectionKey<TableOptions> = Symbol.for('rui:table');
10+
11+
export const createTableDefaults = (
12+
options?: Partial<TableOptions>,
13+
): TableOptions => ({
14+
itemsPerPage: ref(10),
15+
globalItemsPerPage: false,
16+
...options,
17+
});
18+
19+
export const useTable = () => {
20+
const options = inject(TableSymbol);
21+
22+
if (!options) {
23+
throw new Error('Could not find rui table options injection');
24+
}
25+
26+
return options;
27+
};

src/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
/* eslint-disable max-lines,import/max-dependencies */
2-
import Vue, { type VueConstructor } from 'vue';
2+
import Vue, { type VueConstructor, provide } from 'vue';
33
import { isClient } from '@vueuse/core';
44
import { StepperState } from '@/types/stepper';
55
import { createTeleport } from '@/components/overlays/teleport-container';
6+
import {
7+
type TableOptions,
8+
TableSymbol,
9+
createTableDefaults,
10+
} from '@/composables/defaults/table';
611
import type { InitThemeOptions } from '@/types/theme';
712
import '@/style.scss';
813

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

2631
export interface RuiOptions {
2732
theme?: InitThemeOptions;
33+
defaults?: {
34+
table?: TableOptions;
35+
};
2836
}
2937

3038
const installTeleport = () => {
@@ -50,7 +58,13 @@ export function createRui(options: RuiOptions = {}) {
5058
installTeleport();
5159
};
5260

61+
const setupProvide = () => {
62+
const tableDefaults = createTableDefaults(options.defaults?.table);
63+
provide(TableSymbol, tableDefaults);
64+
};
65+
5366
return {
5467
install,
68+
setupProvide,
5569
};
5670
}

0 commit comments

Comments
 (0)