@prairielearn/ui 1.1.2 → 1.3.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/CategoricalColumnFilter.d.ts.map +1 -1
  3. package/dist/components/CategoricalColumnFilter.js +13 -5
  4. package/dist/components/CategoricalColumnFilter.js.map +1 -1
  5. package/dist/components/ColumnManager.d.ts +2 -1
  6. package/dist/components/ColumnManager.d.ts.map +1 -1
  7. package/dist/components/ColumnManager.js +13 -28
  8. package/dist/components/ColumnManager.js.map +1 -1
  9. package/dist/components/MultiSelectColumnFilter.d.ts +25 -0
  10. package/dist/components/MultiSelectColumnFilter.d.ts.map +1 -0
  11. package/dist/components/MultiSelectColumnFilter.js +41 -0
  12. package/dist/components/MultiSelectColumnFilter.js.map +1 -0
  13. package/dist/components/NumericInputColumnFilter.d.ts +42 -0
  14. package/dist/components/NumericInputColumnFilter.d.ts.map +1 -0
  15. package/dist/components/NumericInputColumnFilter.js +79 -0
  16. package/dist/components/NumericInputColumnFilter.js.map +1 -0
  17. package/dist/components/TanstackTable.css +49 -0
  18. package/dist/components/TanstackTable.d.ts +8 -1
  19. package/dist/components/TanstackTable.d.ts.map +1 -1
  20. package/dist/components/TanstackTable.js +78 -46
  21. package/dist/components/TanstackTable.js.map +1 -1
  22. package/dist/components/TanstackTableDownloadButton.d.ts.map +1 -1
  23. package/dist/components/TanstackTableDownloadButton.js +3 -1
  24. package/dist/components/TanstackTableDownloadButton.js.map +1 -1
  25. package/dist/components/useShiftClickCheckbox.d.ts +26 -0
  26. package/dist/components/useShiftClickCheckbox.d.ts.map +1 -0
  27. package/dist/components/useShiftClickCheckbox.js +59 -0
  28. package/dist/components/useShiftClickCheckbox.js.map +1 -0
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -1
  32. package/dist/index.js.map +1 -1
  33. package/package.json +6 -4
  34. package/src/components/CategoricalColumnFilter.tsx +57 -27
  35. package/src/components/ColumnManager.tsx +32 -57
  36. package/src/components/MultiSelectColumnFilter.tsx +103 -0
  37. package/src/components/NumericInputColumnFilter.test.ts +102 -0
  38. package/src/components/NumericInputColumnFilter.tsx +153 -0
  39. package/src/components/TanstackTable.css +49 -0
  40. package/src/components/TanstackTable.tsx +193 -116
  41. package/src/components/TanstackTableDownloadButton.tsx +27 -1
  42. package/src/components/useShiftClickCheckbox.tsx +67 -0
  43. package/src/index.ts +12 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @prairielearn/ui
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3b54dda: Refine styling of `<CategoricalColumnFilter>`
8
+
9
+ ### Patch Changes
10
+
11
+ - 9f5a05f: Improve UI of `TanstackTable` and `TanstackTableCard`
12
+
13
+ ## 1.2.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 50dbe96: - Add optional header labels / max height for column manager
18
+ - Add new option for buttons in next to the View column manager button
19
+ - add MultiSelectColumnFilter
20
+ - NumericInputColumnFilter
21
+ - add useShiftClickCheckbox hook + update table/buttons to support row selection
22
+
3
23
  ## 1.1.2
4
24
 
5
25
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"CategoricalColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAqB,MAAM,eAAe,CAAC;AAiB5D;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,SAAS,GAAG,EAAE,EAAE,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAA0C,EAC1C,kBAAkB,EAClB,qBAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,CAAC,CAAC;IACnB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;IACrF,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CACrD,eA8FA"}
1
+ {"version":3,"file":"CategoricalColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAqB,MAAM,eAAe,CAAC;AAiB5D;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,SAAS,GAAG,EAAE,EAAE,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAA0C,EAC1C,kBAAkB,EAClB,qBAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,CAAC,CAAC;IACnB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;IACrF,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CACrD,eA4HA"}
@@ -41,12 +41,20 @@ export function CategoricalColumnFilter({ columnId, columnLabel, allColumnValues
41
41
  }
42
42
  apply(mode, set);
43
43
  };
44
- return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: "text-muted p-0 ms-2", id: `filter-${columnId}`, "aria-label": `Filter ${columnLabel.toLowerCase()}`, title: `Filter ${columnLabel.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel'), "aria-hidden": "true" }) }), _jsx(Dropdown.Menu, { class: "p-0", children: _jsxs("div", { class: "p-3", children: [_jsxs("div", { class: "d-flex align-items-center justify-content-between mb-2", children: [_jsx("div", { class: "fw-semibold", children: columnLabel }), _jsx("button", { type: "button", class: "btn btn-link btn-sm text-decoration-none", onClick: () => apply(mode, new Set()), children: "Clear" })] }), _jsxs("div", { class: "btn-group w-100 mb-2", role: "group", "aria-label": "Include or exclude values", children: [_jsx("button", { type: "button", class: clsx('btn', mode === 'include' ? 'btn-primary' : 'btn-outline-secondary'), onClick: () => apply('include', selected), children: "Include" }), _jsx("button", { type: "button", class: clsx('btn', mode === 'exclude' ? 'btn-primary' : 'btn-outline-secondary'), onClick: () => apply('exclude', selected), children: "Exclude" })] }), _jsx("div", { class: "list-group list-group-flush", children: allColumnValues.map((value) => {
45
- const isSelected = selected.has(value);
46
- return (_jsxs("div", { class: "list-group-item d-flex align-items-center gap-3", children: [_jsx("input", { class: "form-check-input", type: "checkbox", checked: isSelected, id: `${columnId}-${value}`, onChange: () => toggleSelected(value) }), _jsx("label", { class: "form-check-label", for: `${columnId}-${value}`, children: renderValueLabel({
44
+ return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: "text-muted p-0", id: `filter-${columnId}`, "aria-label": `Filter ${columnLabel.toLowerCase()}`, title: `Filter ${columnLabel.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel'), "aria-hidden": "true" }) }), _jsxs(Dropdown.Menu, { class: "p-0", children: [_jsxs("div", { class: "p-3 pb-0", children: [_jsxs("div", { class: "d-flex align-items-center justify-content-between mb-2", children: [_jsx("div", { class: "fw-semibold", children: columnLabel }), _jsx("button", { type: "button", class: clsx('btn btn-link btn-sm text-decoration-none', {
45
+ // Hide the clear button if no filters are applied.
46
+ // Use `visibility` instead of conditional rendering to avoid layout shift.
47
+ invisible: selected.size === 0 && mode === 'include',
48
+ }), onClick: () => apply('include', new Set()), children: "Clear" })] }), _jsxs("div", { class: "btn-group btn-group-sm w-100 mb-2", children: [_jsx("input", { type: "radio", class: "btn-check", name: `filter-${columnId}-options`, id: `filter-${columnId}-include`, autocomplete: "off", checked: mode === 'include', onChange: () => apply('include', selected) }), _jsx("label", { class: "btn btn-outline-primary", for: `filter-${columnId}-include`, children: _jsxs("span", { class: "text-nowrap", children: [mode === 'include' && _jsx("i", { class: "bi bi-check-lg me-1", "aria-hidden": "true" }), "Include"] }) }), _jsx("input", { type: "radio", class: "btn-check", name: `filter-${columnId}-options`, id: `filter-${columnId}-exclude`, autocomplete: "off", checked: mode === 'exclude', onChange: () => apply('exclude', selected) }), _jsx("label", { class: "btn btn-outline-primary", for: `filter-${columnId}-exclude`, children: _jsxs("span", { class: "text-nowrap", children: [mode === 'exclude' && _jsx("i", { class: "bi bi-check-lg me-1", "aria-hidden": "true" }), "Exclude"] }) })] })] }), _jsx("div", { class: "list-group list-group-flush", style: {
49
+ // This is needed to prevent the last item's background from covering
50
+ // the dropdown's border radius.
51
+ '--bs-list-group-bg': 'transparent',
52
+ }, children: allColumnValues.map((value) => {
53
+ const isSelected = selected.has(value);
54
+ return (_jsx("div", { class: "list-group-item d-flex align-items-center gap-3", children: _jsxs("div", { class: "form-check", children: [_jsx("input", { class: "form-check-input", type: "checkbox", checked: isSelected, id: `${columnId}-${value}`, onChange: () => toggleSelected(value) }), _jsx("label", { class: "form-check-label", for: `${columnId}-${value}`, children: renderValueLabel({
47
55
  value,
48
56
  isSelected,
49
- }) })] }, value));
50
- }) })] }) })] }));
57
+ }) })] }) }, value));
58
+ }) })] })] }));
51
59
  }
