@dhis2-ui/table 10.16.2 → 10.16.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/src/data-table/__tests__/data-table-cell.test.js +168 -0
- package/src/data-table/__tests__/data-table-column-header/filter-handle.test.js +43 -0
- package/src/data-table/__tests__/data-table-column-header/sorter.test.js +41 -0
- package/src/data-table/__tests__/data-table-column-header.test.js +227 -0
- package/src/data-table/__tests__/data-table-row/expand-handle-cell.js +32 -0
- package/src/data-table/__tests__/data-table-row/expanded-row.test.js +57 -0
- package/src/data-table/__tests__/data-table-row.test.js +162 -0
- package/src/data-table/__tests__/data-table.test.js +85 -0
- package/src/data-table/data-table-body.js +3 -0
- package/src/data-table/data-table-cell.js +111 -0
- package/src/data-table/data-table-column-header/data-table-column-header.js +130 -0
- package/src/data-table/data-table-column-header/data-table-column-header.styles.js +30 -0
- package/src/data-table/data-table-column-header/filter-handle.js +30 -0
- package/src/data-table/data-table-column-header/sorter.js +67 -0
- package/src/data-table/data-table-foot.js +3 -0
- package/src/data-table/data-table-head.js +3 -0
- package/src/data-table/data-table-row/data-table-row.js +103 -0
- package/src/data-table/data-table-row/data-table-row.styles.js +20 -0
- package/src/data-table/data-table-row/drag-handle-cell.js +9 -0
- package/src/data-table/data-table-row/expand-handle-cell.js +28 -0
- package/src/data-table/data-table-row/expanded-row.js +51 -0
- package/src/data-table/data-table-toolbar.js +3 -0
- package/src/data-table/data-table.e2e.stories.js +192 -0
- package/src/data-table/data-table.js +75 -0
- package/src/data-table/data-table.prod.stories.js +1342 -0
- package/src/data-table/features/can_scroll/index.js +48 -0
- package/src/data-table/features/can_scroll.feature +31 -0
- package/src/data-table/index.js +8 -0
- package/src/data-table/table-elements/__tests__/table-body.test.js +40 -0
- package/src/data-table/table-elements/__tests__/table-data-cell.test.js +123 -0
- package/src/data-table/table-elements/__tests__/table-foot.test.js +40 -0
- package/src/data-table/table-elements/__tests__/table-head.test.js +40 -0
- package/src/data-table/table-elements/__tests__/table-header-cell-action.test.js +46 -0
- package/src/data-table/table-elements/__tests__/table-header-cell.test.js +130 -0
- package/src/data-table/table-elements/__tests__/table-row.test.js +52 -0
- package/src/data-table/table-elements/__tests__/table-scroll-box.test.js +40 -0
- package/src/data-table/table-elements/__tests__/table-toolbar.test.js +44 -0
- package/src/data-table/table-elements/__tests__/table.test.js +53 -0
- package/src/data-table/table-elements/features/can_scroll/index.js +42 -0
- package/src/data-table/table-elements/features/can_scroll.feature +31 -0
- package/src/data-table/table-elements/index.js +10 -0
- package/src/data-table/table-elements/table-body.js +75 -0
- package/src/data-table/table-elements/table-data-cell/table-data-cell.js +126 -0
- package/src/data-table/table-elements/table-data-cell/table-data-cell.styles.js +42 -0
- package/src/data-table/table-elements/table-foot.js +35 -0
- package/src/data-table/table-elements/table-head.js +26 -0
- package/src/data-table/table-elements/table-header-cell/table-header-cell.js +121 -0
- package/src/data-table/table-elements/table-header-cell/table-header-cell.styles.js +71 -0
- package/src/data-table/table-elements/table-header-cell-action.js +62 -0
- package/src/data-table/table-elements/table-row.js +52 -0
- package/src/data-table/table-elements/table-scroll-box.js +41 -0
- package/src/data-table/table-elements/table-toolbar.js +50 -0
- package/src/data-table/table-elements/table.e2e.stories.js +190 -0
- package/src/data-table/table-elements/table.js +70 -0
- package/src/data-table/table-elements/table.stories.internal.js +1144 -0
- package/src/index.js +32 -0
- package/src/locales/ar/translations.json +4 -0
- package/src/locales/cs/translations.json +4 -0
- package/src/locales/en/translations.json +4 -0
- package/src/locales/es/translations.json +4 -0
- package/src/locales/es_419/translations.json +4 -0
- package/src/locales/fr/translations.json +4 -0
- package/src/locales/index.js +44 -0
- package/src/locales/km/translations.json +4 -0
- package/src/locales/lo/translations.json +4 -0
- package/src/locales/nb/translations.json +4 -0
- package/src/locales/nl/translations.json +4 -0
- package/src/locales/pt/translations.json +4 -0
- package/src/locales/ru/translations.json +4 -0
- package/src/locales/uk/translations.json +4 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +4 -0
- package/src/locales/zh/translations.json +4 -0
- package/src/stacked-table/add-col-num-to-children.js +16 -0
- package/src/stacked-table/content-with-title.js +40 -0
- package/src/stacked-table/extract-header-labels.js +99 -0
- package/src/stacked-table/index.js +8 -0
- package/src/stacked-table/stacked-table-body.js +23 -0
- package/src/stacked-table/stacked-table-cell-head.js +39 -0
- package/src/stacked-table/stacked-table-cell.js +60 -0
- package/src/stacked-table/stacked-table-foot.js +24 -0
- package/src/stacked-table/stacked-table-head.js +23 -0
- package/src/stacked-table/stacked-table-row-head.js +19 -0
- package/src/stacked-table/stacked-table-row.js +50 -0
- package/src/stacked-table/stacked-table.js +33 -0
- package/src/stacked-table/stacked-table.prod.stories.js +463 -0
- package/src/stacked-table/stacked-table.test.js +127 -0
- package/src/stacked-table/supply-header-labels-to-children.js +7 -0
- package/src/stacked-table/table-context.js +4 -0
- package/src/stacked-table/table.js +28 -0
- package/src/table/index.js +8 -0
- package/src/table/table-body.js +21 -0
- package/src/table/table-cell-head.js +56 -0
- package/src/table/table-cell.js +56 -0
- package/src/table/table-context.js +7 -0
- package/src/table/table-foot.js +21 -0
- package/src/table/table-head.js +21 -0
- package/src/table/table-row-head.js +30 -0
- package/src/table/table-row.js +51 -0
- package/src/table/table.js +41 -0
- package/src/table/table.prod.stories.js +724 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { shallow, mount } from 'enzyme'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { DataTableRow } from '../data-table-row/data-table-row.js'
|
|
4
|
+
import { DragHandleCell } from '../data-table-row/drag-handle-cell.js'
|
|
5
|
+
import { ExpandHandleCell } from '../data-table-row/expand-handle-cell.js'
|
|
6
|
+
import { ExpandedRow } from '../data-table-row/expanded-row.js'
|
|
7
|
+
import { TableRow } from '../table-elements/index.js'
|
|
8
|
+
|
|
9
|
+
describe('<DataTableRow>', () => {
|
|
10
|
+
it('renders children', () => {
|
|
11
|
+
const children = 'children'
|
|
12
|
+
const wrapper = shallow(<DataTableRow>{children}</DataTableRow>)
|
|
13
|
+
|
|
14
|
+
expect(wrapper.containsMatchingElement(children)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
it('accepts a ref', () => {
|
|
17
|
+
const ref = React.createRef()
|
|
18
|
+
const wrapper = mount(
|
|
19
|
+
<table>
|
|
20
|
+
<thead>
|
|
21
|
+
<DataTableRow ref={ref} />
|
|
22
|
+
</thead>
|
|
23
|
+
</table>
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
expect(wrapper.find('tr').getDOMNode()).toBe(ref.current)
|
|
27
|
+
})
|
|
28
|
+
it('accepts a className prop', () => {
|
|
29
|
+
const className = 'test'
|
|
30
|
+
const wrapper = shallow(<DataTableRow className={className} />)
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find(TableRow).prop('className')).toBe(className)
|
|
33
|
+
})
|
|
34
|
+
it('accepts a dataTest prop', () => {
|
|
35
|
+
const dataTest = 'test'
|
|
36
|
+
const wrapper = shallow(<DataTableRow dataTest={dataTest} />)
|
|
37
|
+
|
|
38
|
+
expect(wrapper.find(TableRow).prop('dataTest')).toBe(dataTest)
|
|
39
|
+
})
|
|
40
|
+
it('accepts a draggable prop', () => {
|
|
41
|
+
const wrapper = shallow(<DataTableRow draggable />)
|
|
42
|
+
|
|
43
|
+
expect(wrapper.find(TableRow).prop('draggable')).toBe(true)
|
|
44
|
+
expect(wrapper.find(DragHandleCell)).toHaveLength(1)
|
|
45
|
+
})
|
|
46
|
+
it('accepts an expandable prop', () => {
|
|
47
|
+
const wrapper = shallow(
|
|
48
|
+
<DataTableRow
|
|
49
|
+
expandable
|
|
50
|
+
expandableContent="test"
|
|
51
|
+
onExpandToggle={() => {}}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
expect(wrapper.find(ExpandHandleCell)).toHaveLength(1)
|
|
56
|
+
})
|
|
57
|
+
it('accepts an expandableContent prop', () => {
|
|
58
|
+
const expandableContent = <div>content</div>
|
|
59
|
+
const wrapper = shallow(
|
|
60
|
+
<DataTableRow
|
|
61
|
+
expandableContent={expandableContent}
|
|
62
|
+
expanded
|
|
63
|
+
expandable
|
|
64
|
+
onExpandToggle={() => {}}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const additionalRowTag = wrapper
|
|
69
|
+
.find(ExpandedRow)
|
|
70
|
+
.dive()
|
|
71
|
+
.find(TableRow)
|
|
72
|
+
.dive()
|
|
73
|
+
.find('tr')
|
|
74
|
+
|
|
75
|
+
// render expandableContent in additional row
|
|
76
|
+
expect(additionalRowTag).toHaveLength(1)
|
|
77
|
+
|
|
78
|
+
// Hover styles
|
|
79
|
+
additionalRowTag.simulate('mouseover')
|
|
80
|
+
expect(
|
|
81
|
+
wrapper
|
|
82
|
+
.find(TableRow)
|
|
83
|
+
.dive()
|
|
84
|
+
.find('tr')
|
|
85
|
+
.hasClass('isHoveringExpandedContent')
|
|
86
|
+
).toBe(true)
|
|
87
|
+
|
|
88
|
+
additionalRowTag.simulate('mouseout')
|
|
89
|
+
expect(
|
|
90
|
+
wrapper
|
|
91
|
+
.find(TableRow)
|
|
92
|
+
.dive()
|
|
93
|
+
.find('tr')
|
|
94
|
+
.hasClass('isHoveringExpandedContent')
|
|
95
|
+
).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
it('accepts an expanded prop', () => {
|
|
98
|
+
const wrapperClosed = shallow(
|
|
99
|
+
<DataTableRow
|
|
100
|
+
expandable
|
|
101
|
+
expandableContent="test"
|
|
102
|
+
onExpandToggle={() => {}}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
const wrapperOpen = shallow(
|
|
106
|
+
<DataTableRow
|
|
107
|
+
expanded
|
|
108
|
+
expandable
|
|
109
|
+
expandableContent="test"
|
|
110
|
+
onExpandToggle={() => {}}
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(wrapperClosed.find(ExpandedRow)).toHaveLength(0)
|
|
115
|
+
expect(wrapperOpen.find(ExpandedRow)).toHaveLength(1)
|
|
116
|
+
})
|
|
117
|
+
it('accepts a role prop', () => {
|
|
118
|
+
const role = 'test'
|
|
119
|
+
const wrapper = shallow(<DataTableRow role={role} />)
|
|
120
|
+
|
|
121
|
+
expect(wrapper.find(TableRow).prop('role')).toBe(role)
|
|
122
|
+
})
|
|
123
|
+
it('accepts a selected prop', () => {
|
|
124
|
+
const wrapper = shallow(<DataTableRow selected />)
|
|
125
|
+
|
|
126
|
+
expect(wrapper.find(TableRow).prop('selected')).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
it('accepts an onExpandToggle prop', () => {
|
|
129
|
+
const cb = jest.fn()
|
|
130
|
+
const wrapper = shallow(
|
|
131
|
+
<DataTableRow
|
|
132
|
+
expandable
|
|
133
|
+
expandableContent="test"
|
|
134
|
+
onExpandToggle={cb}
|
|
135
|
+
/>
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
wrapper.find(ExpandHandleCell).dive().simulate('click')
|
|
139
|
+
|
|
140
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
141
|
+
expect(cb).toHaveBeenCalledWith({ expanded: true })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('accepts a hover prop', () => {
|
|
145
|
+
const cb = jest.fn()
|
|
146
|
+
const wrapper = mount(
|
|
147
|
+
<DataTableRow
|
|
148
|
+
expandable
|
|
149
|
+
expandableContent="test"
|
|
150
|
+
onExpandToggle={cb}
|
|
151
|
+
onMouseOver={cb}
|
|
152
|
+
onClick={cb}
|
|
153
|
+
/>
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
wrapper.find('tr').simulate('mouseover')
|
|
157
|
+
|
|
158
|
+
wrapper.find('tr').simulate('click')
|
|
159
|
+
|
|
160
|
+
expect(cb).toHaveBeenCalledTimes(2)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { shallow, mount } from 'enzyme'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { DataTable } from '../data-table.js'
|
|
4
|
+
import { Table, TableScrollBox } from '../table-elements/index.js'
|
|
5
|
+
|
|
6
|
+
describe('<DataTable>', () => {
|
|
7
|
+
it('renders children', () => {
|
|
8
|
+
const children = 'children'
|
|
9
|
+
const wrapper = shallow(<DataTable>{children}</DataTable>)
|
|
10
|
+
|
|
11
|
+
expect(wrapper.containsMatchingElement(children)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
it('accepts a ref', () => {
|
|
14
|
+
const ref = React.createRef()
|
|
15
|
+
const wrapper = mount(<DataTable ref={ref} />)
|
|
16
|
+
|
|
17
|
+
expect(wrapper.find('table').getDOMNode()).toBe(ref.current)
|
|
18
|
+
})
|
|
19
|
+
it('accepts a className prop', () => {
|
|
20
|
+
const className = 'test'
|
|
21
|
+
const wrapper = shallow(<DataTable className={className} />)
|
|
22
|
+
|
|
23
|
+
expect(wrapper.find(Table).prop('className')).toBe(className)
|
|
24
|
+
})
|
|
25
|
+
it('accepts a dataTest prop', () => {
|
|
26
|
+
const dataTest = 'test'
|
|
27
|
+
const wrapper = shallow(<DataTable dataTest={dataTest} />)
|
|
28
|
+
|
|
29
|
+
expect(wrapper.find(Table).prop('dataTest')).toBe(dataTest)
|
|
30
|
+
})
|
|
31
|
+
it('accepts a layout prop', () => {
|
|
32
|
+
const layout = 'fixed'
|
|
33
|
+
const wrapper = shallow(<DataTable layout={layout} />)
|
|
34
|
+
|
|
35
|
+
expect(wrapper.find(Table).prop('layout')).toBe(layout)
|
|
36
|
+
})
|
|
37
|
+
it('accepts a role prop', () => {
|
|
38
|
+
const role = 'test'
|
|
39
|
+
const wrapper = shallow(<DataTable role={role} />)
|
|
40
|
+
|
|
41
|
+
expect(wrapper.find(Table).prop('role')).toBe(role)
|
|
42
|
+
})
|
|
43
|
+
it('renders a datatable when no scrollHeight or scrollWidth are provided', () => {
|
|
44
|
+
const wrapper = shallow(<DataTable />)
|
|
45
|
+
|
|
46
|
+
expect(wrapper.find(Table)).toHaveLength(1)
|
|
47
|
+
expect(wrapper.find(TableScrollBox)).toHaveLength(0)
|
|
48
|
+
})
|
|
49
|
+
it('wraps the datatable in a scroll box when scrollHeight is provided', () => {
|
|
50
|
+
const height = '200px'
|
|
51
|
+
const wrapper = shallow(<DataTable scrollHeight={height} />)
|
|
52
|
+
|
|
53
|
+
expect(wrapper.dive().type()).toBe('div')
|
|
54
|
+
expect(wrapper.find(TableScrollBox)).toHaveLength(1)
|
|
55
|
+
expect(wrapper.find(TableScrollBox).dive().find(Table)).toHaveLength(1)
|
|
56
|
+
expect(wrapper.find(TableScrollBox).prop('maxHeight')).toBe(height)
|
|
57
|
+
})
|
|
58
|
+
it('wraps the datatable in a scroll box when scrollWidth is provided', () => {
|
|
59
|
+
const width = '200px'
|
|
60
|
+
const wrapper = shallow(<DataTable scrollWidth={width} />)
|
|
61
|
+
|
|
62
|
+
expect(wrapper.dive().type()).toBe('div')
|
|
63
|
+
expect(wrapper.find(TableScrollBox)).toHaveLength(1)
|
|
64
|
+
expect(wrapper.find(TableScrollBox).dive().find(Table)).toHaveLength(1)
|
|
65
|
+
expect(wrapper.find(TableScrollBox).prop('maxWidth')).toBe(width)
|
|
66
|
+
})
|
|
67
|
+
it('wraps the datatable in a scroll box when both scrollHeight and scrollWidth are provided', () => {
|
|
68
|
+
const size = '200px'
|
|
69
|
+
const wrapper = shallow(
|
|
70
|
+
<DataTable scrollHeight={size} scrollWidth={size} />
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expect(wrapper.dive().type()).toBe('div')
|
|
74
|
+
expect(wrapper.find(TableScrollBox)).toHaveLength(1)
|
|
75
|
+
expect(wrapper.find(TableScrollBox).dive().find(Table)).toHaveLength(1)
|
|
76
|
+
expect(wrapper.find(TableScrollBox).prop('maxHeight')).toBe(size)
|
|
77
|
+
expect(wrapper.find(TableScrollBox).prop('maxWidth')).toBe(size)
|
|
78
|
+
})
|
|
79
|
+
it('accepts a width prop', () => {
|
|
80
|
+
const width = '200px'
|
|
81
|
+
const wrapper = shallow(<DataTable width={width} />)
|
|
82
|
+
|
|
83
|
+
expect(wrapper.find(Table).prop('width')).toBe(width)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { mutuallyExclusive, requiredIf } from '@dhis2/prop-types'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React, { forwardRef } from 'react'
|
|
4
|
+
import { TableDataCell, TableHeaderCell } from './table-elements/index.js'
|
|
5
|
+
|
|
6
|
+
export const DataTableCell = forwardRef(
|
|
7
|
+
(
|
|
8
|
+
{
|
|
9
|
+
active,
|
|
10
|
+
align = 'left',
|
|
11
|
+
backgroundColor,
|
|
12
|
+
bordered,
|
|
13
|
+
children,
|
|
14
|
+
className,
|
|
15
|
+
colSpan,
|
|
16
|
+
dataTest = 'dhis2-uicore-datatablecell',
|
|
17
|
+
error,
|
|
18
|
+
fixed,
|
|
19
|
+
large,
|
|
20
|
+
|
|
21
|
+
left = 'auto',
|
|
22
|
+
muted,
|
|
23
|
+
rowSpan,
|
|
24
|
+
role,
|
|
25
|
+
scope,
|
|
26
|
+
staticStyle,
|
|
27
|
+
tag,
|
|
28
|
+
valid,
|
|
29
|
+
width = 'auto',
|
|
30
|
+
onClick,
|
|
31
|
+
...props
|
|
32
|
+
},
|
|
33
|
+
ref
|
|
34
|
+
) => {
|
|
35
|
+
const TableCell =
|
|
36
|
+
(!tag && fixed) || (tag && tag === 'th')
|
|
37
|
+
? TableHeaderCell
|
|
38
|
+
: TableDataCell
|
|
39
|
+
return (
|
|
40
|
+
<TableCell
|
|
41
|
+
{...props}
|
|
42
|
+
active={active}
|
|
43
|
+
align={align}
|
|
44
|
+
backgroundColor={backgroundColor}
|
|
45
|
+
bordered={bordered}
|
|
46
|
+
className={className}
|
|
47
|
+
colSpan={colSpan}
|
|
48
|
+
dataTest={dataTest}
|
|
49
|
+
error={error}
|
|
50
|
+
fixed={fixed}
|
|
51
|
+
large={large}
|
|
52
|
+
left={left}
|
|
53
|
+
muted={muted}
|
|
54
|
+
rowSpan={rowSpan}
|
|
55
|
+
ref={ref}
|
|
56
|
+
role={role}
|
|
57
|
+
staticStyle={staticStyle}
|
|
58
|
+
scope={scope}
|
|
59
|
+
valid={valid}
|
|
60
|
+
width={width}
|
|
61
|
+
onClick={onClick}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</TableCell>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
DataTableCell.displayName = 'DataTableCell'
|
|
70
|
+
|
|
71
|
+
const stylePropType = mutuallyExclusive(
|
|
72
|
+
['valid', 'error', 'muted'],
|
|
73
|
+
PropTypes.bool
|
|
74
|
+
)
|
|
75
|
+
const requiredIfFixedPropType = requiredIf(
|
|
76
|
+
(props) => props.fixed,
|
|
77
|
+
PropTypes.string
|
|
78
|
+
)
|
|
79
|
+
DataTableCell.propTypes = {
|
|
80
|
+
/** To toggle border color, for example for editing */
|
|
81
|
+
active: PropTypes.bool,
|
|
82
|
+
align: PropTypes.oneOf(['left', 'center', 'right']),
|
|
83
|
+
/** Sets background color of the cell. Disables dynamic background colors from active, hover, and selected states */
|
|
84
|
+
backgroundColor: PropTypes.string,
|
|
85
|
+
bordered: PropTypes.bool,
|
|
86
|
+
children: PropTypes.node,
|
|
87
|
+
className: PropTypes.string,
|
|
88
|
+
colSpan: PropTypes.string,
|
|
89
|
+
dataTest: PropTypes.string,
|
|
90
|
+
/** Mutually exclusive with muted and valid */
|
|
91
|
+
error: stylePropType,
|
|
92
|
+
/** When true a TableHeaderCell with sticky positioning will be rendered */
|
|
93
|
+
fixed: PropTypes.bool,
|
|
94
|
+
large: PropTypes.bool,
|
|
95
|
+
/** Required when fixed */
|
|
96
|
+
left: requiredIfFixedPropType,
|
|
97
|
+
/** Mutually exclusive with error and valid */
|
|
98
|
+
muted: stylePropType,
|
|
99
|
+
role: PropTypes.string,
|
|
100
|
+
rowSpan: PropTypes.string,
|
|
101
|
+
scope: PropTypes.string,
|
|
102
|
+
/** Surpress hover and active event styles */
|
|
103
|
+
staticStyle: PropTypes.bool,
|
|
104
|
+
/** Render a TableDataCell or TableHeaderCell respectively */
|
|
105
|
+
tag: PropTypes.oneOf(['td', 'th']),
|
|
106
|
+
/** Mutually exclusive with error and muted */
|
|
107
|
+
valid: stylePropType,
|
|
108
|
+
/** Required when fixed */
|
|
109
|
+
width: requiredIfFixedPropType,
|
|
110
|
+
onClick: PropTypes.func,
|
|
111
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { requiredIf } from '@dhis2/prop-types'
|
|
2
|
+
import cx from 'classnames'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React, { forwardRef } from 'react'
|
|
5
|
+
import { TableHeaderCell } from '../table-elements/index.js'
|
|
6
|
+
import {
|
|
7
|
+
styles,
|
|
8
|
+
resolvedTableHeaderCss,
|
|
9
|
+
} from './data-table-column-header.styles.js'
|
|
10
|
+
import { FilterHandle } from './filter-handle.js'
|
|
11
|
+
import { Sorter, SORT_DIRECTIONS } from './sorter.js'
|
|
12
|
+
|
|
13
|
+
const flexboxAlignLookup = {
|
|
14
|
+
left: 'flex-start',
|
|
15
|
+
center: 'center',
|
|
16
|
+
right: 'flex-end',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DataTableColumnHeader = forwardRef(
|
|
20
|
+
(
|
|
21
|
+
{
|
|
22
|
+
align,
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
colSpan,
|
|
26
|
+
dataTest = 'dhis2-uicore-datatablecellhead',
|
|
27
|
+
filter,
|
|
28
|
+
fixed,
|
|
29
|
+
large,
|
|
30
|
+
left,
|
|
31
|
+
name,
|
|
32
|
+
role,
|
|
33
|
+
rowSpan,
|
|
34
|
+
scope,
|
|
35
|
+
showFilter,
|
|
36
|
+
sortDirection,
|
|
37
|
+
sortIconTitle,
|
|
38
|
+
top,
|
|
39
|
+
width,
|
|
40
|
+
onFilterIconClick,
|
|
41
|
+
onSortIconClick,
|
|
42
|
+
},
|
|
43
|
+
ref
|
|
44
|
+
) => (
|
|
45
|
+
<TableHeaderCell
|
|
46
|
+
align={align}
|
|
47
|
+
className={cx(
|
|
48
|
+
className,
|
|
49
|
+
'DataTableColumnHeader',
|
|
50
|
+
resolvedTableHeaderCss.className
|
|
51
|
+
)}
|
|
52
|
+
colSpan={colSpan}
|
|
53
|
+
dataTest={dataTest}
|
|
54
|
+
fixed={fixed}
|
|
55
|
+
large={large}
|
|
56
|
+
left={left}
|
|
57
|
+
ref={ref}
|
|
58
|
+
rowSpan={rowSpan}
|
|
59
|
+
role={role}
|
|
60
|
+
scope={scope}
|
|
61
|
+
top={top}
|
|
62
|
+
width={width}
|
|
63
|
+
>
|
|
64
|
+
<span className="container">
|
|
65
|
+
<span className={cx('top', { large })}>
|
|
66
|
+
<span className="content">{children}</span>
|
|
67
|
+
{sortDirection && (
|
|
68
|
+
<Sorter
|
|
69
|
+
name={name}
|
|
70
|
+
sortDirection={sortDirection}
|
|
71
|
+
onClick={onSortIconClick}
|
|
72
|
+
title={sortIconTitle}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
{filter && (
|
|
76
|
+
<FilterHandle
|
|
77
|
+
name={name}
|
|
78
|
+
active={showFilter}
|
|
79
|
+
onClick={onFilterIconClick}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</span>
|
|
83
|
+
{showFilter && filter}
|
|
84
|
+
</span>
|
|
85
|
+
{resolvedTableHeaderCss.styles}
|
|
86
|
+
<style jsx>{styles}</style>
|
|
87
|
+
<style jsx>{`
|
|
88
|
+
.label {
|
|
89
|
+
justify-content: ${flexboxAlignLookup[align]};
|
|
90
|
+
}
|
|
91
|
+
`}</style>
|
|
92
|
+
</TableHeaderCell>
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
DataTableColumnHeader.displayName = 'DataTableColumnHeader'
|
|
97
|
+
|
|
98
|
+
DataTableColumnHeader.propTypes = {
|
|
99
|
+
align: PropTypes.oneOf(['left', 'center', 'right']),
|
|
100
|
+
children: PropTypes.node,
|
|
101
|
+
className: PropTypes.string,
|
|
102
|
+
colSpan: PropTypes.string,
|
|
103
|
+
dataTest: PropTypes.string,
|
|
104
|
+
/** The filter element (JSX), required when onFilterIconClick or showFilter are present */
|
|
105
|
+
filter: requiredIf(
|
|
106
|
+
(props) => props.onFilterIconClick || props.showFilter,
|
|
107
|
+
PropTypes.node
|
|
108
|
+
),
|
|
109
|
+
fixed: PropTypes.bool,
|
|
110
|
+
large: PropTypes.bool,
|
|
111
|
+
/** Left or top required when fixed */
|
|
112
|
+
left: requiredIf((props) => props.fixed && !props.top, PropTypes.string),
|
|
113
|
+
/** Can be used to match a column with a property name */
|
|
114
|
+
name: PropTypes.string,
|
|
115
|
+
role: PropTypes.string,
|
|
116
|
+
rowSpan: PropTypes.string,
|
|
117
|
+
scope: PropTypes.string,
|
|
118
|
+
showFilter: requiredIf((props) => props.filter, PropTypes.bool),
|
|
119
|
+
sortDirection: requiredIf(
|
|
120
|
+
(props) => props.onSortIconClick,
|
|
121
|
+
PropTypes.oneOf(SORT_DIRECTIONS)
|
|
122
|
+
),
|
|
123
|
+
sortIconTitle: PropTypes.string,
|
|
124
|
+
/** Left or top required when fixed */
|
|
125
|
+
top: requiredIf((props) => props.fixed && !props.left, PropTypes.string),
|
|
126
|
+
width: PropTypes.string,
|
|
127
|
+
onFilterIconClick: requiredIf((props) => props.filter, PropTypes.func),
|
|
128
|
+
/** Sort icon click callback with `nextSortDirection` and `name` in payload */
|
|
129
|
+
onSortIconClick: requiredIf((props) => props.sortDirection, PropTypes.func),
|
|
130
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import css from 'styled-jsx/css'
|
|
2
|
+
|
|
3
|
+
export const styles = css`
|
|
4
|
+
span.container {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 100%;
|
|
8
|
+
}
|
|
9
|
+
span.top {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: flex-start;
|
|
13
|
+
}
|
|
14
|
+
span.content {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
min-height: 24px;
|
|
18
|
+
}
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
export const resolvedTableHeaderCss = css.resolve`
|
|
22
|
+
:global(thead) > :global(tr) > th.DataTableColumnHeader {
|
|
23
|
+
padding-top: 6px;
|
|
24
|
+
padding-bottom: 6px;
|
|
25
|
+
}
|
|
26
|
+
:global(thead) > :global(tr) > th.DataTableColumnHeader.large {
|
|
27
|
+
padding-top: 12px;
|
|
28
|
+
padding-bottom: 12px;
|
|
29
|
+
}
|
|
30
|
+
`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { colors } from '@dhis2/ui-constants'
|
|
2
|
+
import { IconSearch16 } from '@dhis2/ui-icons'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import i18n from '../../locales/index.js'
|
|
6
|
+
import { TableHeaderCellAction } from '../table-elements/index.js'
|
|
7
|
+
|
|
8
|
+
export const FilterHandle = ({ active, name, onClick }) => {
|
|
9
|
+
const filterIconColor = active ? colors.blue700 : colors.grey600
|
|
10
|
+
const clickHandler = onClick
|
|
11
|
+
? (event) => {
|
|
12
|
+
onClick({ name, show: !active }, event)
|
|
13
|
+
}
|
|
14
|
+
: undefined
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<TableHeaderCellAction
|
|
18
|
+
onClick={clickHandler}
|
|
19
|
+
title={i18n.t('Toggle filter')}
|
|
20
|
+
>
|
|
21
|
+
<IconSearch16 color={filterIconColor} />
|
|
22
|
+
</TableHeaderCellAction>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
FilterHandle.propTypes = {
|
|
27
|
+
active: PropTypes.bool,
|
|
28
|
+
name: PropTypes.string,
|
|
29
|
+
onClick: PropTypes.func,
|
|
30
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { requiredIf } from '@dhis2/prop-types'
|
|
2
|
+
import { colors } from '@dhis2/ui-constants'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import i18n from '../../locales/index.js'
|
|
6
|
+
import { TableHeaderCellAction } from '../table-elements/index.js'
|
|
7
|
+
|
|
8
|
+
export const ASC = 'asc'
|
|
9
|
+
export const DESC = 'desc'
|
|
10
|
+
export const DEFAULT = 'default'
|
|
11
|
+
export const SORT_DIRECTIONS = [DEFAULT, ASC, DESC]
|
|
12
|
+
|
|
13
|
+
export const getNextSortDirection = (currentDirection) => {
|
|
14
|
+
const currentIndex = SORT_DIRECTIONS.indexOf(currentDirection)
|
|
15
|
+
const nextIndex = (currentIndex + 1) % SORT_DIRECTIONS.length
|
|
16
|
+
|
|
17
|
+
return SORT_DIRECTIONS[nextIndex]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Sorter = ({ name, sortDirection, title, onClick }) => {
|
|
21
|
+
const nextSortDirection = getNextSortDirection(sortDirection)
|
|
22
|
+
const clickHandler = onClick
|
|
23
|
+
? (event) => {
|
|
24
|
+
onClick({ name, direction: nextSortDirection }, event)
|
|
25
|
+
}
|
|
26
|
+
: undefined
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<TableHeaderCellAction
|
|
30
|
+
onClick={clickHandler}
|
|
31
|
+
title={title || i18n.t('Sort items')}
|
|
32
|
+
>
|
|
33
|
+
<svg
|
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
35
|
+
width="16"
|
|
36
|
+
height="16"
|
|
37
|
+
viewBox="0 0 16 16"
|
|
38
|
+
className={sortDirection}
|
|
39
|
+
>
|
|
40
|
+
<g fill="none" fillRule="evenodd">
|
|
41
|
+
<polygon className="top" points="4 9 12 9 8 14" />
|
|
42
|
+
<polygon className="bottom" points="4 7 12 7 8 2" />
|
|
43
|
+
</g>
|
|
44
|
+
</svg>
|
|
45
|
+
<style jsx>{`
|
|
46
|
+
svg .top,
|
|
47
|
+
svg .bottom {
|
|
48
|
+
fill: ${colors.grey500};
|
|
49
|
+
}
|
|
50
|
+
svg.asc .top,
|
|
51
|
+
svg.desc .bottom {
|
|
52
|
+
fill: ${colors.blue700};
|
|
53
|
+
}
|
|
54
|
+
`}</style>
|
|
55
|
+
</TableHeaderCellAction>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Sorter.propTypes = {
|
|
60
|
+
name: PropTypes.string,
|
|
61
|
+
sortDirection: requiredIf(
|
|
62
|
+
(props) => props.onClick,
|
|
63
|
+
PropTypes.oneOf(SORT_DIRECTIONS)
|
|
64
|
+
),
|
|
65
|
+
title: PropTypes.string,
|
|
66
|
+
onClick: PropTypes.func,
|
|
67
|
+
}
|