Skip to content

Commit

Permalink
feat(DataTable): introduce global settings for table
Browse files Browse the repository at this point in the history
  • Loading branch information
kelsos committed Dec 19, 2023
1 parent e10c358 commit 1da38ad
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 3 deletions.
5 changes: 4 additions & 1 deletion .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true
"whenever": true,
"TableSymbol": true,
"createTableDefaults": true,
"useTable": true
}
}
163 changes: 162 additions & 1 deletion src/components/tables/DataTable.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable max-lines */
import { describe, expect, it } from 'vitest';
import { type ComponentMountingOptions, mount } from '@vue/test-utils';
import DataTable, { type TableColumn } from '@/components/tables/DataTable.vue';
import TablePagination from '@/components/tables/TablePagination.vue';
import { RuiSimpleSelect } from '~/src';

type User = {
id: number;
Expand All @@ -11,7 +14,15 @@ type User = {

const createWrapper = (
options?: ComponentMountingOptions<typeof DataTable<User>>,
) => mount(DataTable<User>, options);
) =>
mount(DataTable<User>, {
...options,
global: {
provide: {
[TableSymbol.valueOf()]: createTableDefaults(),
},
},
});

describe('DataTable', () => {
const data: User[] = [
Expand Down Expand Up @@ -298,4 +309,154 @@ 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, {
global: {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
globalItemsPerPage: true,
}),
},
},
});

await nextTick();

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

paginationInstances.forEach((instance) => {
expect(instance.vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 25 }),
);
});

const select = paginationInstances[0].findComponent(RuiSimpleSelect);
select.vm.$emit('update:model-value', 10);

await nextTick();

paginationInstances.forEach((instance) => {
expect(instance.vm.modelValue).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, {
global: {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
globalItemsPerPage: true,
}),
},
},
});

await nextTick();

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

expect(paginate[0].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 25 }),
);
expect(paginate[1].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

const globalSelect = paginate[0].findComponent(RuiSimpleSelect);
const localSelect = paginate[1].findComponent(RuiSimpleSelect);
globalSelect.vm.$emit('update:model-value', 10);
localSelect.vm.$emit('update:model-value', 25);

await nextTick();

expect(paginate[0].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(paginate[1].vm.modelValue).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, {
global: {
components: {
DataTable,
},
provide: {
[TableSymbol.valueOf()]: createTableDefaults({
itemsPerPage,
}),
},
},
});

await nextTick();

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

expect(paginate[0].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(paginate[1].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

const globalSelect = paginate[0].findComponent(RuiSimpleSelect);
const localSelect = paginate[1].findComponent(RuiSimpleSelect);
globalSelect.vm.$emit('update:model-value', 25);
localSelect.vm.$emit('update:model-value', 10);

await nextTick();

expect(paginate[0].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 25 }),
);

expect(paginate[1].vm.modelValue).toMatchObject(
expect.objectContaining({ limit: 10 }),
);

expect(get(itemsPerPage)).toBe(10);
});
});
});
44 changes: 44 additions & 0 deletions src/components/tables/DataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ export interface Props<T, K extends keyof T> {
*/
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 @@ -210,6 +215,7 @@ const props = withDefaults(defineProps<Props<T, IdType>>(), {
singleExpand: false,
stickyHeader: false,
stickyOffset: 0,
globalItemsPerPage: undefined,
});
const emit = defineEmits<{
Expand All @@ -224,6 +230,14 @@ const css = useCssModule();
const { stickyOffset } = toRefs(props);
const { stick, table, tableScroller } = useStickyTableHeader(stickyOffset);
const tableDefaults = useTable();
const globalItemsPerPageSettings = computed(() => {
if (props.globalItemsPerPage !== undefined) {
return props.globalItemsPerPage;
}
return get(tableDefaults.globalItemsPerPage);
});
const getKeys = <T extends object>(t: T) =>
Object.keys(t) as SortableColumnName<T>[];
Expand Down Expand Up @@ -275,6 +289,25 @@ watchImmediate(pagination, (pagination) => {
set(internalPaginationState, pagination);
});
/**
* 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 @@ -633,6 +666,17 @@ 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;
};
18 changes: 17 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import { type App } from 'vue';
import { useRotkiTheme } from '@/composables/theme';
import { StepperState } from '@/types/stepper';
import {
type TableOptions,
TableSymbol,
createTableDefaults,
} from '@/composables/defaults/table';
import type { InitThemeOptions } from '@/types/theme';
import '@/style.scss';

Expand All @@ -24,17 +29,28 @@ export { StepperState };

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

export function createRui(options: RuiOptions = {}) {
const { theme } = options;
const install = (_app: App) => {

const defaults = Object.freeze({
table: createTableDefaults(options.defaults?.table),
});

const install = (app: App) => {
const { registerIcons } = useIcons();
registerIcons(theme?.icons || []);
useRotkiTheme().init({ ...theme });

app.provide(TableSymbol, defaults.table);
};

return {
install,
defaults,
};
}

0 comments on commit 1da38ad

Please sign in to comment.