52
60
  //# sourceMappingURL=CategoricalColumnFilter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CategoricalColumnFilter.js","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport { type JSX, useMemo, useState } from 'preact/compat';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\nfunction computeSelected<T extends readonly any[]>(\n allStatusValues: T,\n mode: 'include' | 'exclude',\n selected: Set<T[number]>,\n) {\n if (mode === 'include') {\n return selected;\n }\n return new Set(allStatusValues.filter((s) => !selected.has(s)));\n}\n\nfunction defaultRenderValueLabel<T>({ value }: { value: T }) {\n return <span>{String(value)}</span>;\n}\n/**\n * A component that allows the user to filter a categorical column. State is managed by the parent component.\n * The filter mode always defaults to \"include\".\n *\n * @param params\n * @param params.columnId - The ID of the column\n * @param params.columnLabel - The label of the column, e.g. \"Status\"\n * @param params.allColumnValues - The values to filter by\n * @param params.renderValueLabel - A function that renders the label for a value\n * @param params.columnValuesFilter - The current state of the column filter\n * @param params.setColumnValuesFilter - A function that sets the state of the column filter\n */\nexport function CategoricalColumnFilter<T extends readonly any[]>({\n columnId,\n columnLabel,\n allColumnValues,\n renderValueLabel = defaultRenderValueLabel,\n columnValuesFilter,\n setColumnValuesFilter,\n}: {\n columnId: string;\n columnLabel: string;\n allColumnValues: T;\n renderValueLabel?: (props: { value: T[number]; isSelected: boolean }) => JSX.Element;\n columnValuesFilter: T[number][];\n setColumnValuesFilter: (value: T[number][]) => void;\n}) {\n const [mode, setMode] = useState<'include' | 'exclude'>('include');\n\n const selected = useMemo(\n () => computeSelected(allColumnValues, mode, new Set(columnValuesFilter)),\n [mode, columnValuesFilter, allColumnValues],\n );\n\n const apply = (newMode: 'include' | 'exclude', newSelected: Set<T[number]>) => {\n const selected = computeSelected(allColumnValues, newMode, newSelected);\n setMode(newMode);\n setColumnValuesFilter(Array.from(selected));\n };\n\n const toggleSelected = (value: T[number]) => {\n const set = new Set(selected);\n if (set.has(value)) {\n set.delete(value);\n } else {\n set.add(value);\n }\n apply(mode, set);\n };\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class=\"text-muted p-0 ms-2\"\n id={`filter-${columnId}`}\n aria-label={`Filter ${columnLabel.toLowerCase()}`}\n title={`Filter ${columnLabel.toLowerCase()}`}\n >\n <i\n class={clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu class=\"p-0\">\n <div class=\"p-3\">\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <div class=\"fw-semibold\">{columnLabel}</div>\n <button\n type=\"button\"\n class=\"btn btn-link btn-sm text-decoration-none\"\n onClick={() => apply(mode, new Set())}\n >\n Clear\n </button>\n </div>\n\n <div class=\"btn-group w-100 mb-2\" role=\"group\" aria-label=\"Include or exclude values\">\n <button\n type=\"button\"\n class={clsx('btn', mode === 'include' ? 'btn-primary' : 'btn-outline-secondary')}\n onClick={() => apply('include', selected)}\n >\n Include\n </button>\n <button\n type=\"button\"\n class={clsx('btn', mode === 'exclude' ? 'btn-primary' : 'btn-outline-secondary')}\n onClick={() => apply('exclude', selected)}\n >\n Exclude\n </button>\n </div>\n\n <div class=\"list-group list-group-flush\">\n {allColumnValues.map((value) => {\n const isSelected = selected.has(value);\n return (\n <div key={value} class=\"list-group-item d-flex align-items-center gap-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n checked={isSelected}\n id={`${columnId}-${value}`}\n onChange={() => toggleSelected(value)}\n />\n <label class=\"form-check-label\" for={`${columnId}-${value}`}>\n {renderValueLabel({\n value,\n isSelected,\n })}\n </label>\n </div>\n );\n })}\n </div>\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
