@cytario/web 2.1.3 → 2.1.5

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 (202) hide show
  1. package/README.md +20 -20
  2. package/app/.server/auth/README.md +8 -8
  3. package/app/.server/auth/authMiddleware.ts +9 -25
  4. package/app/.server/auth/exchangeAuthCode.ts +2 -6
  5. package/app/.server/auth/getS3Client.ts +3 -13
  6. package/app/.server/auth/getSessionCredentials.ts +6 -20
  7. package/app/.server/auth/getUserInfo.ts +2 -6
  8. package/app/.server/auth/keycloakAdmin/client.ts +2 -9
  9. package/app/.server/auth/keycloakAdmin/groups.ts +9 -26
  10. package/app/.server/auth/keycloakAdmin/users.ts +7 -23
  11. package/app/.server/auth/oauthState.ts +4 -13
  12. package/app/.server/auth/redirectIfAuthenticated.ts +1 -3
  13. package/app/.server/auth/refreshAuthTokens.ts +5 -19
  14. package/app/.server/auth/sessionMiddleware.ts +1 -4
  15. package/app/.server/auth/sessionStorage.ts +1 -4
  16. package/app/.server/auth/verifyIdToken.ts +1 -3
  17. package/app/.server/auth/wellKnownEndpoints.ts +1 -4
  18. package/app/.server/db/redis.ts +5 -1
  19. package/app/.server/logging.ts +1 -4
  20. package/app/.server/requestDurationMiddleware.ts +1 -4
  21. package/app/components/.client/ImageViewer/README.md +5 -5
  22. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsController.tsx +7 -9
  23. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerBrightfieldItem.tsx +1 -2
  24. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItem.tsx +2 -5
  25. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItemList.tsx +7 -15
  26. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorPicker.tsx +2 -9
  27. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorSwatch.tsx +1 -4
  28. package/app/components/.client/ImageViewer/components/ChannelsController/DomainSlider.tsx +1 -3
  29. package/app/components/.client/ImageViewer/components/ChannelsController/Histogram.tsx +16 -18
  30. package/app/components/.client/ImageViewer/components/ChannelsController/HistogramChannel.tsx +2 -8
  31. package/app/components/.client/ImageViewer/components/ChannelsController/MinMaxSettings.tsx +6 -15
  32. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarDragHandle.tsx +1 -5
  33. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarToggle.tsx +1 -5
  34. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureItem.tsx +1 -5
  35. package/app/components/.client/ImageViewer/components/FeatureBar/Presets.tsx +3 -11
  36. package/app/components/.client/ImageViewer/components/FeatureBar/useFeatureBar.tsx +16 -25
  37. package/app/components/.client/ImageViewer/components/Image/Channels/useChannelsLayer.ts +7 -18
  38. package/app/components/.client/ImageViewer/components/Image/ImageContainer.tsx +1 -1
  39. package/app/components/.client/ImageViewer/components/Image/ImagePanel.tsx +6 -26
  40. package/app/components/.client/ImageViewer/components/Image/ImagePreview.tsx +2 -9
  41. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditivePolygonLayer.tsx +1 -5
  42. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditiveScatterplotLayer.tsx +1 -5
  43. package/app/components/.client/ImageViewer/components/Image/Overlays/OverlaysLayer.tsx +6 -24
  44. package/app/components/.client/ImageViewer/components/Image/Overlays/markerUniforms.ts +2 -5
  45. package/app/components/.client/ImageViewer/components/Image/Overlays/useOverlaysLayer.tsx +7 -21
  46. package/app/components/.client/ImageViewer/components/Image/useInitializeChannels.ts +1 -7
  47. package/app/components/.client/ImageViewer/components/Image/useResizeObserver.ts +1 -1
  48. package/app/components/.client/ImageViewer/components/Magnifier.tsx +5 -13
  49. package/app/components/.client/ImageViewer/components/Measurements/ActiveViewStatePreview.tsx +3 -8
  50. package/app/components/.client/ImageViewer/components/Measurements/CursorTick.tsx +2 -7
  51. package/app/components/.client/ImageViewer/components/Measurements/Ruler.tsx +1 -8
  52. package/app/components/.client/ImageViewer/components/Measurements/SlideCarrier.tsx +3 -13
  53. package/app/components/.client/ImageViewer/components/Measurements/Tick.tsx +1 -1
  54. package/app/components/.client/ImageViewer/components/Measurements/calculateViewStateToFit.ts +1 -1
  55. package/app/components/.client/ImageViewer/components/Measurements/useMeasurements.ts +9 -28
  56. package/app/components/.client/ImageViewer/components/OverlaysController/AddOverlay.tsx +4 -13
  57. package/app/components/.client/ImageViewer/components/OverlaysController/OverlayPicker.modal.tsx +1 -6
  58. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.Item.tsx +24 -54
  59. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.tsx +1 -3
  60. package/app/components/.client/ImageViewer/components/SplitViewToggle.tsx +1 -3
  61. package/app/components/.client/ImageViewer/components/ViewerHeader.tsx +1 -3
  62. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.d.ts +9 -11
  63. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.js +11 -11
  64. package/app/components/.client/ImageViewer/state/decoders/decoder.worker.js +49 -49
  65. package/app/components/.client/ImageViewer/state/decoders/genericDecoder.ts +76 -81
  66. package/app/components/.client/ImageViewer/state/decoders/jp2k-decoder.ts +9 -9
  67. package/app/components/.client/ImageViewer/state/decoders/lzwDecoder.ts +9 -9
  68. package/app/components/.client/ImageViewer/state/loaders/loadBioformatsZarrWithCredentials.ts +10 -22
  69. package/app/components/.client/ImageViewer/state/store/ViewerStoreContext.tsx +4 -18
  70. package/app/components/.client/ImageViewer/state/store/createViewerStore.ts +110 -194
  71. package/app/components/.client/ImageViewer/state/store/getInitialChannelsState.ts +2 -6
  72. package/app/components/.client/ImageViewer/state/store/selectors.ts +9 -9
  73. package/app/components/.client/ImageViewer/state/store/types.ts +3 -12
  74. package/app/components/.client/ImageViewer/state/transport/CredentialedHTTPStore.ts +1 -5
  75. package/app/components/.client/ImageViewer/state/transport/SigV4TiffClient.ts +2 -9
  76. package/app/components/.client/ImageViewer/utils/getSelectionStats.ts +1 -4
  77. package/app/components/.client/ImageViewer/utils/handleImageViewerHover.ts +1 -1
  78. package/app/components/.client/ImageViewer/utils/mapChannelConfigsToState.ts +2 -4
  79. package/app/components/.client/ImageViewer/utils/useTilesLoading.ts +3 -3
  80. package/app/components/AppHeader.tsx +1 -4
  81. package/app/components/Breadcrumbs/Breadcrumbs.tsx +4 -13
  82. package/app/components/Breadcrumbs/getCrumbs.tsx +1 -1
  83. package/app/components/ClientOnly.tsx +1 -1
  84. package/app/components/Container.tsx +3 -15
  85. package/app/components/DataGrid/ConvertOverlay.modal.tsx +1 -6
  86. package/app/components/DataGrid/DataGrid.tsx +7 -27
  87. package/app/components/DataGrid/WktSvg.tsx +2 -4
  88. package/app/components/DataGrid/getParquetSchema.ts +1 -4
  89. package/app/components/DescriptionList.tsx +1 -3
  90. package/app/components/DirectoryView/ConnectionMenu.tsx +8 -22
  91. package/app/components/DirectoryView/DirectoryView.tsx +10 -46
  92. package/app/components/DirectoryView/DirectoryViewGrid.tsx +16 -49
  93. package/app/components/DirectoryView/DirectoryViewTableConnection.tsx +1 -4
  94. package/app/components/DirectoryView/DirectoryViewTableDirectory.tsx +2 -7
  95. package/app/components/DirectoryView/DirectoryViewTree.tsx +5 -21
  96. package/app/components/DirectoryView/FilterBar.tsx +9 -48
  97. package/app/components/DirectoryView/buildDirectoryTree.ts +6 -25
  98. package/app/components/DirectoryView/filterNodes.ts +4 -11
  99. package/app/components/DirectoryView/modals/Cyberduck.modal.tsx +6 -15
  100. package/app/components/DirectoryView/modals/FileInfo.modal.tsx +1 -5
  101. package/app/components/DirectoryView/useLayoutStore.ts +5 -25
  102. package/app/components/GlobalSearch/GlobalSearch.tsx +1 -4
  103. package/app/components/GlobalSearch/SearchBar.tsx +1 -7
  104. package/app/components/GlobalSearch/Suggestions.tsx +0 -1
  105. package/app/components/ImageViewer/state/formatRegistry.ts +5 -18
  106. package/app/components/LavaLoader.tsx +4 -11
  107. package/app/components/Layout/Footer.tsx +1 -4
  108. package/app/components/Pills/ScopePill.tsx +2 -8
  109. package/app/components/Table/ColumnFilterInput.tsx +5 -20
  110. package/app/components/Table/ColumnResizeHandle.tsx +1 -5
  111. package/app/components/Table/ColumnSortButton.tsx +1 -5
  112. package/app/components/Table/SelectionFooter.tsx +3 -5
  113. package/app/components/Table/Table.tsx +5 -21
  114. package/app/components/Table/TableBodyRow.tsx +19 -31
  115. package/app/components/Table/TableHeaderRow.tsx +7 -28
  116. package/app/components/Table/TableMenu.tsx +4 -20
  117. package/app/components/Table/state/createTableStore.ts +3 -9
  118. package/app/components/Table/state/useTableStore.ts +1 -3
  119. package/app/components/Table/types.ts +4 -10
  120. package/app/components/Table/useColumnFilters.ts +1 -4
  121. package/app/components/Table/useColumnVisibility.ts +4 -11
  122. package/app/components/Table/useColumnWidths.ts +1 -3
  123. package/app/components/Table/useTableSorting.ts +4 -12
  124. package/app/components/Tooltip/Tooltip.tsx +4 -17
  125. package/app/components/Tooltip/TooltipSpan.tsx +5 -28
  126. package/app/components/Tooltip/useCopyToClipboard.ts +1 -3
  127. package/app/components/Tooltip/useMiddleEllipsis.ts +2 -7
  128. package/app/components/Tooltip/useOverflowDetection.ts +2 -5
  129. package/app/components/UserMenu.tsx +2 -9
  130. package/app/entry.server.tsx +9 -19
  131. package/app/hooks/useSearchParam.ts +2 -4
  132. package/app/lib/bootstrapPluginsCore.ts +4 -9
  133. package/app/root.tsx +4 -15
  134. package/app/routes/admin/assertAdminScope.ts +1 -3
  135. package/app/routes/admin/assertGroupPathsInScope.ts +3 -11
  136. package/app/routes/admin/assertGroupsInScope.ts +3 -11
  137. package/app/routes/admin/assertUsersInScope.ts +2 -8
  138. package/app/routes/admin/bulkInvite/bulkInvite.action.ts +2 -13
  139. package/app/routes/admin/bulkInvite/bulkInvite.form.tsx +18 -35
  140. package/app/routes/admin/createGroup/createGroup.action.ts +3 -10
  141. package/app/routes/admin/createGroup/createGroup.form.tsx +2 -9
  142. package/app/routes/admin/createGroup/createGroup.modal.tsx +1 -5
  143. package/app/routes/admin/inviteUser/inviteUser.action.ts +1 -4
  144. package/app/routes/admin/inviteUser/inviteUser.form.tsx +2 -8
  145. package/app/routes/admin/inviteUser/inviteUser.loader.ts +1 -4
  146. package/app/routes/admin/inviteUser/inviteUser.modal.tsx +3 -16
  147. package/app/routes/admin/updateUser/updateUser.form.tsx +4 -15
  148. package/app/routes/admin/updateUser/updateUser.modal.tsx +5 -23
  149. package/app/routes/admin/updateUser/userDetail.action.ts +2 -10
  150. package/app/routes/admin/users/BulkActions.tsx +15 -38
  151. package/app/routes/admin/users/bulkUsers.action.ts +2 -9
  152. package/app/routes/admin/users/bulkUsers.schema.ts +1 -6
  153. package/app/routes/admin/users/users.route.tsx +14 -63
  154. package/app/routes/api/cyberduck-profile.$name.ts +6 -2
  155. package/app/routes/auth/callback.route.tsx +8 -33
  156. package/app/routes/auth/login.route.tsx +8 -11
  157. package/app/routes/auth/logout.route.tsx +4 -14
  158. package/app/routes/config.route.tsx +1 -5
  159. package/app/routes/connections/connection.form.tsx +5 -14
  160. package/app/routes/connections/connection.schema.ts +4 -16
  161. package/app/routes/connections/connections.loader.ts +11 -23
  162. package/app/routes/connections/connections.route.tsx +1 -5
  163. package/app/routes/connections/connections.server.ts +1 -3
  164. package/app/routes/connections/createConnection.action.ts +2 -8
  165. package/app/routes/connections/createConnection.modal.tsx +3 -13
  166. package/app/routes/connections/deleteConnection.action.ts +1 -4
  167. package/app/routes/connections/updateConnection.action.ts +2 -8
  168. package/app/routes/connections/updateConnection.modal.tsx +4 -14
  169. package/app/routes/home/home.route.tsx +8 -33
  170. package/app/routes/layouts/ModalOutlet.tsx +6 -18
  171. package/app/routes/objects/objects.loader.ts +5 -19
  172. package/app/routes/objects/objects.route.tsx +11 -30
  173. package/app/routes/presign.route.tsx +5 -18
  174. package/app/routes/recent.route.tsx +1 -4
  175. package/app/routes/search.route.tsx +4 -17
  176. package/app/routes.ts +1 -4
  177. package/app/tailwind.css +17 -12
  178. package/app/types/cornerstone-codecs.d.ts +25 -29
  179. package/app/utils/connectionsStore/selectors.ts +2 -6
  180. package/app/utils/connectionsStore/useConnectionsStore.ts +2 -6
  181. package/app/utils/db/convertCsvToParquet.ts +1 -3
  182. package/app/utils/db/createDatabase.ts +1 -3
  183. package/app/utils/db/createSingleton.ts +1 -1
  184. package/app/utils/db/getBlobFromObjectNode.ts +3 -9
  185. package/app/utils/db/getGeomQuery.ts +1 -1
  186. package/app/utils/db/getMarkerInfoWasm.ts +1 -3
  187. package/app/utils/db/getTileBoundingBox.ts +1 -4
  188. package/app/utils/db/sqlQueries.ts +1 -4
  189. package/app/utils/fileType.ts +80 -10
  190. package/app/utils/filterObjects.ts +2 -5
  191. package/app/utils/localFilesStore/useFileStore.ts +7 -7
  192. package/app/utils/recentlyViewed.server.ts +1 -4
  193. package/app/utils/resourceId.ts +3 -11
  194. package/app/utils/s3Provider.ts +3 -7
  195. package/app/utils/signedFetch.ts +4 -13
  196. package/bin-src/codegen.ts +4 -1
  197. package/package.json +5 -1
  198. package/prisma/seed.ts +1 -2
  199. package/public/favicon/site.webmanifest +1 -1
  200. package/server.js +1 -4
  201. package/server.js.map +1 -1
  202. package/vite-plugins/cytario-plugins.ts +2 -8
