@prairielearn/ui 1.9.1 → 1.10.0
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/CHANGELOG.md +11 -0
- package/dist/components/CategoricalColumnFilter.d.ts.map +1 -1
- package/dist/components/CategoricalColumnFilter.js +4 -4
- package/dist/components/CategoricalColumnFilter.js.map +1 -1
- package/dist/components/ColumnManager.js +7 -7
- package/dist/components/ColumnManager.js.map +1 -1
- package/dist/components/MultiSelectColumnFilter.js +2 -2
- package/dist/components/MultiSelectColumnFilter.js.map +1 -1
- package/dist/components/NumericInputColumnFilter.d.ts.map +1 -1
- package/dist/components/NumericInputColumnFilter.js +5 -9
- package/dist/components/NumericInputColumnFilter.js.map +1 -1
- package/dist/components/PresetFilterDropdown.js +3 -3
- package/dist/components/PresetFilterDropdown.js.map +1 -1
- package/dist/components/TanstackTable.js +13 -13
- package/dist/components/TanstackTable.js.map +1 -1
- package/dist/components/TanstackTableDownloadButton.js +1 -1
- package/dist/components/TanstackTableDownloadButton.js.map +1 -1
- package/dist/components/TanstackTableHeaderCell.js +8 -8
- package/dist/components/TanstackTableHeaderCell.js.map +1 -1
- package/dist/components/useAutoSizeColumns.js +1 -1
- package/dist/components/useAutoSizeColumns.js.map +1 -1
- package/package.json +2 -2
- package/src/components/CategoricalColumnFilter.tsx +25 -22
- package/src/components/ColumnManager.tsx +19 -19
- package/src/components/MultiSelectColumnFilter.tsx +12 -12
- package/src/components/NumericInputColumnFilter.tsx +13 -16
- package/src/components/PresetFilterDropdown.tsx +3 -3
- package/src/components/TanstackTable.tsx +25 -25
- package/src/components/TanstackTableDownloadButton.tsx +11 -11
- package/src/components/TanstackTableHeaderCell.tsx +11 -11
- package/src/components/useAutoSizeColumns.tsx +1 -1
|
@@ -15,7 +15,7 @@ function computeSelected<T extends readonly any[]>(
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function defaultRenderValueLabel<T>({ value }: { value: T }) {
|
|
18
|
-
return <span
|
|
18
|
+
return <span className="text-nowrap">{String(value)}</span>;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* A component that allows the user to filter a categorical column.
|
|
@@ -70,23 +70,26 @@ export function CategoricalColumnFilter<TData, TValue>({
|
|
|
70
70
|
<Dropdown align="end">
|
|
71
71
|
<Dropdown.Toggle
|
|
72
72
|
variant="link"
|
|
73
|
-
|
|
73
|
+
className="text-muted p-0"
|
|
74
74
|
id={`filter-${columnId}`}
|
|
75
75
|
aria-label={`Filter ${label.toLowerCase()}`}
|
|
76
76
|
title={`Filter ${label.toLowerCase()}`}
|
|
77
77
|
>
|
|
78
78
|
<i
|
|
79
|
-
|
|
79
|
+
className={clsx(
|
|
80
|
+
'bi',
|
|
81
|
+
selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel',
|
|
82
|
+
)}
|
|
80
83
|
aria-hidden="true"
|
|
81
84
|
/>
|
|
82
85
|
</Dropdown.Toggle>
|
|
83
|
-
<Dropdown.Menu
|
|
84
|
-
<div
|
|
85
|
-
<div
|
|
86
|
-
<div
|
|
86
|
+
<Dropdown.Menu className="p-0">
|
|
87
|
+
<div className="p-3 pb-0">
|
|
88
|
+
<div className="d-flex align-items-center justify-content-between mb-2">
|
|
89
|
+
<div className="fw-semibold text-nowrap">{label}</div>
|
|
87
90
|
<button
|
|
88
91
|
type="button"
|
|
89
|
-
|
|
92
|
+
className={clsx('btn btn-link btn-sm text-decoration-none', {
|
|
90
93
|
// Hide the clear button if no filters are applied.
|
|
91
94
|
// Use `visibility` instead of conditional rendering to avoid layout shift.
|
|
92
95
|
invisible: selected.size === 0 && mode === 'include',
|
|
@@ -97,35 +100,35 @@ export function CategoricalColumnFilter<TData, TValue>({
|
|
|
97
100
|
</button>
|
|
98
101
|
</div>
|
|
99
102
|
|
|
100
|
-
<div
|
|
103
|
+
<div className="btn-group btn-group-sm w-100 mb-2">
|
|
101
104
|
<input
|
|
102
105
|
type="radio"
|
|
103
|
-
|
|
106
|
+
className="btn-check"
|
|
104
107
|
name={`filter-${columnId}-options`}
|
|
105
108
|
id={`filter-${columnId}-include`}
|
|
106
109
|
autocomplete="off"
|
|
107
110
|
checked={mode === 'include'}
|
|
108
111
|
onChange={() => apply('include', selected)}
|
|
109
112
|
/>
|
|
110
|
-
<label
|
|
111
|
-
<span
|
|
112
|
-
{mode === 'include' && <i
|
|
113
|
+
<label className="btn btn-outline-primary" for={`filter-${columnId}-include`}>
|
|
114
|
+
<span className="text-nowrap">
|
|
115
|
+
{mode === 'include' && <i className="bi bi-check-lg me-1" aria-hidden="true" />}
|
|
113
116
|
Include
|
|
114
117
|
</span>
|
|
115
118
|
</label>
|
|
116
119
|
|
|
117
120
|
<input
|
|
118
121
|
type="radio"
|
|
119
|
-
|
|
122
|
+
className="btn-check"
|
|
120
123
|
name={`filter-${columnId}-options`}
|
|
121
124
|
id={`filter-${columnId}-exclude`}
|
|
122
125
|
autocomplete="off"
|
|
123
126
|
checked={mode === 'exclude'}
|
|
124
127
|
onChange={() => apply('exclude', selected)}
|
|
125
128
|
/>
|
|
126
|
-
<label
|
|
127
|
-
<span
|
|
128
|
-
{mode === 'exclude' && <i
|
|
129
|
+
<label className="btn btn-outline-primary" for={`filter-${columnId}-exclude`}>
|
|
130
|
+
<span className="text-nowrap">
|
|
131
|
+
{mode === 'exclude' && <i className="bi bi-check-lg me-1" aria-hidden="true" />}
|
|
129
132
|
Exclude
|
|
130
133
|
</span>
|
|
131
134
|
</label>
|
|
@@ -133,7 +136,7 @@ export function CategoricalColumnFilter<TData, TValue>({
|
|
|
133
136
|
</div>
|
|
134
137
|
|
|
135
138
|
<div
|
|
136
|
-
|
|
139
|
+
className="list-group list-group-flush"
|
|
137
140
|
style={{
|
|
138
141
|
// This is needed to prevent the last item's background from covering
|
|
139
142
|
// the dropdown's border radius.
|
|
@@ -143,16 +146,16 @@ export function CategoricalColumnFilter<TData, TValue>({
|
|
|
143
146
|
{allColumnValues.map((value) => {
|
|
144
147
|
const isSelected = selected.has(value);
|
|
145
148
|
return (
|
|
146
|
-
<div key={value}
|
|
147
|
-
<div
|
|
149
|
+
<div key={value} className="list-group-item d-flex align-items-center gap-3">
|
|
150
|
+
<div className="form-check">
|
|
148
151
|
<input
|
|
149
|
-
|
|
152
|
+
className="form-check-input"
|
|
150
153
|
type="checkbox"
|
|
151
154
|
checked={isSelected}
|
|
152
155
|
id={`${columnId}-${value}`}
|
|
153
156
|
onChange={() => toggleSelected(value)}
|
|
154
157
|
/>
|
|
155
|
-
<label
|
|
158
|
+
<label className="form-check-label fw-normal" for={`${columnId}-${value}`}>
|
|
156
159
|
{renderValueLabel({
|
|
157
160
|
value,
|
|
158
161
|
isSelected,
|
|
@@ -27,19 +27,19 @@ function ColumnLeafItem<RowDataModel>({
|
|
|
27
27
|
return (
|
|
28
28
|
<div
|
|
29
29
|
key={column.id}
|
|
30
|
-
|
|
30
|
+
className={clsx('px-2 py-1 d-flex align-items-center justify-content-between', className)}
|
|
31
31
|
>
|
|
32
|
-
<label
|
|
32
|
+
<label className="form-check me-auto text-nowrap d-flex align-items-stretch">
|
|
33
33
|
<input
|
|
34
34
|
type="checkbox"
|
|
35
|
-
|
|
35
|
+
className="form-check-input"
|
|
36
36
|
checked={column.getIsVisible()}
|
|
37
37
|
disabled={!column.getCanHide()}
|
|
38
38
|
aria-label={column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`}
|
|
39
39
|
aria-describedby={`${column.id}-label`}
|
|
40
40
|
onChange={column.getToggleVisibilityHandler()}
|
|
41
41
|
/>
|
|
42
|
-
<span
|
|
42
|
+
<span className="form-check-label ms-2" id={`${column.id}-label`}>
|
|
43
43
|
{header}
|
|
44
44
|
</span>
|
|
45
45
|
</label>
|
|
@@ -48,7 +48,7 @@ function ColumnLeafItem<RowDataModel>({
|
|
|
48
48
|
// Since the HTML changes, but we want to refocus the pin button, we track
|
|
49
49
|
// the active pin button and refocuses it when the column manager is rerendered.
|
|
50
50
|
id={`${column.id}-pin`}
|
|
51
|
-
|
|
51
|
+
className={clsx(
|
|
52
52
|
'btn btn-sm btn-ghost ms-2',
|
|
53
53
|
(!column.getCanPin() || !onPinningBoundary) && 'invisible',
|
|
54
54
|
)}
|
|
@@ -59,7 +59,7 @@ function ColumnLeafItem<RowDataModel>({
|
|
|
59
59
|
data-bs-toggle="tooltip"
|
|
60
60
|
onClick={() => onTogglePin(column.id)}
|
|
61
61
|
>
|
|
62
|
-
<i
|
|
62
|
+
<i className={`bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`} aria-hidden="true" />
|
|
63
63
|
</button>
|
|
64
64
|
</div>
|
|
65
65
|
);
|
|
@@ -98,12 +98,12 @@ function ColumnGroupItem<RowDataModel>({
|
|
|
98
98
|
(typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
|
-
<div
|
|
102
|
-
<div
|
|
103
|
-
<div
|
|
101
|
+
<div className="d-flex flex-column">
|
|
102
|
+
<div className="px-2 py-1 d-flex align-items-center justify-content-between">
|
|
103
|
+
<div className="d-flex align-items-center flex-grow-1">
|
|
104
104
|
<input
|
|
105
105
|
type="checkbox"
|
|
106
|
-
|
|
106
|
+
className="form-check-input flex-shrink-0"
|
|
107
107
|
checked={isAllVisible}
|
|
108
108
|
indeterminate={isSomeVisible}
|
|
109
109
|
aria-label={`Toggle visibility for group '${header}'`}
|
|
@@ -111,16 +111,16 @@ function ColumnGroupItem<RowDataModel>({
|
|
|
111
111
|
/>
|
|
112
112
|
<button
|
|
113
113
|
type="button"
|
|
114
|
-
|
|
114
|
+
className="btn btn-link text-decoration-none text-reset w-100 text-start d-flex align-items-center justify-content-between ps-2 py-0 pe-0"
|
|
115
115
|
aria-expanded={isExpanded}
|
|
116
116
|
onClick={(e) => {
|
|
117
117
|
e.stopPropagation();
|
|
118
118
|
setIsExpanded(!isExpanded);
|
|
119
119
|
}}
|
|
120
120
|
>
|
|
121
|
-
<span
|
|
121
|
+
<span className="fw-bold text-truncate">{header}</span>
|
|
122
122
|
<i
|
|
123
|
-
|
|
123
|
+
className={clsx(
|
|
124
124
|
'bi ms-2 text-muted',
|
|
125
125
|
isExpanded ? 'bi-chevron-down' : 'bi-chevron-right',
|
|
126
126
|
)}
|
|
@@ -130,7 +130,7 @@ function ColumnGroupItem<RowDataModel>({
|
|
|
130
130
|
</div>
|
|
131
131
|
</div>
|
|
132
132
|
{isExpanded && (
|
|
133
|
-
<div
|
|
133
|
+
<div className="ps-3 border-start ms-3 mb-1">
|
|
134
134
|
{column.columns.map((childCol) => (
|
|
135
135
|
<ColumnItem
|
|
136
136
|
key={childCol.id}
|
|
@@ -310,7 +310,7 @@ export function ColumnManager<RowDataModel>({
|
|
|
310
310
|
id="column-manager"
|
|
311
311
|
variant="tanstack-table"
|
|
312
312
|
>
|
|
313
|
-
<i
|
|
313
|
+
<i className="bi bi-view-list me-2" aria-hidden="true" /> View{' '}
|
|
314
314
|
</Dropdown.Toggle>
|
|
315
315
|
<Dropdown.Menu style={{ maxHeight: '60vh', overflowY: 'auto' }}>
|
|
316
316
|
{topContent && (
|
|
@@ -321,7 +321,7 @@ export function ColumnManager<RowDataModel>({
|
|
|
321
321
|
)}
|
|
322
322
|
{pinnedMenuColumns.length > 0 && (
|
|
323
323
|
<>
|
|
324
|
-
<div
|
|
324
|
+
<div className="px-2 py-1 text-muted small" role="presentation">
|
|
325
325
|
Frozen columns
|
|
326
326
|
</div>
|
|
327
327
|
<div role="group">
|
|
@@ -358,11 +358,11 @@ export function ColumnManager<RowDataModel>({
|
|
|
358
358
|
</>
|
|
359
359
|
)}
|
|
360
360
|
{showResetButton && (
|
|
361
|
-
<div
|
|
361
|
+
<div className="px-2 py-1">
|
|
362
362
|
<Button
|
|
363
363
|
variant="secondary"
|
|
364
364
|
size="sm"
|
|
365
|
-
|
|
365
|
+
className="w-100"
|
|
366
366
|
aria-label="Reset all columns to default visibility and pinning"
|
|
367
367
|
onClick={() => {
|
|
368
368
|
table.resetColumnVisibility();
|
|
@@ -371,7 +371,7 @@ export function ColumnManager<RowDataModel>({
|
|
|
371
371
|
setActiveElementId('column-manager');
|
|
372
372
|
}}
|
|
373
373
|
>
|
|
374
|
-
<i
|
|
374
|
+
<i className="bi bi-arrow-counterclockwise me-2" aria-hidden="true" />
|
|
375
375
|
Reset view
|
|
376
376
|
</Button>
|
|
377
377
|
</div>
|
|
@@ -55,23 +55,23 @@ export function MultiSelectColumnFilter<TData, TValue>({
|
|
|
55
55
|
<Dropdown align="end">
|
|
56
56
|
<Dropdown.Toggle
|
|
57
57
|
variant="link"
|
|
58
|
-
|
|
58
|
+
className="text-muted p-0"
|
|
59
59
|
id={`filter-${columnId}`}
|
|
60
60
|
aria-label={`Filter ${label.toLowerCase()}`}
|
|
61
61
|
title={`Filter ${label.toLowerCase()}`}
|
|
62
62
|
>
|
|
63
63
|
<i
|
|
64
|
-
|
|
64
|
+
className={clsx('bi', hasActiveFilter ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}
|
|
65
65
|
aria-hidden="true"
|
|
66
66
|
/>
|
|
67
67
|
</Dropdown.Toggle>
|
|
68
|
-
<Dropdown.Menu
|
|
69
|
-
<div
|
|
70
|
-
<div
|
|
71
|
-
<div
|
|
68
|
+
<Dropdown.Menu className="p-0">
|
|
69
|
+
<div className="p-3 pb-0" style={{ minWidth: '250px' }}>
|
|
70
|
+
<div className="d-flex align-items-center justify-content-between mb-2">
|
|
71
|
+
<div className="fw-semibold">{label}</div>
|
|
72
72
|
<button
|
|
73
73
|
type="button"
|
|
74
|
-
|
|
74
|
+
className="btn btn-link btn-sm text-decoration-none p-0"
|
|
75
75
|
onClick={() => column.setFilterValue([])}
|
|
76
76
|
>
|
|
77
77
|
Clear
|
|
@@ -80,7 +80,7 @@ export function MultiSelectColumnFilter<TData, TValue>({
|
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
82
|
<div
|
|
83
|
-
|
|
83
|
+
className="list-group list-group-flush"
|
|
84
84
|
style={{
|
|
85
85
|
// This is needed to prevent the last item's background from covering
|
|
86
86
|
// the dropdown's border radius.
|
|
@@ -90,16 +90,16 @@ export function MultiSelectColumnFilter<TData, TValue>({
|
|
|
90
90
|
{allColumnValues.map((value) => {
|
|
91
91
|
const isSelected = selected.has(value);
|
|
92
92
|
return (
|
|
93
|
-
<div key={value}
|
|
94
|
-
<div
|
|
93
|
+
<div key={value} className="list-group-item d-flex align-items-center gap-3">
|
|
94
|
+
<div className="form-check">
|
|
95
95
|
<input
|
|
96
|
-
|
|
96
|
+
className="form-check-input"
|
|
97
97
|
type="checkbox"
|
|
98
98
|
checked={isSelected}
|
|
99
99
|
id={`${columnId}-${value}`}
|
|
100
100
|
onChange={() => toggleSelected(value)}
|
|
101
101
|
/>
|
|
102
|
-
<label
|
|
102
|
+
<label className="form-check-label fw-normal" for={`${columnId}-${value}`}>
|
|
103
103
|
{renderValueLabel({
|
|
104
104
|
value,
|
|
105
105
|
isSelected,
|
|
@@ -43,7 +43,7 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
43
43
|
<Dropdown align="end">
|
|
44
44
|
<Dropdown.Toggle
|
|
45
45
|
variant="link"
|
|
46
|
-
|
|
46
|
+
className={clsx(
|
|
47
47
|
'text-muted p-0',
|
|
48
48
|
hasActiveFilter && (isInvalid ? 'text-warning' : 'text-primary'),
|
|
49
49
|
)}
|
|
@@ -52,7 +52,7 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
52
52
|
title={`Filter ${label.toLowerCase()}`}
|
|
53
53
|
>
|
|
54
54
|
<i
|
|
55
|
-
|
|
55
|
+
className={clsx(
|
|
56
56
|
'bi',
|
|
57
57
|
isInvalid
|
|
58
58
|
? 'bi-exclamation-triangle'
|
|
@@ -63,18 +63,15 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
63
63
|
aria-hidden="true"
|
|
64
64
|
/>
|
|
65
65
|
</Dropdown.Toggle>
|
|
66
|
-
<Dropdown.Menu
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<div class="p-3" style={{ minWidth: '240px' }}>
|
|
71
|
-
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
72
|
-
<label class="form-label fw-semibold mb-0" id={`${columnId}-filter-label`}>
|
|
66
|
+
<Dropdown.Menu className="p-0">
|
|
67
|
+
<div className="p-3" style={{ minWidth: '240px' }}>
|
|
68
|
+
<div className="d-flex align-items-center justify-content-between mb-2">
|
|
69
|
+
<label className="form-label fw-semibold mb-0" id={`${columnId}-filter-label`}>
|
|
73
70
|
{label}
|
|
74
71
|
</label>
|
|
75
72
|
<button
|
|
76
73
|
type="button"
|
|
77
|
-
|
|
74
|
+
className={clsx(
|
|
78
75
|
'btn btn-link btn-sm text-decoration-none',
|
|
79
76
|
!hasActiveFilter && 'invisible',
|
|
80
77
|
)}
|
|
@@ -87,7 +84,7 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
87
84
|
</div>
|
|
88
85
|
<input
|
|
89
86
|
type="text"
|
|
90
|
-
|
|
87
|
+
className={clsx('form-control form-control-sm', isInvalid && 'is-invalid')}
|
|
91
88
|
placeholder="e.g., >0, <5, =10"
|
|
92
89
|
aria-labelledby={`${columnId}-filter-label`}
|
|
93
90
|
value={filterValue}
|
|
@@ -102,19 +99,19 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
102
99
|
onClick={(e) => e.stopPropagation()}
|
|
103
100
|
/>
|
|
104
101
|
{isInvalid && (
|
|
105
|
-
<div
|
|
102
|
+
<div className="invalid-feedback d-block">
|
|
106
103
|
Invalid filter format. Use operators like <code>>5</code> or <code><=10</code>
|
|
107
104
|
</div>
|
|
108
105
|
)}
|
|
109
106
|
{!isInvalid && (
|
|
110
|
-
<small
|
|
107
|
+
<small className="form-text text-nowrap" id={`${columnId}-filter-description`}>
|
|
111
108
|
Operators: <code><</code>, <code>></code>, <code><=</code>,{' '}
|
|
112
109
|
<code>>=</code>, <code>=</code>
|
|
113
110
|
</small>
|
|
114
111
|
)}
|
|
115
|
-
<div
|
|
112
|
+
<div className="form-check mt-2">
|
|
116
113
|
<input
|
|
117
|
-
|
|
114
|
+
className="form-check-input"
|
|
118
115
|
type="checkbox"
|
|
119
116
|
checked={emptyOnly}
|
|
120
117
|
id={`${columnId}-empty-filter`}
|
|
@@ -126,7 +123,7 @@ export function NumericInputColumnFilter<TData, TValue>({
|
|
|
126
123
|
);
|
|
127
124
|
}}
|
|
128
125
|
/>
|
|
129
|
-
<label
|
|
126
|
+
<label className="form-check-label" for={`${columnId}-empty-filter`}>
|
|
130
127
|
Empty values
|
|
131
128
|
</label>
|
|
132
129
|
</div>
|
|
@@ -123,7 +123,7 @@ export function PresetFilterDropdown<OptionName extends string, TData>({
|
|
|
123
123
|
return (
|
|
124
124
|
<Dropdown as={ButtonGroup}>
|
|
125
125
|
<Dropdown.Toggle variant="tanstack-table">
|
|
126
|
-
<i
|
|
126
|
+
<i className="bi bi-funnel me-2" aria-hidden="true" />
|
|
127
127
|
{label}: {displayLabel}
|
|
128
128
|
</Dropdown.Toggle>
|
|
129
129
|
<Dropdown.Menu>
|
|
@@ -137,7 +137,7 @@ export function PresetFilterDropdown<OptionName extends string, TData>({
|
|
|
137
137
|
active={isSelected}
|
|
138
138
|
onClick={() => handleOptionClick(optionName as OptionName)}
|
|
139
139
|
>
|
|
140
|
-
<i
|
|
140
|
+
<i className={`bi ${isSelected ? 'bi-check-circle-fill' : 'bi-circle'} me-2`} />
|
|
141
141
|
{optionName}
|
|
142
142
|
</Dropdown.Item>
|
|
143
143
|
);
|
|
@@ -145,7 +145,7 @@ export function PresetFilterDropdown<OptionName extends string, TData>({
|
|
|
145
145
|
{/* Show Custom option only when no preset matches */}
|
|
146
146
|
{selectedOption === null && (
|
|
147
147
|
<Dropdown.Item as="button" type="button" active disabled>
|
|
148
|
-
<i
|
|
148
|
+
<i className="bi bi-check-circle-fill me-2" />
|
|
149
149
|
Custom
|
|
150
150
|
</Dropdown.Item>
|
|
151
151
|
)}
|
|
@@ -43,7 +43,7 @@ function TableCell<RowDataModel>({
|
|
|
43
43
|
tabIndex={0}
|
|
44
44
|
data-grid-cell-row={rowIdx}
|
|
45
45
|
data-grid-cell-col={colIdx}
|
|
46
|
-
|
|
46
|
+
className={clsx(!canSort && !canFilter && 'text-center')}
|
|
47
47
|
style={{
|
|
48
48
|
display: 'flex',
|
|
49
49
|
width: cell.column.getSize(),
|
|
@@ -259,7 +259,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
259
259
|
const totalCount = table.getCoreRowModel().rows.length;
|
|
260
260
|
|
|
261
261
|
return (
|
|
262
|
-
<div style={{ position: 'relative' }}
|
|
262
|
+
<div style={{ position: 'relative' }} className="d-flex flex-column h-100">
|
|
263
263
|
<div
|
|
264
264
|
ref={scrollContainerRef}
|
|
265
265
|
style={{
|
|
@@ -280,13 +280,13 @@ export function TanstackTable<RowDataModel>({
|
|
|
280
280
|
}}
|
|
281
281
|
>
|
|
282
282
|
<table
|
|
283
|
-
|
|
283
|
+
className="table table-hover mb-0"
|
|
284
284
|
style={{ display: 'grid', tableLayout: 'fixed' }}
|
|
285
285
|
aria-label={title}
|
|
286
286
|
role="grid"
|
|
287
287
|
>
|
|
288
288
|
<thead
|
|
289
|
-
|
|
289
|
+
className="position-sticky top-0 w-100 border-top"
|
|
290
290
|
style={{
|
|
291
291
|
display: 'grid',
|
|
292
292
|
zIndex: 1,
|
|
@@ -295,7 +295,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
295
295
|
>
|
|
296
296
|
<tr
|
|
297
297
|
key={leafHeaderGroup.id}
|
|
298
|
-
|
|
298
|
+
className="d-flex w-100"
|
|
299
299
|
style={{ minWidth: `${table.getTotalSize()}px` }}
|
|
300
300
|
>
|
|
301
301
|
{/* Left pinned columns */}
|
|
@@ -342,14 +342,14 @@ export function TanstackTable<RowDataModel>({
|
|
|
342
342
|
{/* Filler to span remaining width */}
|
|
343
343
|
<th
|
|
344
344
|
tabIndex={-1}
|
|
345
|
-
|
|
345
|
+
className="d-flex flex-grow-1 p-0"
|
|
346
346
|
style={{ minWidth: 0 }}
|
|
347
347
|
aria-hidden="true"
|
|
348
348
|
/>
|
|
349
349
|
</tr>
|
|
350
350
|
</thead>
|
|
351
351
|
<tbody
|
|
352
|
-
|
|
352
|
+
className="position-relative w-100"
|
|
353
353
|
style={{
|
|
354
354
|
display: 'grid',
|
|
355
355
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
@@ -368,7 +368,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
368
368
|
key={row.id}
|
|
369
369
|
ref={(node) => rowVirtualizer.measureElement(node)}
|
|
370
370
|
data-index={virtualRow.index}
|
|
371
|
-
|
|
371
|
+
className="d-flex position-absolute w-100"
|
|
372
372
|
style={{
|
|
373
373
|
transform: `translateY(${virtualRow.start}px)`,
|
|
374
374
|
minWidth: `${table.getTotalSize()}px`,
|
|
@@ -428,7 +428,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
428
428
|
{/* Filler to span remaining width */}
|
|
429
429
|
<td
|
|
430
430
|
tabIndex={-1}
|
|
431
|
-
|
|
431
|
+
className="d-flex flex-grow-1 p-0"
|
|
432
432
|
style={{ minWidth: 0 }}
|
|
433
433
|
aria-hidden="true"
|
|
434
434
|
/>
|
|
@@ -442,7 +442,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
442
442
|
{table.getVisibleLeafColumns().length === 0 || displayedCount === 0 ? (
|
|
443
443
|
<div>
|
|
444
444
|
<div
|
|
445
|
-
|
|
445
|
+
className="d-flex flex-column justify-content-center align-items-center p-4"
|
|
446
446
|
style={{
|
|
447
447
|
position: 'absolute',
|
|
448
448
|
top: 0,
|
|
@@ -456,7 +456,7 @@ export function TanstackTable<RowDataModel>({
|
|
|
456
456
|
aria-live="polite"
|
|
457
457
|
>
|
|
458
458
|
<div
|
|
459
|
-
|
|
459
|
+
className="col-lg-6"
|
|
460
460
|
style={{
|
|
461
461
|
// Allow selecting and interacting with the empty state content.
|
|
462
462
|
pointerEvents: 'auto',
|
|
@@ -559,11 +559,11 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
559
559
|
const totalCount = table.getCoreRowModel().rows.length;
|
|
560
560
|
|
|
561
561
|
return (
|
|
562
|
-
<div
|
|
563
|
-
<div
|
|
564
|
-
<div
|
|
562
|
+
<div className={clsx('card d-flex flex-column', className)} {...divProps}>
|
|
563
|
+
<div className="card-header bg-primary text-white">
|
|
564
|
+
<div className="d-flex align-items-center justify-content-between gap-2">
|
|
565
565
|
<div>{title}</div>
|
|
566
|
-
<div
|
|
566
|
+
<div className="d-flex gap-2">
|
|
567
567
|
{headerButtons}
|
|
568
568
|
|
|
569
569
|
{downloadButtonOptions && (
|
|
@@ -577,12 +577,12 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
577
577
|
</div>
|
|
578
578
|
</div>
|
|
579
579
|
</div>
|
|
580
|
-
<div
|
|
581
|
-
<div
|
|
580
|
+
<div className="card-body d-flex flex-row flex-wrap flex-grow-0 align-items-center gap-2">
|
|
581
|
+
<div className="position-relative w-100" style={{ maxWidth: 'min(400px, 100%)' }}>
|
|
582
582
|
<input
|
|
583
583
|
ref={searchInputRef}
|
|
584
584
|
type="text"
|
|
585
|
-
|
|
585
|
+
className="form-control pl-ui-tanstack-table-search-input pl-ui-tanstack-table-focusable-shadow"
|
|
586
586
|
aria-label={globalFilter.placeholder}
|
|
587
587
|
placeholder={globalFilter.placeholder}
|
|
588
588
|
value={inputValue}
|
|
@@ -597,7 +597,7 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
597
597
|
<OverlayTrigger overlay={<Tooltip>Clear search</Tooltip>}>
|
|
598
598
|
<button
|
|
599
599
|
type="button"
|
|
600
|
-
|
|
600
|
+
className="btn btn-floating-icon"
|
|
601
601
|
aria-label="Clear search"
|
|
602
602
|
onClick={() => {
|
|
603
603
|
setInputValue('');
|
|
@@ -605,20 +605,20 @@ export function TanstackTableCard<RowDataModel>({
|
|
|
605
605
|
table.setGlobalFilter('');
|
|
606
606
|
}}
|
|
607
607
|
>
|
|
608
|
-
<i
|
|
608
|
+
<i className="bi bi-x-circle-fill" aria-hidden="true" />
|
|
609
609
|
</button>
|
|
610
610
|
</OverlayTrigger>
|
|
611
611
|
)}
|
|
612
612
|
</div>
|
|
613
|
-
<div
|
|
613
|
+
<div className="d-flex flex-wrap flex-row align-items-center gap-2">
|
|
614
614
|
<ColumnManager table={table} topContent={columnManager?.topContent} />
|
|
615
615
|
{columnManager?.buttons}
|
|
616
616
|
</div>
|
|
617
|
-
<div
|
|
617
|
+
<div className="ms-auto text-muted text-nowrap">
|
|
618
618
|
Showing {displayedCount} of {totalCount} {totalCount === 1 ? singularLabel : pluralLabel}
|
|
619
619
|
</div>
|
|
620
620
|
</div>
|
|
621
|
-
<div
|
|
621
|
+
<div className="flex-grow-1">
|
|
622
622
|
<TanstackTable table={table} title={title} {...tableOptions} />
|
|
623
623
|
</div>
|
|
624
624
|
</div>
|
|
@@ -633,8 +633,8 @@ export function TanstackTableEmptyState({
|
|
|
633
633
|
children: ComponentChildren;
|
|
634
634
|
}) {
|
|
635
635
|
return (
|
|
636
|
-
<div
|
|
637
|
-
<i
|
|
636
|
+
<div className="d-flex flex-column justify-content-center align-items-center text-muted">
|
|
637
|
+
<i className={clsx('bi', iconName, 'display-4 mb-2')} aria-hidden="true" />
|
|
638
638
|
<div>{children}</div>
|
|
639
639
|
</div>
|
|
640
640
|
);
|
|
@@ -54,22 +54,22 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
|
-
<div
|
|
57
|
+
<div className="btn-group">
|
|
58
58
|
<button
|
|
59
59
|
type="button"
|
|
60
60
|
data-bs-toggle="dropdown"
|
|
61
61
|
aria-expanded="false"
|
|
62
62
|
aria-haspopup="true"
|
|
63
63
|
aria-label={`Download ${pluralLabel} data in various formats`}
|
|
64
|
-
|
|
64
|
+
className="btn btn-light btn-sm dropdown-toggle"
|
|
65
65
|
>
|
|
66
|
-
<i aria-hidden="true"
|
|
67
|
-
<span
|
|
66
|
+
<i aria-hidden="true" className="pe-2 bi bi-download" />
|
|
67
|
+
<span className="d-none d-sm-inline">Download</span>
|
|
68
68
|
</button>
|
|
69
|
-
<ul
|
|
69
|
+
<ul className="dropdown-menu" role="menu" aria-label="Download options">
|
|
70
70
|
<li role="presentation">
|
|
71
71
|
<button
|
|
72
|
-
|
|
72
|
+
className="dropdown-item"
|
|
73
73
|
type="button"
|
|
74
74
|
role="menuitem"
|
|
75
75
|
aria-label={`Download all ${pluralLabel} as CSV file`}
|
|
@@ -81,7 +81,7 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
81
81
|
</li>
|
|
82
82
|
<li role="presentation">
|
|
83
83
|
<button
|
|
84
|
-
|
|
84
|
+
className="dropdown-item"
|
|
85
85
|
type="button"
|
|
86
86
|
role="menuitem"
|
|
87
87
|
aria-label={`Download all ${pluralLabel} as JSON file`}
|
|
@@ -95,7 +95,7 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
95
95
|
<>
|
|
96
96
|
<li role="presentation">
|
|
97
97
|
<button
|
|
98
|
-
|
|
98
|
+
className="dropdown-item"
|
|
99
99
|
type="button"
|
|
100
100
|
role="menuitem"
|
|
101
101
|
aria-label={`Download selected ${pluralLabel} as CSV file`}
|
|
@@ -108,7 +108,7 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
108
108
|
</li>
|
|
109
109
|
<li role="presentation">
|
|
110
110
|
<button
|
|
111
|
-
|
|
111
|
+
className="dropdown-item"
|
|
112
112
|
type="button"
|
|
113
113
|
role="menuitem"
|
|
114
114
|
aria-label={`Download selected ${pluralLabel} as JSON file`}
|
|
@@ -123,7 +123,7 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
123
123
|
)}
|
|
124
124
|
<li role="presentation">
|
|
125
125
|
<button
|
|
126
|
-
|
|
126
|
+
className="dropdown-item"
|
|
127
127
|
type="button"
|
|
128
128
|
role="menuitem"
|
|
129
129
|
aria-label={`Download filtered ${pluralLabel} as CSV file`}
|
|
@@ -136,7 +136,7 @@ export function TanstackTableDownloadButton<RowDataModel>({
|
|
|
136
136
|
</li>
|
|
137
137
|
<li role="presentation">
|
|
138
138
|
<button
|
|
139
|
-
|
|
139
|
+
className="dropdown-item"
|
|
140
140
|
type="button"
|
|
141
141
|
role="menuitem"
|
|
142
142
|
aria-label={`Download filtered ${pluralLabel} as JSON file`}
|