1
+ {"version":3,"file":"CategoricalColumnFilter.js","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport { type JSX, useMemo, useState } from 'preact/compat';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\nfunction computeSelected<T extends readonly any[]>(\n allStatusValues: T,\n mode: 'include' | 'exclude',\n selected: Set<T[number]>,\n) {\n if (mode === 'include') {\n return selected;\n }\n return new Set(allStatusValues.filter((s) => !selected.has(s)));\n}\n\nfunction defaultRenderValueLabel<T>({ value }: { value: T }) {\n return <span>{String(value)}</span>;\n}\n/**\n * A component that allows the user to filter a categorical column. State is managed by the parent component.\n * The filter mode always defaults to \"include\".\n *\n * @param params\n * @param params.columnId - The ID of the column\n * @param params.columnLabel - The label of the column, e.g. \"Status\"\n * @param params.allColumnValues - The values to filter by\n * @param params.renderValueLabel - A function that renders the label for a value\n * @param params.columnValuesFilter - The current state of the column filter\n * @param params.setColumnValuesFilter - A function that sets the state of the column filter\n */\nexport function CategoricalColumnFilter<T extends readonly any[]>({\n columnId,\n columnLabel,\n allColumnValues,\n renderValueLabel = defaultRenderValueLabel,\n columnValuesFilter,\n setColumnValuesFilter,\n}: {\n columnId: string;\n columnLabel: string;\n allColumnValues: T;\n renderValueLabel?: (props: { value: T[number]; isSelected: boolean }) => JSX.Element;\n columnValuesFilter: T[number][];\n setColumnValuesFilter: (value: T[number][]) => void;\n}) {\n const [mode, setMode] = useState<'include' | 'exclude'>('include');\n\n const selected = useMemo(\n () => computeSelected(allColumnValues, mode, new Set(columnValuesFilter)),\n [mode, columnValuesFilter, allColumnValues],\n );\n\n const apply = (newMode: 'include' | 'exclude', newSelected: Set<T[number]>) => {\n const selected = computeSelected(allColumnValues, newMode, newSelected);\n setMode(newMode);\n setColumnValuesFilter(Array.from(selected));\n };\n\n const toggleSelected = (value: T[number]) => {\n const set = new Set(selected);\n if (set.has(value)) {\n set.delete(value);\n } else {\n set.add(value);\n }\n apply(mode, set);\n };\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class=\"text-muted p-0\"\n id={`filter-${columnId}`}\n aria-label={`Filter ${columnLabel.toLowerCase()}`}\n title={`Filter ${columnLabel.toLowerCase()}`}\n >\n <i\n class={clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu class=\"p-0\">\n <div class=\"p-3 pb-0\">\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <div class=\"fw-semibold\">{columnLabel}</div>\n <button\n type=\"button\"\n class={clsx('btn btn-link btn-sm text-decoration-none', {\n // Hide the clear button if no filters are applied.\n // Use `visibility` instead of conditional rendering to avoid layout shift.\n invisible: selected.size === 0 && mode === 'include',\n })}\n onClick={() => apply('include', new Set())}\n >\n Clear\n </button>\n </div>\n\n <div class=\"btn-group btn-group-sm w-100 mb-2\">\n <input\n type=\"radio\"\n class=\"btn-check\"\n name={`filter-${columnId}-options`}\n id={`filter-${columnId}-include`}\n autocomplete=\"off\"\n checked={mode === 'include'}\n onChange={() => apply('include', selected)}\n />\n <label class=\"btn btn-outline-primary\" for={`filter-${columnId}-include`}>\n <span class=\"text-nowrap\">\n {mode === 'include' && <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\" />}\n Include\n </span>\n </label>\n\n <input\n type=\"radio\"\n class=\"btn-check\"\n name={`filter-${columnId}-options`}\n id={`filter-${columnId}-exclude`}\n autocomplete=\"off\"\n checked={mode === 'exclude'}\n onChange={() => apply('exclude', selected)}\n />\n <label class=\"btn btn-outline-primary\" for={`filter-${columnId}-exclude`}>\n <span class=\"text-nowrap\">\n {mode === 'exclude' && <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\" />}\n Exclude\n </span>\n </label>\n </div>\n </div>\n\n <div\n class=\"list-group list-group-flush\"\n style={{\n // This is needed to prevent the last item's background from covering\n // the dropdown's border radius.\n '--bs-list-group-bg': 'transparent',\n }}\n >\n {allColumnValues.map((value) => {\n const isSelected = selected.has(value);\n return (\n <div key={value} class=\"list-group-item d-flex align-items-center gap-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n checked={isSelected}\n id={`${columnId}-${value}`}\n onChange={() => toggleSelected(value)}\n />\n <label class=\"form-check-label\" for={`${columnId}-${value}`}>\n {renderValueLabel({\n value,\n isSelected,\n })}\n </label>\n </div>\n </div>\n );\n })}\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { type Table } from '@tanstack/react-table';
2
- export declare function ColumnManager<RowDataModel>({ table }: {
2
+ export declare function ColumnManager<RowDataModel>({ table, id, }: {
3
3
  table: Table<RowDataModel>;
4
+ id: string;
4
5
  }): import("preact/compat").JSX.Element;
5
6
  //# sourceMappingURL=ColumnManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ColumnManager.d.ts","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AA6FhE,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;CAAE,uCA2HpF"}
1
+ {"version":3,"file":"ColumnManager.d.ts","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AA4DhE,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAC1C,KAAK,EACL,EAAE,GACH,EAAE;IACD,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ,uCA6HA"}
@@ -3,35 +3,18 @@ import {} from '@tanstack/react-table';
3
3
  import { useEffect, useRef, useState } from 'preact/compat';
4
4
  import Button from 'react-bootstrap/Button';
5
5
  import Dropdown from 'react-bootstrap/Dropdown';
6
- import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
7
- import Tooltip from 'react-bootstrap/Tooltip';
8
- function ColumnMenuItem({ column, hidePinButton = false, onTogglePin, onClearElementFocus, }) {
9
- const pinButtonRef = useRef(null);
6
+ function ColumnMenuItem({ column, hidePinButton = false, onTogglePin, }) {
10
7
  if (!column.getCanHide() && !column.getCanPin())
11
8
  return null;
12
- const header = typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
13
- return (_jsxs(Dropdown.Item, { as: "div", class: "px-2 py-1 d-flex align-items-center justify-content-between", onKeyDown: onClearElementFocus, children: [_jsxs("label", { class: "form-check me-auto text-nowrap d-flex align-items-stretch", children: [_jsx(OverlayTrigger, { placement: "top", overlay: _jsx(Tooltip, { children: column.getIsVisible() ? 'Hide column' : 'Show column' }), children: _jsx("input", { type: "checkbox", class: "form-check-input", checked: column.getIsVisible(), disabled: !column.getCanHide(), "aria-label": column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`, "aria-describedby": `${column.id}-label`, onChange: column.getToggleVisibilityHandler() }) }), _jsx("span", { class: "form-check-label ms-2", id: `${column.id}-label`, children: header })] }), column.getCanPin() && !hidePinButton && (_jsx("button", { ref: pinButtonRef, type: "button",
9
+ // Use meta.label if available, otherwise fall back to header or column.id
10
+ const header = column.columnDef.meta?.label ??
11
+ (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);
12
+ return (_jsxs("div", { class: "px-2 py-1 d-flex align-items-center justify-content-between", children: [_jsxs("label", { class: "form-check me-auto text-nowrap d-flex align-items-stretch", children: [_jsx("input", { type: "checkbox", class: "form-check-input", checked: column.getIsVisible(), disabled: !column.getCanHide(), "aria-label": column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`, "aria-describedby": `${column.id}-label`, onChange: column.getToggleVisibilityHandler() }), _jsx("span", { class: "form-check-label ms-2", id: `${column.id}-label`, children: header })] }), column.getCanPin() && !hidePinButton && (_jsx("button", { type: "button",
14
13
  // Since the HTML changes, but we want to refocus the pin button, we track
15
14
  // the active pin button and refocuses it when the column manager is rerendered.
16
- id: `${column.id}-pin`, class: "btn btn-sm btn-ghost ms-2", "aria-label": column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`, title: column.getIsPinned() ? 'Unfreeze column' : 'Freeze column', "data-bs-toggle": "tooltip", tabIndex: 0, onKeyDown: (e) => {
17
- if (!pinButtonRef.current) {
18
- throw new Error('pinButtonRef.current is null');
19
- }
20
- if (e.key === 'Enter' || e.key === ' ') {
21
- e.preventDefault();
22
- onTogglePin(column.id);
23
- return;
24
- }
25
- },
26
- // Instead, use the arrow keys to move between interactive elements in each menu item.
27
- onClick: () => {
28
- if (!pinButtonRef.current) {
29
- throw new Error('pinButtonRef.current is null');
30
- }
31
- onTogglePin(column.id);
32
- }, children: _jsx("i", { class: `bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`, "aria-hidden": "true" }) }))] }, column.id));
15
+ id: `${column.id}-pin`, class: "btn btn-sm btn-ghost ms-2", "aria-label": column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`, title: column.getIsPinned() ? 'Unfreeze column' : 'Freeze column', "data-bs-toggle": "tooltip", onClick: () => onTogglePin(column.id), children: _jsx("i", { class: `bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`, "aria-hidden": "true" }) }))] }, column.id));
33
16
  }
34
- export function ColumnManager({ table }) {
17
+ export function ColumnManager({ table, id, }) {
35
18
  const [activeElementId, setActiveElementId] = useState(null);
36
19
  const [dropdownOpen, setDropdownOpen] = useState(false);
37
20
  const menuRef = useRef(null);
@@ -73,14 +56,16 @@ export function ColumnManager({ table }) {
73
56
  if (menuRef.current && !menuRef.current.contains(e.target)) {
74
57
  setDropdownOpen(false);
75
58
  }
76
- }, children: [_jsxs(Dropdown.Toggle, { variant: "outline-secondary", id: "column-manager-button", children: [_jsx("i", { class: "bi bi-view-list me-2", "aria-hidden": "true" }), "View"] }), _jsxs(Dropdown.Menu, { children: [pinnedColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { class: "px-2 py-1 text-muted small", role: "presentation", children: "Frozen columns" }), _jsx("div", { role: "group", children: pinnedColumns.map((column, index) => {
77
- return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== pinnedColumns.length - 1, onTogglePin: handleTogglePin, onClearElementFocus: () => setActiveElementId(null) }, column.id));
59
+ }, children: [_jsxs(Dropdown.Toggle, { variant: "tanstack-table", id: id,
60
+ // eslint-disable-next-line @eslint-react/no-forbidden-props
61
+ className: "tanstack-table-focusable-shadow", children: [_jsx("i", { class: "bi bi-view-list me-2", "aria-hidden": "true" }), " View", ' '] }), _jsxs(Dropdown.Menu, { style: { maxHeight: '60vh', overflowY: 'auto' }, children: [pinnedColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { class: "px-2 py-1 text-muted small", role: "presentation", children: "Frozen columns" }), _jsx("div", { role: "group", children: pinnedColumns.map((column, index) => {
62
+ return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== pinnedColumns.length - 1, onTogglePin: handleTogglePin }, column.id));
78
63
  }) }), _jsx(Dropdown.Divider, {})] })), unpinnedColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { role: "group", children: unpinnedColumns.map((column, index) => {
79
- return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== 0, onTogglePin: handleTogglePin, onClearElementFocus: () => setActiveElementId(null) }, column.id));
64
+ return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== 0, onTogglePin: handleTogglePin }, column.id));
80
65
  }) }), showResetButton && _jsx(Dropdown.Divider, {})] })), showResetButton && (_jsx("div", { class: "px-2 py-1", children: _jsxs(Button, { variant: "secondary", size: "sm", class: "w-100", "aria-label": "Reset all columns to default visibility and pinning", onClick: () => {
81
66
  table.resetColumnVisibility();
82
67
  table.resetColumnPinning();
83
- setActiveElementId('column-manager-button');
68
+ setActiveElementId(id);
84
69
  }, children: [_jsx("i", { class: "bi bi-arrow-counterclockwise me-2", "aria-hidden": "true" }), "Reset view"] }) }))] })] }));