@@ -17,11 +17,7 @@ export const LavaLoader = (props: LavaLoaderProps) => (
17
17
  </ClientOnly>
18
18
  );
19
19
 
20
- const LavaLoaderInner = ({
21
- absolute = false,
22
- rows = 3,
23
- cols = 3,
24
- }: LavaLoaderProps) => {
20
+ const LavaLoaderInner = ({ absolute = false, rows = 3, cols = 3 }: LavaLoaderProps) => {
25
21
  const totalDots = rows * cols;
26
22
  const size = 12;
27
23
  const duration = 500;
@@ -48,7 +44,7 @@ const LavaLoaderInner = ({
48
44
  }
49
45
  return neighbors[Math.floor(Math.random() * neighbors.length)];
50
46
  },
51
- [rows, cols]
47
+ [rows, cols],
52
48
  );
53
49
 
54
50
  const [activeDot, setActiveDot] = useState<{
@@ -57,16 +53,13 @@ const LavaLoaderInner = ({
57
53
  }>(pickRandomDot());
58
54
 
59
55
  useEffect(() => {
60
- const interval = setInterval(
61
- () => setActiveDot((prev) => pickNeighbor(prev)),
62
- duration
63
- );
56
+ const interval = setInterval(() => setActiveDot((prev) => pickNeighbor(prev)), duration);
64
57
  return () => clearInterval(interval);
65
58
  }, [pickNeighbor, duration]);
66
59
 
67
60
  const cx = twMerge(
68
61
  "flex items-center justify-center w-full h-full",
69
- absolute ? "absolute" : "relative"
62
+ absolute ? "absolute" : "relative",
70
63
  );
71
64
 
72
65
  return (
@@ -6,10 +6,7 @@ interface FooterProps {
6
6
  className?: string;
7
7
  }
8
8
  export function Footer({ children, className }: FooterProps) {
9
- const cx = twMerge(
10
- "px-6 p-2 bg-slate-50 border-t border-slate-300",
11
- className
12
- );
9
+ const cx = twMerge("px-6 p-2 bg-slate-50 border-t border-slate-300", className);
13
10
  return (
14
11
  <footer className={cx}>
15
12
  <div className="container mx-auto flex justify-between">{children}</div>
@@ -1,13 +1,7 @@
1
- import {
2
- PathPill,
3
- Pill,
4
- type PillColor,
5
- pillColorFromName,
6
- } from "@cytario/design";
1
+ import { PathPill, Pill, type PillColor, pillColorFromName } from "@cytario/design";
7
2
  import { Shield } from "lucide-react";
8
3
 
9
- const UUID_RE =
10
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
5
 
12
6
  /** "cytario" root segment always renders teal; everything else uses hash color. */
13
7
  function scopeColor(segment: string, index: number): PillColor {
@@ -1,10 +1,4 @@
1
- import {
2
- IconButton,
3
- Input,
4
- Pill,
5
- Select,
6
- type SelectItem,
7
- } from "@cytario/design";
1
+ import { IconButton, Input, Pill, Select, type SelectItem } from "@cytario/design";
8
2
  import { Column } from "@tanstack/react-table";
9
3
  import { X } from "lucide-react";
10
4
  import { type ReactNode, useMemo } from "react";
@@ -37,9 +31,7 @@ export function ColumnFilterInput({
37
31
  if (filterOptions) {
38
32
  return [
39
33
  ALL_OPTION,
40
- ...filterOptions
41
- .filter((o) => o.value !== "")
42
- .map((o) => ({ id: o.value, name: o.label })),
34
+ ...filterOptions.filter((o) => o.value !== "").map((o) => ({ id: o.value, name: o.label })),
43
35
  ];
44
36
  }
45
37
  const facetedValues = column.getFacetedUniqueValues();
@@ -55,8 +47,7 @@ export function ColumnFilterInput({
55
47
 
56
48
  const selectedKey = filterValue || ALL_KEY;
57
49
 
58
- const setFilter = (key: string) =>
59
- column.setFilterValue(key === ALL_KEY ? undefined : key);
50
+ const setFilter = (key: string) => column.setFilterValue(key === ALL_KEY ? undefined : key);
60
51
 
61
52
  const clearFilter = () => column.setFilterValue(undefined);
62
53
 
@@ -64,11 +55,7 @@ export function ColumnFilterInput({
64
55
  if (!filterRender) return undefined;
65
56
  const render = filterRender;
66
57
  function FilterOption(item: SelectItem) {
67
- return item.id === ALL_KEY ? (
68
- <AllPill />
69
- ) : (
70
- render({ label: item.name, value: item.id })
71
- );
58
+ return item.id === ALL_KEY ? <AllPill /> : render({ label: item.name, value: item.id });
72
59
  }
73
60
  return FilterOption;
74
61
  }, [filterRender]);
@@ -89,9 +76,7 @@ export function ColumnFilterInput({
89
76
  <Input
90
77
  value={filterValue}
91
78
  onChange={setFilter}
92
- placeholder={
93
- filterPlaceholder ?? `Filter ${column.columnDef.header}...`
94
- }
79
+ placeholder={filterPlaceholder ?? `Filter ${column.columnDef.header}...`}
95
80
  aria-label={`Filter by ${column.columnDef.header}`}
96
81
  size="sm"
97
82
  />
@@ -1,10 +1,6 @@
1
1
  import { Header } from "@tanstack/react-table";
2
2
 
3
- export const ColumnResizeHandle = ({
4
- header,
5
- }: {
6
- header: Header<unknown, unknown>;
7
- }) => {
3
+ export const ColumnResizeHandle = ({ header }: { header: Header<unknown, unknown> }) => {
8
4
  return (
9
5
  <button
10
6
  type="button"
@@ -14,11 +14,7 @@ function getStyle(sortDirection: false | SortDirection) {
14
14
  }
15
15
 
16
16
  /** Sort indicator icon for a table column header. Shows on hover when unsorted, always visible when active. */
17
- export const ColumnSortButton = ({
18
- header,
19
- }: {
20
- header: Header<unknown, unknown>;
21
- }) => {
17
+ export const ColumnSortButton = ({ header }: { header: Header<unknown, unknown> }) => {
22
18
  const sortDirection = header.column.getIsSorted();
23
19
  const cx = twMerge("shrink-0", getStyle(sortDirection));
24
20
 
@@ -23,16 +23,14 @@ export function SelectionFooter({
23
23
  <div className="container mx-auto flex items-center justify-between">
24
24
  <div className="flex items-center gap-3">
25
25
  <span className="text-sm text-slate-600">
26
- <span className="font-medium text-slate-900">{selectedCount}</span>
27
- {" "}of {totalCount} selected
26
+ <span className="font-medium text-slate-900">{selectedCount}</span> of {totalCount}{" "}
27
+ selected
28
28
  </span>
29
29
  <Button variant="secondary" size="sm" onPress={onReset}>
30
30
  Clear selection
31
31
  </Button>
32
32
  </div>
33
- {children && (
34
- <div className="flex items-center gap-2">{children}</div>
35
- )}
33
+ {children && <div className="flex items-center gap-2">{children}</div>}
36
34
  </div>
37
35
  </div>
38
36
  );
@@ -22,11 +22,7 @@ import { useTableSorting } from "./useTableSorting";
22
22
  // Re-export types for external use
23
23
  export type { ColumnConfig, TableProps, CellRenderers } from "./types";
24
24
 
25
- function booleanSortingFn<TData>(
26
- rowA: Row<TData>,
27
- rowB: Row<TData>,
28
- columnId: string,
29
- ): number {
25
+ function booleanSortingFn<TData>(rowA: Row<TData>, rowB: Row<TData>, columnId: string): number {
30
26
  const a = rowA.getValue<boolean>(columnId) ? 1 : 0;
31
27
  const b = rowB.getValue<boolean>(columnId) ? 1 : 0;
32
28
  return a - b;
@@ -47,12 +43,8 @@ export function Table<TData extends object>({
47
43
  const { columnSizing, setColumnSizing } = useColumnWidths(columns, tableId);
48
44
  const anchorColumnId = columns.find((c) => c.anchor)?.id ?? columns[0]?.id;
49
45
  const { sorting, setSorting } = useTableSorting(tableId, anchorColumnId);
50
- const {
51
- columnVisibility,
52
- setColumnVisibility,
53
- toggleableColumns,
54
- toggleColumn,
55
- } = useColumnVisibility(columns, tableId);
46
+ const { columnVisibility, setColumnVisibility, toggleableColumns, toggleColumn } =
47
+ useColumnVisibility(columns, tableId);
56
48
  const { columnFilters, setColumnFilters, resetFilters } = useColumnFilters({
57
49
  tableId,
58
50
  });
@@ -195,11 +187,7 @@ export function Table<TData extends object>({
195
187
  </div>
196
188
 
197
189
  {/* Scrollable body — horizontal scrollbar visible */}
198
- <div
199
- ref={bodyRef}
200
- className="overflow-x-auto"
201
- onScroll={handleBodyScroll}
202
- >
190
+ <div ref={bodyRef} className="overflow-x-auto" onScroll={handleBodyScroll}>
203
191
  <table className="min-w-full" aria-hidden="true">
204
192
  <tbody>
205
193
  {data.length === 0 ? (
@@ -210,11 +198,7 @@ export function Table<TData extends object>({
210
198
  title="No results"
211
199
  description="No results match your filters"
212
200
  action={
213
- <Button
214
- variant="secondary"
215
- iconLeft={FilterX}
216
- onPress={resetFilters}
217
- >
201
+ <Button variant="secondary" iconLeft={FilterX} onPress={resetFilters}>
218
202
  Clear all filters
219
203
  </Button>
220
204
  }
@@ -21,25 +21,22 @@ export function TableBodyRow({
21
21
  enableRowSelection,
22
22
  className,
23
23
  }: TableBodyRowProps) {
24
- const handleKeyDown = useCallback(
25
- (event: KeyboardEvent<HTMLTableRowElement>) => {
26
- const tr = event.currentTarget;
24
+ const handleKeyDown = useCallback((event: KeyboardEvent<HTMLTableRowElement>) => {
25
+ const tr = event.currentTarget;
27
26
 
28
- if (event.key === "ArrowDown") {
29
- event.preventDefault();
30
- const next = tr.nextElementSibling as HTMLElement | null;
31
- next?.focus();
32
- } else if (event.key === "ArrowUp") {
33
- event.preventDefault();
34
- const prev = tr.previousElementSibling as HTMLElement | null;
35
- prev?.focus();
36
- } else if (event.key === "Enter") {
37
- const link = tr.querySelector("a");
38
- link?.click();
39
- }
40
- },
41
- [],
42
- );
27
+ if (event.key === "ArrowDown") {
28
+ event.preventDefault();
29
+ const next = tr.nextElementSibling as HTMLElement | null;
30
+ next?.focus();
31
+ } else if (event.key === "ArrowUp") {
32
+ event.preventDefault();
33
+ const prev = tr.previousElementSibling as HTMLElement | null;
34
+ prev?.focus();
35
+ } else if (event.key === "Enter") {
36
+ const link = tr.querySelector("a");
37
+ link?.click();
38
+ }
39
+ }, []);
43
40
 
44
41
  return (
45
42
  <tr
@@ -78,8 +75,7 @@ export function TableBodyRow({
78
75
  };
79
76
 
80
77
  const rawValue = cell.getValue();
81
- const useRawString =
82
- columnConfig?.ellipsis === "middle" && typeof rawValue === "string";
78
+ const useRawString = columnConfig?.ellipsis === "middle" && typeof rawValue === "string";
83
79
 
84
80
  const content = isIndexColumn
85
81
  ? rowIndex + 1
@@ -88,28 +84,20 @@ export function TableBodyRow({
88
84
  : flexRender(cell.column.columnDef.cell, cell.getContext());
89
85
 
90
86
  const copyValue =
91
- columnConfig?.copyable && typeof rawValue === "string"
92
- ? rawValue
93
- : undefined;
87
+ columnConfig?.copyable && typeof rawValue === "string" ? rawValue : undefined;
94
88
 
95
89
  return isIndexColumn ? (
96
90
  <th key={cell.id} className="p-2" style={style}>
97
91
  <div className="flex items-center gap-1 text-sm text-slate-500 tabular-nums justify-between">
98
92
  {enableRowSelection && (
99
- <Checkbox
100
- isSelected={row.getIsSelected()}
101
- onChange={() => row.toggleSelected()}
102
- />
93
+ <Checkbox isSelected={row.getIsSelected()} onChange={() => row.toggleSelected()} />
103
94
  )}
104
95
  <span>{rowIndex + 1}</span>
105
96
  </div>
106
97
  </th>
107
98
  ) : (
108
99
  <td key={cell.id} className={cxCell} style={style}>
109
- <TooltipSpan
110
- ellipsis={columnConfig?.ellipsis}
111
- copyValue={copyValue}
112
- >
100
+ <TooltipSpan ellipsis={columnConfig?.ellipsis} copyValue={copyValue}>
113
101
  {content}
114
102
  </TooltipSpan>
115
103
  </td>
@@ -1,9 +1,5 @@
1
1
  import { Checkbox, IconButton } from "@cytario/design";
2
- import {
3
- HeaderGroup,
4
- VisibilityState,
5
- flexRender,
6
- } from "@tanstack/react-table";
2
+ import { HeaderGroup, VisibilityState, flexRender } from "@tanstack/react-table";
7
3
  import { FilterX } from "lucide-react";
8
4
  import { twMerge } from "tailwind-merge";
9
5
 
@@ -59,10 +55,7 @@ export function TableHeaderRow({
59
55
  };
60
56
  const alignClass = alignClasses[columnConfig?.align ?? "left"];
61
57
 
62
- const cxTh = twMerge(
63
- baseClass,
64
- isIndexColumn ? indexClass : alignClass,
65
- );
58
+ const cxTh = twMerge(baseClass, isIndexColumn ? indexClass : alignClass);
66
59
 
67
60
  const style = {
68
61
  width: header.getSize(),
@@ -93,11 +86,7 @@ export function TableHeaderRow({
93
86
  className={cxTh}
94
87
  style={style}
95
88
  aria-sort={
96
- isSorted === "asc"
97
- ? "ascending"
98
- : isSorted === "desc"
99
- ? "descending"
100
- : undefined
89
+ isSorted === "asc" ? "ascending" : isSorted === "desc" ? "descending" : undefined
101
90
  }
102
91
  >
103
92
  {isIndexColumn ? (
@@ -110,9 +99,7 @@ export function TableHeaderRow({
110
99
  header.getContext().table.getIsSomeRowsSelected() &&
111
100
  !header.getContext().table.getIsAllRowsSelected()
112
101
  }
113
- onChange={() =>
114
- header.getContext().table.toggleAllRowsSelected()
115
- }
102
+ onChange={() => header.getContext().table.toggleAllRowsSelected()}
116
103
  />
117
104
  )}
118
105
  <TableMenu
@@ -144,16 +131,11 @@ export function TableHeaderRow({
144
131
  isRight && "flex-row-reverse",
145
132
  isSorted && "text-slate-900",
146
133
  )}
147
- onClick={
148
- header.column.getToggleSortingHandler() ?? undefined
149
- }
134
+ onClick={header.column.getToggleSortingHandler() ?? undefined}
150
135
  >
151
136
  <div className="min-w-0">
152
137
  <TooltipSpan>
153
- {flexRender(
154
- header.column.columnDef.header,
155
- header.getContext(),
156
- )}
138
+ {flexRender(header.column.columnDef.header, header.getContext())}
157
139
  </TooltipSpan>
158
140
  </div>
159
141
 
@@ -164,10 +146,7 @@ export function TableHeaderRow({
164
146
  <div className={tableHeadCx}>
165
147
  <div className="min-w-0">
166
148
  <TooltipSpan>
167
- {flexRender(
168
- header.column.columnDef.header,
169
- header.getContext(),
170
- )}
149
+ {flexRender(header.column.columnDef.header, header.getContext())}
171
150
  </TooltipSpan>
172
151
  </div>
173
152
  </div>
@@ -1,10 +1,4 @@
1
- import {
2
- IconButton,
3
- Menu,
4
- MenuCheckboxItem,
5
- MenuItem,
6
- MenuSeparator,
7
- } from "@cytario/design";
1
+ import { IconButton, Menu, MenuCheckboxItem, MenuItem, MenuSeparator } from "@cytario/design";
8
2
  import type { VisibilityState } from "@tanstack/react-table";
9
3
  import { Columns3 } from "lucide-react";
10
4
  import type { Selection } from "react-aria-components";
@@ -28,16 +22,11 @@ export function TableMenu({
28
22
  const store = useTableStore(tableId);
29
23
 
30
24
  const selectedKeys = new Set(
31
- toggleableColumns
32
- .filter((col) => columnVisibility[col.id] !== false)
33
- .map((col) => col.id),
25
+ toggleableColumns.filter((col) => columnVisibility[col.id] !== false).map((col) => col.id),
34
26
  );
35
27
 
36
28
  function handleSelectionChange(keys: Selection) {
37
- const newKeys =
38
- keys === "all"
39
- ? new Set(toggleableColumns.map((c) => c.id))
40
- : keys;
29
+ const newKeys = keys === "all" ? new Set(toggleableColumns.map((c) => c.id)) : keys;
41
30
 
42
31
  for (const col of toggleableColumns) {
43
32
  const wasSelected = columnVisibility[col.id] !== false;
@@ -67,12 +56,7 @@ export function TableMenu({
67
56
  </>
68
57
  }
69
58
  >
70
- <IconButton
71
- icon={Columns3}
72
- variant="ghost"
73
- size="sm"
74
- aria-label="Column settings"
75
- />
59
+ <IconButton icon={Columns3} variant="ghost" size="sm" aria-label="Column settings" />
76
60
  </Menu>
77
61
  );
78
62
  }
@@ -1,8 +1,4 @@
1
- import type {
2
- ColumnFiltersState,
3
- SortingState,
4
- VisibilityState,
5
- } from "@tanstack/react-table";
1
+ import type { ColumnFiltersState, SortingState, VisibilityState } from "@tanstack/react-table";
6
2
  import { createStore } from "zustand";
7
3
  import { devtools, persist } from "zustand/middleware";
8
4
 
@@ -47,8 +43,7 @@ export const createTableStore = (tableId: string) =>
47
43
  "setColumnWidth",
48
44
  ),
49
45
 
50
- setSorting: (sorting: SortingState) =>
51
- set({ sorting }, false, "setSorting"),
46
+ setSorting: (sorting: SortingState) => set({ sorting }, false, "setSorting"),
52
47
 
53
48
  setColumnVisibility: (columnVisibility: VisibilityState) =>
54
49
  set({ columnVisibility }, false, "setColumnVisibility"),
@@ -90,8 +85,7 @@ export const createTableStore = (tableId: string) =>
90
85
  },
91
86
  ),
92
87
  onRehydrateStorage: () => (_state, error) => {
93
- if (error)
94
- console.error(`[TableStore-${tableId}] Rehydration failed:`, error);
88
+ if (error) console.error(`[TableStore-${tableId}] Rehydration failed:`, error);
95
89
  },
96
90
  },
97
91
  ),
@@ -51,8 +51,6 @@ export function useTableStore(tableId: string): StoreApi<TableStore> {
51
51
  * }
52
52
  * ```
53
53
  */
54
- export function getTableStore(
55
- tableId: string,
56
- ): StoreApi<TableStore> | undefined {
54
+ export function getTableStore(tableId: string): StoreApi<TableStore> | undefined {
57
55
  return tableStores.get(tableId);
58
56
  }
@@ -1,9 +1,4 @@
1
- import type {
2
- ColumnDefBase,
3
- FilterFn,
4
- OnChangeFn,
5
- RowSelectionState,
6
- } from "@tanstack/react-table";
1
+ import type { ColumnDefBase, FilterFn, OnChangeFn, RowSelectionState } from "@tanstack/react-table";
7
2
  import { ReactNode } from "react";
8
3
 
9
4
  // Behavior props aligned with TanStack naming
@@ -48,7 +43,8 @@ interface ColumnVisibility {
48
43
  }
49
44
 
50
45
  export interface ColumnConfig
51
- extends ColumnBehavior,
46
+ extends
47
+ ColumnBehavior,
52
48
  ColumnSorting,
53
49
  ColumnSizing,
54
50
  ColumnDisplay,
@@ -63,9 +59,7 @@ export interface ColumnConfig
63
59
  * row object and returns a ReactNode. Columns without a renderer display
64
60
  * their raw accessor value.
65
61
  */
66
- export type CellRenderers<TData> = Partial<
67
- Record<string, (row: TData) => ReactNode>
68
- >;
62
+ export type CellRenderers<TData> = Partial<Record<string, (row: TData) => ReactNode>>;
69
63
 
70
64
  export interface TableProps<TData extends object> {
71
65
  columns: ColumnConfig[];
@@ -25,10 +25,7 @@ export function useColumnFilters({ tableId }: UseColumnFiltersOptions) {
25
25
  const setColumnFilters: OnChangeFn<ColumnFiltersState> = useCallback(
26
26
  (updaterOrValue) => {
27
27
  const current = store.getState().columnFilters;
28
- const next =
29
- typeof updaterOrValue === "function"
30
- ? updaterOrValue(current)
31
- : updaterOrValue;
28
+ const next = typeof updaterOrValue === "function" ? updaterOrValue(current) : updaterOrValue;
32
29
  setFiltersStore(next);
33
30
  },
34
31
  [store, setFiltersStore],
@@ -25,8 +25,7 @@ export function useColumnVisibility(columns: ColumnConfig[], tableId: string) {
25
25
  if (col.anchor) {
26
26
  vis[col.id] = true;
27
27
  } else {
28
- vis[col.id] =
29
- storedVisibility[col.id] ?? (col.defaultVisible !== false);
28
+ vis[col.id] = storedVisibility[col.id] ?? col.defaultVisible !== false;
30
29
  }
31
30
  });
32
31
  return vis;
@@ -37,14 +36,11 @@ export function useColumnVisibility(columns: ColumnConfig[], tableId: string) {
37
36
  const current = store.getState().columnVisibility;
38
37
  const currentFull: VisibilityState = {};
39
38
  columns.forEach((col) => {
40
- currentFull[col.id] =
41
- current[col.id] ?? (col.defaultVisible !== false);
39
+ currentFull[col.id] = current[col.id] ?? col.defaultVisible !== false;
42
40
  });
43
41
 
44
42
  const next =
45
- typeof updaterOrValue === "function"
46
- ? updaterOrValue(currentFull)
47
- : updaterOrValue;
43
+ typeof updaterOrValue === "function" ? updaterOrValue(currentFull) : updaterOrValue;
48
44
 
49
45
  // Enforce anchors
50
46
  columns.forEach((col) => {
@@ -56,10 +52,7 @@ export function useColumnVisibility(columns: ColumnConfig[], tableId: string) {
56
52
  [store, columns, setVisibilityStore],
57
53
  );
58
54
 
59
- const toggleableColumns = useMemo(
60
- () => columns.filter((col) => !col.anchor),
61
- [columns],
62
- );
55
+ const toggleableColumns = useMemo(() => columns.filter((col) => !col.anchor), [columns]);
63
56
 
64
57
  const toggleColumn = useCallback(
65
58
  (columnId: string) => {
@@ -45,9 +45,7 @@ export function useColumnWidths(columns: ColumnConfig[], tableId: string) {
45
45
  });
46
46
 
47
47
  const next =
48
- typeof updaterOrValue === "function"
49
- ? updaterOrValue(currentSizing)
50
- : updaterOrValue;
48
+ typeof updaterOrValue === "function" ? updaterOrValue(currentSizing) : updaterOrValue;
51
49
 
52
50
  // Update only columns that changed (skip index column)
53
51
  Object.entries(next).forEach(([columnId, width]) => {
@@ -10,33 +10,25 @@ import { useTableStore } from "./state/useTableStore";
10
10
  * When no stored sorting exists, falls back to sorting by
11
11
  * `defaultSortColumnId` ascending (typically the anchor column).
12
12
  */
13
- export function useTableSorting(
14
- tableId: string,
15
- defaultSortColumnId?: string,
16
- ) {
13
+ export function useTableSorting(tableId: string, defaultSortColumnId?: string) {
17
14
  const store = useTableStore(tableId);
18
15
 
19
16
  const storedSorting = useStore(store, (state) => state.sorting);
20
17
  const setSortingStore = useStore(store, (state) => state.setSorting);
21
18
 
22
19
  const defaultSorting: SortingState = useMemo(
23
- () =>
24
- defaultSortColumnId ? [{ id: defaultSortColumnId, desc: false }] : [],
20
+ () => (defaultSortColumnId ? [{ id: defaultSortColumnId, desc: false }] : []),
25
21
  [defaultSortColumnId],
26
22
  );
27
23
 
28
- const sorting =
29
- storedSorting.length > 0 ? storedSorting : defaultSorting;
24
+ const sorting = storedSorting.length > 0 ? storedSorting : defaultSorting;
30
25
 
31
26
  const setSorting: OnChangeFn<SortingState> = useCallback(
32
27
  (updaterOrValue) => {
33
28
  const currentSorting = store.getState().sorting;
34
29
  const current = currentSorting.length > 0 ? currentSorting : defaultSorting;
35
30
 
36
- const next =
37
- typeof updaterOrValue === "function"
38
- ? updaterOrValue(current)
39
- : updaterOrValue;
31
+ const next = typeof updaterOrValue === "function" ? updaterOrValue(current) : updaterOrValue;
40
32
 
41
33
  setSortingStore(next);
42
34
  },
@@ -1,15 +1,8 @@
1
- import React, {
2
- useState,
3
- useRef,
4
- useEffect,
5
- useLayoutEffect,
6
- ReactNode,
7
- } from "react";
1
+ import React, { useState, useRef, useEffect, useLayoutEffect, ReactNode } from "react";
8
2
  import ReactDOM from "react-dom";
9
3
 
10
4
  // Use useLayoutEffect on client, useEffect on server to avoid SSR warning
11
- const useIsomorphicLayoutEffect =
12
- typeof window !== "undefined" ? useLayoutEffect : useEffect;
5
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
13
6
 
14
7
  interface TooltipProps {
15
8
  content: ReactNode;
@@ -34,10 +27,7 @@ const tooltipClassName = `
34
27
  blurred backdrop-filter backdrop-blur-sm
35
28
  `;
36
29
 
37
- export const Tooltip: React.FC<TooltipProps> = ({
38
- content,
39
- children,
40
- }) => {
30
+ export const Tooltip: React.FC<TooltipProps> = ({ content, children }) => {
41
31
  const [visible, setVisible] = useState(false);
42
32
  const [coordsInitial, setCoordsInitial] = useState<Coords>({ x: 0, y: 0 });
43
33
  const [coords, setCoords] = useState<Coords>({ x: 0, y: 0 });
@@ -140,10 +130,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
140
130
  >
141
131
  {children}
142
132
  {typeof window !== "undefined" &&
143
- ReactDOM.createPortal(
144
- tooltipContent,
145
- document.getElementById("tooltip")!,
146
- )}
133
+ ReactDOM.createPortal(tooltipContent, document.getElementById("tooltip")!)}
147
134
  </span>
148
135
  );
149
136
  };