Skip to content

Commit b864531

Browse files
jialin-hedaneahbrentswishersirrah-tam
authored
Table: Create table components (#695)
* feat(table): add pharos table component * feat(table): add more to tables * feat(table): implement page size dropdown * feat(table): add react storybook * feat(table): add unit tests to table * docs(table): add changeset * feat(table): update storybook * feat(table): improve storybook reactivity * feat(table): add accessibility update * feat(table): use pharos color * feat(table): fix bug * feat(table): fix build issue * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Dane Hillard <github@danehillard.com> * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Dane Hillard <github@danehillard.com> * feat(table): fix pagination height and remove any type * feat(table): add unit test for start and end page number * Update packages/pharos/src/components/table/pharos-table.test.ts Co-authored-by: Dane Hillard <github@danehillard.com> * Update packages/pharos/src/components/table/pharos-table.test.ts Co-authored-by: Dane Hillard <github@danehillard.com> * Update packages/pharos/src/components/table/pharos-table.test.ts Co-authored-by: Dane Hillard <github@danehillard.com> * feat(table): update row data type * feat(table): update unit test * feat(table): add hide caption visually attribute * feat(table): update visually hidden css * feat(table): use hidden mixin * feat(table): fix lint and bug * feat(table): fix unit tests * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Dane Hillard <github@danehillard.com> * Update packages/pharos/src/components/table/pharos-table.test.ts Co-authored-by: Brent Swisher <brent@brentswisher.com> * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Mat Harris <mat.harris@ithaka.org> * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Mat Harris <mat.harris@ithaka.org> * Update packages/pharos/src/components/table/pharos-table.ts Co-authored-by: Mat Harris <mat.harris@ithaka.org> * feat(table): update attribute name and add unit test --------- Co-authored-by: Dane Hillard <github@danehillard.com> Co-authored-by: Brent Swisher <brent@brentswisher.com> Co-authored-by: Mat Harris <mat.harris@ithaka.org>
1 parent 976ac61 commit b864531

File tree

11 files changed

+607
-0
lines changed

11 files changed

+607
-0
lines changed

.changeset/orange-fireants-impress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ithaka/pharos': minor
3+
---
4+
5+
Create table component

.storybook/initComponents.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
PharosSidenavSection,
3737
PharosTabs,
3838
PharosTab,
39+
PharosTable,
3940
PharosTabPanel,
4041
PharosTextInput,
4142
PharosTextarea,
@@ -86,6 +87,7 @@ registerComponents('storybook', [
8687
PharosSidenavSection,
8788
PharosTabs,
8889
PharosTab,
90+
PharosTable,
8991
PharosTabPanel,
9092
PharosTextInput,
9193
PharosTextarea,

packages/pharos-site/initComponents.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ if (typeof window !== `undefined`) {
3939
pharos.PharosSidenavSection,
4040
pharos.PharosTabs,
4141
pharos.PharosTab,
42+
pharos.PharosTable,
4243
pharos.PharosTabPanel,
4344
pharos.PharosTextInput,
4445
pharos.PharosTextarea,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { PharosTable } from '../../react-components';
2+
import { configureDocsPage } from '@config/docsPageConfig';
3+
import { PharosContext } from '../../utils/PharosContext';
4+
import { defaultArgs } from './storyArgs';
5+
6+
export default {
7+
title: 'Components/Table',
8+
component: PharosTable,
9+
decorators: [
10+
(Story) => (
11+
<PharosContext.Provider value={{ prefix: 'storybook' }}>
12+
<Story />
13+
</PharosContext.Provider>
14+
),
15+
],
16+
parameters: {
17+
docs: { page: configureDocsPage('table') },
18+
options: { selectedPanel: 'addon-controls' },
19+
},
20+
};
21+
22+
export const Base = {
23+
render: (args) => (
24+
<PharosTable
25+
columns={args.columns}
26+
rowData={args.rowData}
27+
showPagination={args.showPagination}
28+
caption={'An example table'}
29+
></PharosTable>
30+
),
31+
args: {
32+
...defaultArgs,
33+
showPagination: false,
34+
},
35+
};
36+
37+
export const WithPagination = {
38+
render: (args) => (
39+
<PharosTable
40+
columns={args.columns}
41+
rowData={args.rowData}
42+
showPagination={args.showPagination}
43+
totalResults={5}
44+
pageSizeOptions={[2, 4]}
45+
caption={'An example table'}
46+
></PharosTable>
47+
),
48+
args: {
49+
...defaultArgs,
50+
showPagination: true,
51+
},
52+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@use '../../utils/scss/mixins';
2+
3+
:host {
4+
display: inline-flex;
5+
flex-direction: column;
6+
}
7+
8+
.table {
9+
border-collapse: collapse;
10+
11+
th {
12+
border: 1px solid var(--pharos-color-marble-gray-base);
13+
padding: var(--pharos-spacing-1-x);
14+
}
15+
16+
td {
17+
border: 1px solid var(--pharos-color-marble-gray-base);
18+
padding: var(--pharos-spacing-1-x);
19+
}
20+
}
21+
22+
.table-controls {
23+
display: flex;
24+
justify-content: space-between;
25+
align-items: center;
26+
margin-top: var(--pharos-spacing-one-half-x);
27+
28+
.item-per-page-wrapper {
29+
display: flex;
30+
align-items: center;
31+
column-gap: var(--pharos-spacing-one-half-x);
32+
33+
.item-per-page-selector {
34+
width: 75px;
35+
}
36+
}
37+
38+
.pagination {
39+
height: var(--pharos-spacing-one-and-a-half-x);
40+
}
41+
}
42+
43+
.visually-hidden:not(:focus, :active) {
44+
@include mixins.hidden;
45+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { fixture, expect } from '@open-wc/testing';
2+
import type { TemplateResult } from 'lit';
3+
4+
import { html } from 'lit/static-html.js';
5+
6+
import type { PharosTable } from './pharos-table';
7+
import type { PharosLink } from '../link/pharos-link';
8+
import type { PharosPagination } from '../pagination/pharos-pagination';
9+
import type { PharosSelect } from '../select/pharos-select';
10+
11+
describe('pharos-table', () => {
12+
let component: PharosTable, componentWithPagination: PharosTable;
13+
const columns = [
14+
{
15+
name: 'Item',
16+
field: 'item',
17+
},
18+
{
19+
name: 'Filename',
20+
field: 'filename',
21+
},
22+
];
23+
const rowData = [
24+
{
25+
item: 1,
26+
filename: '12345.jpg',
27+
expired_date: '2020-1-1',
28+
created_on: '2010-1-1',
29+
university: 'University of Michigan',
30+
},
31+
{
32+
item: 2,
33+
filename: '123456.jpg',
34+
expired_date: '2020-1-1',
35+
created_on: '2010-1-1',
36+
university: 'University of Michigan',
37+
},
38+
];
39+
40+
beforeEach(async () => {
41+
component = await fixture(html`
42+
<test-pharos-table
43+
.columns="${columns}"
44+
.rowData="${rowData}"
45+
.totalResults="${2}"
46+
caption="test table"
47+
>
48+
</test-pharos-table>
49+
`);
50+
51+
componentWithPagination = await fixture(html`
52+
<test-pharos-table
53+
.columns="${columns}"
54+
.rowData="${rowData}"
55+
.showPagination="${true}"
56+
.totalResults="${2}"
57+
.pageSizeOptions="${[1, 2]}"
58+
caption="test table with pagination"
59+
>
60+
</test-pharos-table>
61+
`);
62+
});
63+
64+
it('is accessible', async () => {
65+
await expect(component).to.be.accessible();
66+
});
67+
68+
it('is accessible with pagination', async () => {
69+
await expect(componentWithPagination).to.be.accessible();
70+
});
71+
72+
it('has the correct number of rows', async () => {
73+
const rows = Array.prototype.slice.call(
74+
component.renderRoot.querySelectorAll(`tr`)
75+
) as TemplateResult[];
76+
expect(rows.length).to.be.eq(3);
77+
});
78+
79+
it('renders rows according to page size', async () => {
80+
const rows = Array.prototype.slice.call(
81+
componentWithPagination.renderRoot.querySelectorAll(`tr`)
82+
) as TemplateResult[];
83+
expect(rows.length).to.be.eq(2);
84+
});
85+
86+
it('shows correct page start number according to page size', async () => {
87+
let pageNumber: HTMLElement | null =
88+
componentWithPagination.renderRoot.querySelector(`.page-number-display`);
89+
expect(pageNumber?.innerText).contains('Displaying 1-1 of 2');
90+
91+
const selectDropdown = componentWithPagination.renderRoot.querySelector(
92+
`pharos-select`
93+
) as PharosSelect;
94+
selectDropdown['_select'].value = '2';
95+
selectDropdown['_select'].dispatchEvent(new Event('change'));
96+
97+
await componentWithPagination.updateComplete;
98+
99+
pageNumber = componentWithPagination.renderRoot.querySelector(`.page-number-display`);
100+
expect(pageNumber?.innerText).contains('Displaying 1-2 of 2');
101+
});
102+
103+
it('updates correctly after page size selection', async () => {
104+
let rows = Array.prototype.slice.call(
105+
componentWithPagination.renderRoot.querySelectorAll(`tr`)
106+
) as TemplateResult[];
107+
expect(rows.length).to.be.eq(2);
108+
109+
const selectDropdown = componentWithPagination.renderRoot.querySelector(
110+
`pharos-select`
111+
) as PharosSelect;
112+
selectDropdown['_select'].value = '2';
113+
selectDropdown['_select'].dispatchEvent(new Event('change'));
114+
115+
await componentWithPagination.updateComplete;
116+
117+
rows = Array.prototype.slice.call(
118+
componentWithPagination.renderRoot.querySelectorAll(`tr`)
119+
) as TemplateResult[];
120+
expect(rows.length).to.be.eq(3);
121+
});
122+
123+
it('fires a custom event when going to previous and next page', async () => {
124+
let prevWasFired = false;
125+
let nextWasFired = false;
126+
const handlePrevPage = (): void => {
127+
prevWasFired = true;
128+
};
129+
const handleNextPage = (): void => {
130+
nextWasFired = true;
131+
};
132+
componentWithPagination.addEventListener('pharos-table-prev-page', handlePrevPage);
133+
componentWithPagination.addEventListener('pharos-table-next-page', handleNextPage);
134+
135+
const pagination = componentWithPagination.renderRoot.querySelector(
136+
`pharos-pagination`
137+
) as PharosPagination;
138+
const nextPageLink = pagination.renderRoot.querySelector(`.next`) as PharosLink;
139+
nextPageLink.click();
140+
await componentWithPagination.updateComplete;
141+
142+
expect(nextWasFired).to.be.true;
143+
144+
const prevPageLink = pagination.renderRoot.querySelector(`.prev`) as PharosLink;
145+
prevPageLink.click();
146+
await componentWithPagination.updateComplete;
147+
148+
expect(prevWasFired).to.be.true;
149+
});
150+
});
151+
152+
it('throws an error if caption is not provided', async () => {
153+
let errorThrown = false;
154+
try {
155+
await fixture(
156+
html`<test-pharos-table
157+
.columns="${[]}"
158+
.rowData="${[]}"
159+
.showPagination="${true}"
160+
.totalResults="${2}"
161+
.pageSizeOptions="${[1, 2]}"
162+
>
163+
</test-pharos-table> `
164+
);
165+
} catch (error) {
166+
if (error instanceof Error) {
167+
errorThrown = true;
168+
expect(error?.message).to.be.equal(
169+
'Table must have an accessible name. Please provide a caption for the table using the `caption` attribute. You can hide the caption visually by setting the `hide-caption-visually` property.'
170+
);
171+
}
172+
}
173+
expect(errorThrown).to.be.true;
174+
});

0 commit comments

Comments
 (0)