85
70
  }
86
71
  //# sourceMappingURL=ColumnManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ColumnManager.js","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type Column, type Table } from '@tanstack/react-table';\nimport { useEffect, useRef, useState } from 'preact/compat';\nimport Button from 'react-bootstrap/Button';\nimport Dropdown from 'react-bootstrap/Dropdown';\nimport OverlayTrigger from 'react-bootstrap/OverlayTrigger';\nimport Tooltip from 'react-bootstrap/Tooltip';\n\ninterface ColumnMenuItemProps<RowDataModel> {\n column: Column<RowDataModel>;\n hidePinButton: boolean;\n onTogglePin: (columnId: string) => void;\n onClearElementFocus: () => void;\n}\n\nfunction ColumnMenuItem<RowDataModel>({\n column,\n hidePinButton = false,\n onTogglePin,\n onClearElementFocus,\n}: ColumnMenuItemProps<RowDataModel>) {\n const pinButtonRef = useRef<HTMLButtonElement>(null);\n\n if (!column.getCanHide() && !column.getCanPin()) return null;\n\n const header = typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;\n\n return (\n <Dropdown.Item\n key={column.id}\n as=\"div\"\n class=\"px-2 py-1 d-flex align-items-center justify-content-between\"\n onKeyDown={onClearElementFocus}\n >\n <label class=\"form-check me-auto text-nowrap d-flex align-items-stretch\">\n <OverlayTrigger\n placement=\"top\"\n overlay={<Tooltip>{column.getIsVisible() ? 'Hide column' : 'Show column'}</Tooltip>}\n >\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n checked={column.getIsVisible()}\n disabled={!column.getCanHide()}\n aria-label={\n column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`\n }\n aria-describedby={`${column.id}-label`}\n onChange={column.getToggleVisibilityHandler()}\n />\n </OverlayTrigger>\n <span class=\"form-check-label ms-2\" id={`${column.id}-label`}>\n {header}\n </span>\n </label>\n {column.getCanPin() && !hidePinButton && (\n <button\n ref={pinButtonRef}\n type=\"button\"\n // Since the HTML changes, but we want to refocus the pin button, we track\n // the active pin button and refocuses it when the column manager is rerendered.\n id={`${column.id}-pin`}\n class=\"btn btn-sm btn-ghost ms-2\"\n aria-label={\n column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`\n }\n title={column.getIsPinned() ? 'Unfreeze column' : 'Freeze column'}\n data-bs-toggle=\"tooltip\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (!pinButtonRef.current) {\n throw new Error('pinButtonRef.current is null');\n }\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onTogglePin(column.id);\n return;\n }\n }}\n // Instead, use the arrow keys to move between interactive elements in each menu item.\n onClick={() => {\n if (!pinButtonRef.current) {\n throw new Error('pinButtonRef.current is null');\n }\n onTogglePin(column.id);\n }}\n >\n <i class={`bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`} aria-hidden=\"true\" />\n </button>\n )}\n </Dropdown.Item>\n );\n}\n\nexport function ColumnManager<RowDataModel>({ table }: { table: Table<RowDataModel> }) {\n const [activeElementId, setActiveElementId] = useState<string | null>(null);\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const handleTogglePin = (columnId: string) => {\n const currentLeft = table.getState().columnPinning.left ?? [];\n const isPinned = currentLeft.includes(columnId);\n let newLeft: string[];\n if (isPinned) {\n newLeft = currentLeft.filter((id) => id !== columnId);\n } else {\n const columnOrder = table.getAllLeafColumns().map((c) => c.id);\n const newPinned = new Set([...currentLeft, columnId]);\n newLeft = columnOrder.filter((id) => newPinned.has(id));\n }\n table.setColumnPinning({ left: newLeft, right: [] });\n setActiveElementId(`${columnId}-pin`);\n };\n\n const isVisibilityChanged = Object.entries(table.getState().columnVisibility).some(\n ([key, value]) => {\n return value !== table.initialState.columnVisibility[key];\n },\n );\n\n const initialPinning = table.initialState.columnPinning.left ?? [];\n const currentPinning = table.getState().columnPinning.left ?? [];\n const isPinningChanged =\n initialPinning.length !== currentPinning.length ||\n initialPinning.some((id) => !currentPinning.includes(id));\n const showResetButton = isVisibilityChanged || isPinningChanged;\n\n const pinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() === 'left');\n const unpinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() !== 'left');\n\n useEffect(() => {\n // When we use the pin or reset button, we want to refocus to another element.\n // We want this in a useEffect so that this code runs after the component re-renders.\n\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-event-handler\n if (activeElementId) {\n document.getElementById(activeElementId)?.focus();\n }\n }, [activeElementId]);\n\n return (\n <Dropdown\n ref={menuRef}\n autoClose=\"outside\"\n show={dropdownOpen}\n onToggle={(isOpen, _meta) => setDropdownOpen(isOpen)}\n onFocusOut={(e: FocusEvent) => {\n // Since we aren't using role=\"menu\", we need to manually close the dropdown when focus leaves.\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n }}\n >\n <Dropdown.Toggle variant=\"outline-secondary\" id=\"column-manager-button\">\n <i class=\"bi bi-view-list me-2\" aria-hidden=\"true\" />\n View\n </Dropdown.Toggle>\n <Dropdown.Menu>\n {pinnedColumns.length > 0 && (\n <>\n <div class=\"px-2 py-1 text-muted small\" role=\"presentation\">\n Frozen columns\n </div>\n <div role=\"group\">\n {pinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== pinnedColumns.length - 1}\n onTogglePin={handleTogglePin}\n onClearElementFocus={() => setActiveElementId(null)}\n />\n );\n })}\n </div>\n <Dropdown.Divider />\n </>\n )}\n {unpinnedColumns.length > 0 && (\n <>\n <div role=\"group\">\n {unpinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== 0}\n onTogglePin={handleTogglePin}\n onClearElementFocus={() => setActiveElementId(null)}\n />\n );\n })}\n </div>\n {showResetButton && <Dropdown.Divider />}\n </>\n )}\n {showResetButton && (\n <div class=\"px-2 py-1\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n class=\"w-100\"\n aria-label=\"Reset all columns to default visibility and pinning\"\n onClick={() => {\n table.resetColumnVisibility();\n table.resetColumnPinning();\n setActiveElementId('column-manager-button');\n }}\n >\n <i class=\"bi bi-arrow-counterclockwise me-2\" aria-hidden=\"true\" />\n Reset view\n </Button>\n </div>\n )}\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
1
+ {"version":3,"file":"ColumnManager.js","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type Column, type Table } from '@tanstack/react-table';\nimport { useEffect, useRef, useState } from 'preact/compat';\nimport Button from 'react-bootstrap/Button';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\ninterface ColumnMenuItemProps<RowDataModel> {\n column: Column<RowDataModel>;\n hidePinButton: boolean;\n onTogglePin: (columnId: string) => void;\n}\n\nfunction ColumnMenuItem<RowDataModel>({\n column,\n hidePinButton = false,\n onTogglePin,\n}: ColumnMenuItemProps<RowDataModel>) {\n if (!column.getCanHide() && !column.getCanPin()) return null;\n\n // Use meta.label if available, otherwise fall back to header or column.id\n const header =\n (column.columnDef.meta as any)?.label ??\n (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);\n\n return (\n <div key={column.id} class=\"px-2 py-1 d-flex align-items-center justify-content-between\">\n <label class=\"form-check me-auto text-nowrap d-flex align-items-stretch\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n checked={column.getIsVisible()}\n disabled={!column.getCanHide()}\n aria-label={column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`}\n aria-describedby={`${column.id}-label`}\n onChange={column.getToggleVisibilityHandler()}\n />\n <span class=\"form-check-label ms-2\" id={`${column.id}-label`}>\n {header}\n </span>\n </label>\n {column.getCanPin() && !hidePinButton && (\n <button\n type=\"button\"\n // Since the HTML changes, but we want to refocus the pin button, we track\n // the active pin button and refocuses it when the column manager is rerendered.\n id={`${column.id}-pin`}\n class=\"btn btn-sm btn-ghost ms-2\"\n aria-label={\n column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`\n }\n title={column.getIsPinned() ? 'Unfreeze column' : 'Freeze column'}\n data-bs-toggle=\"tooltip\"\n onClick={() => onTogglePin(column.id)}\n >\n <i class={`bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`} aria-hidden=\"true\" />\n </button>\n )}\n </div>\n );\n}\n\nexport function ColumnManager<RowDataModel>({\n table,\n id,\n}: {\n table: Table<RowDataModel>;\n id: string;\n}) {\n const [activeElementId, setActiveElementId] = useState<string | null>(null);\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const handleTogglePin = (columnId: string) => {\n const currentLeft = table.getState().columnPinning.left ?? [];\n const isPinned = currentLeft.includes(columnId);\n let newLeft: string[];\n if (isPinned) {\n newLeft = currentLeft.filter((id) => id !== columnId);\n } else {\n const columnOrder = table.getAllLeafColumns().map((c) => c.id);\n const newPinned = new Set([...currentLeft, columnId]);\n newLeft = columnOrder.filter((id) => newPinned.has(id));\n }\n table.setColumnPinning({ left: newLeft, right: [] });\n setActiveElementId(`${columnId}-pin`);\n };\n\n const isVisibilityChanged = Object.entries(table.getState().columnVisibility).some(\n ([key, value]) => {\n return value !== table.initialState.columnVisibility[key];\n },\n );\n\n const initialPinning = table.initialState.columnPinning.left ?? [];\n const currentPinning = table.getState().columnPinning.left ?? [];\n const isPinningChanged =\n initialPinning.length !== currentPinning.length ||\n initialPinning.some((id) => !currentPinning.includes(id));\n const showResetButton = isVisibilityChanged || isPinningChanged;\n\n const pinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() === 'left');\n const unpinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() !== 'left');\n\n useEffect(() => {\n // When we use the pin or reset button, we want to refocus to another element.\n // We want this in a useEffect so that this code runs after the component re-renders.\n\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-event-handler\n if (activeElementId) {\n document.getElementById(activeElementId)?.focus();\n }\n }, [activeElementId]);\n\n return (\n <Dropdown\n ref={menuRef}\n autoClose=\"outside\"\n show={dropdownOpen}\n onToggle={(isOpen, _meta) => setDropdownOpen(isOpen)}\n onFocusOut={(e: FocusEvent) => {\n // Since we aren't using role=\"menu\", we need to manually close the dropdown when focus leaves.\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n }}\n >\n <Dropdown.Toggle\n variant=\"tanstack-table\"\n id={id}\n // eslint-disable-next-line @eslint-react/no-forbidden-props\n className=\"tanstack-table-focusable-shadow\"\n >\n <i class=\"bi bi-view-list me-2\" aria-hidden=\"true\" /> View{' '}\n </Dropdown.Toggle>\n <Dropdown.Menu style={{ maxHeight: '60vh', overflowY: 'auto' }}>\n {pinnedColumns.length > 0 && (\n <>\n <div class=\"px-2 py-1 text-muted small\" role=\"presentation\">\n Frozen columns\n </div>\n <div role=\"group\">\n {pinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== pinnedColumns.length - 1}\n onTogglePin={handleTogglePin}\n />\n );\n })}\n </div>\n <Dropdown.Divider />\n </>\n )}\n {unpinnedColumns.length > 0 && (\n <>\n <div role=\"group\">\n {unpinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== 0}\n onTogglePin={handleTogglePin}\n />\n );\n })}\n </div>\n {showResetButton && <Dropdown.Divider />}\n </>\n )}\n {showResetButton && (\n <div class=\"px-2 py-1\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n class=\"w-100\"\n aria-label=\"Reset all columns to default visibility and pinning\"\n onClick={() => {\n table.resetColumnVisibility();\n table.resetColumnPinning();\n setActiveElementId(id);\n }}\n >\n <i class=\"bi bi-arrow-counterclockwise me-2\" aria-hidden=\"true\" />\n Reset view\n </Button>\n </div>\n )}\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { type JSX } from 'preact/compat';
2
+ /**
3
+ * A component that allows the user to filter a column containing arrays of values.
4
+ * Uses AND logic: rows must contain ALL selected values to match.
5
+ *
6
+ * @param params
7
+ * @param params.columnId - The ID of the column
8
+ * @param params.columnLabel - The label of the column, e.g. "Rubric Items"
9
+ * @param params.allColumnValues - All possible values that can appear in the column
10
+ * @param params.renderValueLabel - A function that renders the label for a value
11
+ * @param params.columnValuesFilter - The current state of the column filter
12
+ * @param params.setColumnValuesFilter - A function that sets the state of the column filter
13
+ */
14
+ export declare function MultiSelectColumnFilter<T extends readonly any[]>({ columnId, columnLabel, allColumnValues, renderValueLabel, columnValuesFilter, setColumnValuesFilter, }: {
15
+ columnId: string;
16
+ columnLabel: string;
17
+ allColumnValues: T;
18
+ renderValueLabel?: (props: {
19
+ value: T[number];
20
+ isSelected: boolean;
21
+ }) => JSX.Element;
22
+ columnValuesFilter: T[number][];
23
+ setColumnValuesFilter: (value: T[number][]) => void;
24
+ }): JSX.Element;
25
+ //# sourceMappingURL=MultiSelectColumnFilter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiSelectColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/MultiSelectColumnFilter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,eAAe,CAAC;AAOlD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,SAAS,GAAG,EAAE,EAAE,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAA0C,EAC1C,kBAAkB,EAClB,qBAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,CAAC,CAAC;IACnB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;IACrF,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CACrD,eAoEA"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@prairielearn/preact-cjs/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useMemo } from 'preact/compat';
4
+ import Dropdown from 'react-bootstrap/Dropdown';
5
+ function defaultRenderValueLabel({ value }) {
6
+ return _jsx("span", { children: String(value) });
7
+ }
8
+ /**
9
+ * A component that allows the user to filter a column containing arrays of values.
10
+ * Uses AND logic: rows must contain ALL selected values to match.
11
+ *
12
+ * @param params
13
+ * @param params.columnId - The ID of the column
14
+ * @param params.columnLabel - The label of the column, e.g. "Rubric Items"
15
+ * @param params.allColumnValues - All possible values that can appear in the column
16
+ * @param params.renderValueLabel - A function that renders the label for a value
17
+ * @param params.columnValuesFilter - The current state of the column filter
18
+ * @param params.setColumnValuesFilter - A function that sets the state of the column filter
19
+ */
20
+ export function MultiSelectColumnFilter({ columnId, columnLabel, allColumnValues, renderValueLabel = defaultRenderValueLabel, columnValuesFilter, setColumnValuesFilter, }) {
21
+ const selected = useMemo(() => new Set(columnValuesFilter), [columnValuesFilter]);
22
+ const toggleSelected = (value) => {
23
+ const set = new Set(selected);
24
+ if (set.has(value)) {
25
+ set.delete(value);
26
+ }
27
+ else {
28
+ set.add(value);
29
+ }
30
+ setColumnValuesFilter(Array.from(set));
31
+ };
32
+ const hasActiveFilter = selected.size > 0;
33
+ return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: "text-muted p-0", id: `filter-${columnId}`, "aria-label": `Filter ${columnLabel.toLowerCase()}`, title: `Filter ${columnLabel.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', hasActiveFilter ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel'), "aria-hidden": "true" }) }), _jsx(Dropdown.Menu, { class: "p-0", children: _jsxs("div", { class: "p-3", style: { minWidth: '250px' }, children: [_jsxs("div", { class: "d-flex align-items-center justify-content-between mb-2", children: [_jsx("div", { class: "fw-semibold", children: columnLabel }), _jsx("button", { type: "button", class: "btn btn-link btn-sm text-decoration-none p-0", onClick: () => setColumnValuesFilter([]), children: "Clear" })] }), _jsx("div", { class: "list-group list-group-flush", children: allColumnValues.map((value) => {
34
+ const isSelected = selected.has(value);
35
+ return (_jsxs("div", { class: "list-group-item d-flex align-items-center gap-3 px-0", children: [_jsx("input", { class: "form-check-input", type: "checkbox", checked: isSelected, id: `${columnId}-${value}`, onChange: () => toggleSelected(value) }), _jsx("label", { class: "form-check-label fw-normal", for: `${columnId}-${value}`, children: renderValueLabel({
36
+ value,
37
+ isSelected,
38
+ }) })] }, value));
39
+ }) })] }) })] }));
40
+ }
41
+ //# sourceMappingURL=MultiSelectColumnFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiSelectColumnFilter.js","sourceRoot":"","sources":["../../src/components/MultiSelectColumnFilter.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport { type JSX, useMemo } from 'preact/compat';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\nfunction defaultRenderValueLabel<T>({ value }: { value: T }) {\n return <span>{String(value)}</span>;\n}\n\n/**\n * A component that allows the user to filter a column containing arrays of values.\n * Uses AND logic: rows must contain ALL selected values to match.\n *\n * @param params\n * @param params.columnId - The ID of the column\n * @param params.columnLabel - The label of the column, e.g. \"Rubric Items\"\n * @param params.allColumnValues - All possible values that can appear in the column\n * @param params.renderValueLabel - A function that renders the label for a value\n * @param params.columnValuesFilter - The current state of the column filter\n * @param params.setColumnValuesFilter - A function that sets the state of the column filter\n */\nexport function MultiSelectColumnFilter<T extends readonly any[]>({\n columnId,\n columnLabel,\n allColumnValues,\n renderValueLabel = defaultRenderValueLabel,\n columnValuesFilter,\n setColumnValuesFilter,\n}: {\n columnId: string;\n columnLabel: string;\n allColumnValues: T;\n renderValueLabel?: (props: { value: T[number]; isSelected: boolean }) => JSX.Element;\n columnValuesFilter: T[number][];\n setColumnValuesFilter: (value: T[number][]) => void;\n}) {\n const selected = useMemo(() => new Set(columnValuesFilter), [columnValuesFilter]);\n\n const toggleSelected = (value: T[number]) => {\n const set = new Set(selected);\n if (set.has(value)) {\n set.delete(value);\n } else {\n set.add(value);\n }\n setColumnValuesFilter(Array.from(set));\n };\n\n const hasActiveFilter = selected.size > 0;\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class=\"text-muted p-0\"\n id={`filter-${columnId}`}\n aria-label={`Filter ${columnLabel.toLowerCase()}`}\n title={`Filter ${columnLabel.toLowerCase()}`}\n >\n <i\n class={clsx('bi', hasActiveFilter ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu class=\"p-0\">\n <div class=\"p-3\" style={{ minWidth: '250px' }}>\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <div class=\"fw-semibold\">{columnLabel}</div>\n <button\n type=\"button\"\n class=\"btn btn-link btn-sm text-decoration-none p-0\"\n onClick={() => setColumnValuesFilter([])}\n >\n Clear\n </button>\n </div>\n\n <div class=\"list-group list-group-flush\">\n {allColumnValues.map((value) => {\n const isSelected = selected.has(value);\n return (\n <div key={value} class=\"list-group-item d-flex align-items-center gap-3 px-0\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n checked={isSelected}\n id={`${columnId}-${value}`}\n onChange={() => toggleSelected(value)}\n />\n <label class=\"form-check-label fw-normal\" for={`${columnId}-${value}`}>\n {renderValueLabel({\n value,\n isSelected,\n })}\n </label>\n </div>\n );\n })}\n </div>\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
@@ -0,0 +1,42 @@
1
+ interface NumericInputColumnFilterProps {
2
+ columnId: string;
3
+ columnLabel: string;
4
+ value: string;
5
+ onChange: (value: string) => void;
6
+ }
7
+ /**
8
+ * A component that allows the user to filter a numeric column using comparison operators.
9
+ * Supports syntax like: <1, >0, <=5, >=10, =5, or just 5 (implicit equals)
10
+ *
11
+ * @param params
12
+ * @param params.columnId - The ID of the column
13
+ * @param params.columnLabel - The label of the column, e.g. "Manual Points"
14
+ * @param params.value - The current filter value (e.g., ">5" or "10")
15
+ * @param params.onChange - Callback when the filter value changes
16
+ */
17
+ export declare function NumericInputColumnFilter({ columnId, columnLabel, value, onChange, }: NumericInputColumnFilterProps): import("original-preact").JSX.Element;
18
+ /**
19
+ * Helper function to parse a numeric filter value.
20
+ * Returns null if the filter is invalid or empty.
21
+ *
22
+ * @param filterValue - The filter string (e.g., ">5", "<=10", "3")
23
+ * @returns Parsed operator and value, or null if invalid
24
+ */
25
+ export declare function parseNumericFilter(filterValue: string): {
26
+ operator: '<' | '>' | '<=' | '>=' | '=';
27
+ value: number;
28
+ } | null;
29
+ /**
30
+ * TanStack Table filter function for numeric columns.
31
+ * Use this as the `filterFn` for numeric columns.
32
+ *
33
+ * @example
34
+ * {
35
+ * id: 'manual_points',
36
+ * accessorKey: 'manual_points',
37
+ * filterFn: numericColumnFilterFn,
38
+ * }
39
+ */
40
+ export declare function numericColumnFilterFn(row: any, columnId: string, filterValue: string): boolean;
41
+ export {};
42
+ //# sourceMappingURL=NumericInputColumnFilter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumericInputColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/NumericInputColumnFilter.tsx"],"names":[],"mappings":"AAGA,UAAU,6BAA6B;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,WAAW,EACX,KAAK,EACL,QAAQ,GACT,EAAE,6BAA6B,yCAqE/B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG;IACvD,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,IAAI,CAYP;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAqB9F"}
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@prairielearn/preact-cjs/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import Dropdown from 'react-bootstrap/Dropdown';
4
+ /**
5
+ * A component that allows the user to filter a numeric column using comparison operators.
6
+ * Supports syntax like: <1, >0, <=5, >=10, =5, or just 5 (implicit equals)
7
+ *
8
+ * @param params
9
+ * @param params.columnId - The ID of the column
10
+ * @param params.columnLabel - The label of the column, e.g. "Manual Points"
11
+ * @param params.value - The current filter value (e.g., ">5" or "10")
12
+ * @param params.onChange - Callback when the filter value changes
13
+ */
14
+ export function NumericInputColumnFilter({ columnId, columnLabel, value, onChange, }) {
15
+ const hasActiveFilter = value.trim().length > 0;
16
+ const isInvalid = hasActiveFilter && parseNumericFilter(value) === null;
17
+ return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: clsx('text-muted p-0', hasActiveFilter && (isInvalid ? 'text-warning' : 'text-primary')), id: `filter-${columnId}`, "aria-label": `Filter ${columnLabel.toLowerCase()}`, title: `Filter ${columnLabel.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', isInvalid
18
+ ? 'bi-exclamation-triangle'
19
+ : hasActiveFilter
20
+ ? 'bi-funnel-fill'
21
+ : 'bi-funnel'), "aria-hidden": "true" }) }), _jsx(Dropdown.Menu, { children: _jsxs("div", { class: "p-3", style: { minWidth: '240px' }, children: [_jsx("label", { class: "form-label small fw-semibold mb-2", children: columnLabel }), _jsx("input", { type: "text", class: clsx('form-control form-control-sm', isInvalid && 'is-invalid'), placeholder: "e.g., >0, <5, =10", value: value, onInput: (e) => {
22
+ if (e.target instanceof HTMLInputElement) {
23
+ onChange(e.target.value);
24
+ }
25
+ }, onClick: (e) => e.stopPropagation() }), isInvalid && (_jsxs("div", { class: "invalid-feedback d-block", children: ["Invalid filter format. Use operators like ", _jsx("code", { children: ">5" }), " or ", _jsx("code", { children: "<=10" })] })), !isInvalid && (_jsxs("div", { class: "form-text small mt-2", children: ["Use operators: ", _jsx("code", { children: "<" }), ", ", _jsx("code", { children: ">" }), ", ", _jsx("code", { children: "<=" }), ",", ' ', _jsx("code", { children: ">=" }), ", ", _jsx("code", { children: "=" }), _jsx("br", {}), "Example: ", _jsx("code", { children: ">5" }), " or ", _jsx("code", { children: "<=10" })] })), hasActiveFilter && (_jsx("button", { type: "button", class: "btn btn-sm btn-link text-decoration-none mt-2 p-0", onClick: () => onChange(''), children: "Clear filter" }))] }) })] }));
26
+ }
27
+ /**
28
+ * Helper function to parse a numeric filter value.
29
+ * Returns null if the filter is invalid or empty.
30
+ *
31
+ * @param filterValue - The filter string (e.g., ">5", "<=10", "3")
32
+ * @returns Parsed operator and value, or null if invalid
33
+ */
34
+ export function parseNumericFilter(filterValue) {
35
+ if (!filterValue.trim())
36
+ return null;
37
+ const match = filterValue.trim().match(/^(<=?|>=?|=)?\s*(-?\d+\.?\d*)$/);
38
+ if (!match)
39
+ return null;
40
+ const operator = (match[1] || '=');
41
+ const value = Number.parseFloat(match[2]);
42
+ if (Number.isNaN(value))
43
+ return null;
44
+ return { operator, value };
45
+ }
46
+ /**
47
+ * TanStack Table filter function for numeric columns.
48
+ * Use this as the `filterFn` for numeric columns.
49
+ *
50
+ * @example
51
+ * {
52
+ * id: 'manual_points',
53
+ * accessorKey: 'manual_points',
54
+ * filterFn: numericColumnFilterFn,
55
+ * }
56
+ */
57
+ export function numericColumnFilterFn(row, columnId, filterValue) {
58
+ const parsed = parseNumericFilter(filterValue);
59
+ if (!parsed)
60
+ return true; // Invalid or empty filter = show all
61
+ const cellValue = row.getValue(columnId);
62
+ if (cellValue === null || cellValue === undefined)
63
+ return false;
64
+ switch (parsed.operator) {
65
+ case '<':
66
+ return cellValue < parsed.value;
67
+ case '>':
68
+ return cellValue > parsed.value;
69
+ case '<=':
70
+ return cellValue <= parsed.value;
71
+ case '>=':
72
+ return cellValue >= parsed.value;
73
+ case '=':
74
+ return cellValue === parsed.value;
75
+ default:
76
+ return true;
77
+ }
78
+ }
79
+ //# sourceMappingURL=NumericInputColumnFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumericInputColumnFilter.js","sourceRoot":"","sources":["../../src/components/NumericInputColumnFilter.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\ninterface NumericInputColumnFilterProps {\n columnId: string;\n columnLabel: string;\n value: string;\n onChange: (value: string) => void;\n}\n\n/**\n * A component that allows the user to filter a numeric column using comparison operators.\n * Supports syntax like: <1, >0, <=5, >=10, =5, or just 5 (implicit equals)\n *\n * @param params\n * @param params.columnId - The ID of the column\n * @param params.columnLabel - The label of the column, e.g. \"Manual Points\"\n * @param params.value - The current filter value (e.g., \">5\" or \"10\")\n * @param params.onChange - Callback when the filter value changes\n */\nexport function NumericInputColumnFilter({\n columnId,\n columnLabel,\n value,\n onChange,\n}: NumericInputColumnFilterProps) {\n const hasActiveFilter = value.trim().length > 0;\n const isInvalid = hasActiveFilter && parseNumericFilter(value) === null;\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class={clsx(\n 'text-muted p-0',\n hasActiveFilter && (isInvalid ? 'text-warning' : 'text-primary'),\n )}\n id={`filter-${columnId}`}\n aria-label={`Filter ${columnLabel.toLowerCase()}`}\n title={`Filter ${columnLabel.toLowerCase()}`}\n >\n <i\n class={clsx(\n 'bi',\n isInvalid\n ? 'bi-exclamation-triangle'\n : hasActiveFilter\n ? 'bi-funnel-fill'\n : 'bi-funnel',\n )}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu>\n <div class=\"p-3\" style={{ minWidth: '240px' }}>\n <label class=\"form-label small fw-semibold mb-2\">{columnLabel}</label>\n <input\n type=\"text\"\n class={clsx('form-control form-control-sm', isInvalid && 'is-invalid')}\n placeholder=\"e.g., >0, <5, =10\"\n value={value}\n onInput={(e) => {\n if (e.target instanceof HTMLInputElement) {\n onChange(e.target.value);\n }\n }}\n onClick={(e) => e.stopPropagation()}\n />\n {isInvalid && (\n <div class=\"invalid-feedback d-block\">\n Invalid filter format. Use operators like <code>&gt;5</code> or <code>&lt;=10</code>\n </div>\n )}\n {!isInvalid && (\n <div class=\"form-text small mt-2\">\n Use operators: <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>,{' '}\n <code>&gt;=</code>, <code>=</code>\n <br />\n Example: <code>&gt;5</code> or <code>&lt;=10</code>\n </div>\n )}\n {hasActiveFilter && (\n <button\n type=\"button\"\n class=\"btn btn-sm btn-link text-decoration-none mt-2 p-0\"\n onClick={() => onChange('')}\n >\n Clear filter\n </button>\n )}\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n\n/**\n * Helper function to parse a numeric filter value.\n * Returns null if the filter is invalid or empty.\n *\n * @param filterValue - The filter string (e.g., \">5\", \"<=10\", \"3\")\n * @returns Parsed operator and value, or null if invalid\n */\nexport function parseNumericFilter(filterValue: string): {\n operator: '<' | '>' | '<=' | '>=' | '=';\n value: number;\n} | null {\n if (!filterValue.trim()) return null;\n\n const match = filterValue.trim().match(/^(<=?|>=?|=)?\\s*(-?\\d+\\.?\\d*)$/);\n if (!match) return null;\n\n const operator = (match[1] || '=') as '<' | '>' | '<=' | '>=' | '=';\n const value = Number.parseFloat(match[2]);\n\n if (Number.isNaN(value)) return null;\n\n return { operator, value };\n}\n\n/**\n * TanStack Table filter function for numeric columns.\n * Use this as the `filterFn` for numeric columns.\n *\n * @example\n * {\n * id: 'manual_points',\n * accessorKey: 'manual_points',\n * filterFn: numericColumnFilterFn,\n * }\n */\nexport function numericColumnFilterFn(row: any, columnId: string, filterValue: string): boolean {\n const parsed = parseNumericFilter(filterValue);\n if (!parsed) return true; // Invalid or empty filter = show all\n\n const cellValue = row.getValue(columnId) as number | null;\n if (cellValue === null || cellValue === undefined) return false;\n\n switch (parsed.operator) {\n case '<':\n return cellValue < parsed.value;\n case '>':\n return cellValue > parsed.value;\n case '<=':\n return cellValue <= parsed.value;\n case '>=':\n return cellValue >= parsed.value;\n case '=':\n return cellValue === parsed.value;\n default:\n return true;\n }\n}\n"]}
@@ -2,3 +2,52 @@ body.no-user-select {
2
2
  user-select: none;
3
3
  -webkit-user-select: none;
4
4
  }
5
+
6
+ .tanstack-table-search-input {
7
+ padding-right: 2.5rem;
8
+ }
9
+
10
+ .tanstack-table-clear-search {
11
+ position: absolute;
12
+ right: 0;
13
+ top: 50%;
14
+ transform: translateY(-50%);
15
+ padding: 0.25rem 0.5rem;
16
+ color: var(--bs-secondary);
17
+ opacity: 0.6;
18
+ transition: opacity 0.15s ease-in-out;
19
+ text-decoration: none;
20
+ }
21
+
22
+ .tanstack-table-clear-search:hover {
23
+ opacity: 1;
24
+ color: var(--bs-secondary);
25
+ text-decoration: none;
26
+ }
27
+
28
+ .tanstack-table-clear-search:focus {
29
+ opacity: 1;
30
+ color: var(--bs-secondary);
31
+ text-decoration: none;
32
+ }
33
+
34
+ .btn.btn-tanstack-table {
35
+ --bs-btn-color: var(--bs-body-color);
36
+ --bs-btn-bg: var(--bs-input-bg);
37
+ --bs-btn-border-color: var(--bs-border-color);
38
+ --bs-btn-hover-color: var(--bs-body-color);
39
+ --bs-btn-hover-bg: var(--bs-border-color);
40
+ --bs-btn-hover-border-color: var(--bs-border-color);
41
+ --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
42
+ --bs-btn-active-color: var(--bs-body-color);
43
+ --bs-btn-active-bg: var(--bs-border-color);
44
+ --bs-btn-active-border-color: var(--bs-border-color);
45
+ --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
46
+ --bs-btn-disabled-color: var(--bs-body-color);
47
+ --bs-btn-disabled-bg: var(--bs-secondary-bg);
48
+ --bs-btn-disabled-border-color: var(--bs-border-color);
49
+ }
50
+
51
+ .tanstack-table-focusable-shadow:not(:focus-visible) {
52
+ box-shadow: var(--bs-box-shadow-sm);
53
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Header, Table } from '@tanstack/table-core';
2
+ import type { ComponentChildren } from 'preact';
2
3
  import type { JSX } from 'preact/jsx-runtime';
3
4
  import { type TanstackTableDownloadButtonProps } from './TanstackTableDownloadButton.js';
4
5
  interface TanstackTableProps<RowDataModel> {
@@ -28,6 +29,7 @@ export declare function TanstackTable<RowDataModel>({ table, title, filters, row
28
29
  * @param params.table - The table model
29
30
  * @param params.title - The title of the card
30
31
  * @param params.headerButtons - The buttons to display in the header
32
+ * @param params.columnManagerButtons - The buttons to display next to the column manager (View button)
31
33
  * @param params.globalFilter - State management for the global filter
32
34
  * @param params.globalFilter.value
33
35
  * @param params.globalFilter.setValue
@@ -35,10 +37,11 @@ export declare function TanstackTable<RowDataModel>({ table, title, filters, row
35
37
  * @param params.tableOptions - Specific options for the table. See {@link TanstackTableProps} for more details.
36
38
  * @param params.downloadButtonOptions - Specific options for the download button. See {@link TanstackTableDownloadButtonProps} for more details.
37
39
  */
38
- export declare function TanstackTableCard<RowDataModel>({ table, title, headerButtons, globalFilter, tableOptions, downloadButtonOptions, }: {
40
+ export declare function TanstackTableCard<RowDataModel>({ table, title, headerButtons, columnManagerButtons, globalFilter, tableOptions, downloadButtonOptions, }: {
39
41
  table: Table<RowDataModel>;
40
42
  title: string;
41
43
  headerButtons: JSX.Element;
44
+ columnManagerButtons?: JSX.Element;
42
45
  globalFilter: {
43
46
  value: string;
44
47
  setValue: (value: string) => void;
@@ -47,5 +50,9 @@ export declare function TanstackTableCard<RowDataModel>({ table, title, headerBu
47
50
  tableOptions: Partial<Omit<TanstackTableProps<RowDataModel>, 'table'>>;
48
51
  downloadButtonOptions?: Omit<TanstackTableDownloadButtonProps<RowDataModel>, 'table'> | null;
49
52
  }): JSX.Element;
53
+ export declare function TanstackTableEmptyState({ iconName, children, }: {
54
+ iconName: `bi-${string}`;
55
+ children: ComponentChildren;
56
+ }): JSX.Element;
50
57
  export {};
51
58
  //# sourceMappingURL=TanstackTable.d.ts.map