@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.
- package/README.md +20 -20
- package/app/.server/auth/README.md +8 -8
- package/app/.server/auth/authMiddleware.ts +9 -25
- package/app/.server/auth/exchangeAuthCode.ts +2 -6
- package/app/.server/auth/getS3Client.ts +3 -13
- package/app/.server/auth/getSessionCredentials.ts +6 -20
- package/app/.server/auth/getUserInfo.ts +2 -6
- package/app/.server/auth/keycloakAdmin/client.ts +2 -9
- package/app/.server/auth/keycloakAdmin/groups.ts +9 -26
- package/app/.server/auth/keycloakAdmin/users.ts +7 -23
- package/app/.server/auth/oauthState.ts +4 -13
- package/app/.server/auth/redirectIfAuthenticated.ts +1 -3
- package/app/.server/auth/refreshAuthTokens.ts +5 -19
- package/app/.server/auth/sessionMiddleware.ts +1 -4
- package/app/.server/auth/sessionStorage.ts +1 -4
- package/app/.server/auth/verifyIdToken.ts +1 -3
- package/app/.server/auth/wellKnownEndpoints.ts +1 -4
- package/app/.server/db/redis.ts +5 -1
- package/app/.server/logging.ts +1 -4
- package/app/.server/requestDurationMiddleware.ts +1 -4
- package/app/components/.client/ImageViewer/README.md +5 -5
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsController.tsx +7 -9
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerBrightfieldItem.tsx +1 -2
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItem.tsx +2 -5
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItemList.tsx +7 -15
- package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorPicker.tsx +2 -9
- package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorSwatch.tsx +1 -4
- package/app/components/.client/ImageViewer/components/ChannelsController/DomainSlider.tsx +1 -3
- package/app/components/.client/ImageViewer/components/ChannelsController/Histogram.tsx +16 -18
- package/app/components/.client/ImageViewer/components/ChannelsController/HistogramChannel.tsx +2 -8
- package/app/components/.client/ImageViewer/components/ChannelsController/MinMaxSettings.tsx +6 -15
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarDragHandle.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarToggle.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureItem.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/Presets.tsx +3 -11
- package/app/components/.client/ImageViewer/components/FeatureBar/useFeatureBar.tsx +16 -25
- package/app/components/.client/ImageViewer/components/Image/Channels/useChannelsLayer.ts +7 -18
- package/app/components/.client/ImageViewer/components/Image/ImageContainer.tsx +1 -1
- package/app/components/.client/ImageViewer/components/Image/ImagePanel.tsx +6 -26
- package/app/components/.client/ImageViewer/components/Image/ImagePreview.tsx +2 -9
- package/app/components/.client/ImageViewer/components/Image/Overlays/AdditivePolygonLayer.tsx +1 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/AdditiveScatterplotLayer.tsx +1 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/OverlaysLayer.tsx +6 -24
- package/app/components/.client/ImageViewer/components/Image/Overlays/markerUniforms.ts +2 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/useOverlaysLayer.tsx +7 -21
- package/app/components/.client/ImageViewer/components/Image/useInitializeChannels.ts +1 -7
- package/app/components/.client/ImageViewer/components/Image/useResizeObserver.ts +1 -1
- package/app/components/.client/ImageViewer/components/Magnifier.tsx +5 -13
- package/app/components/.client/ImageViewer/components/Measurements/ActiveViewStatePreview.tsx +3 -8
- package/app/components/.client/ImageViewer/components/Measurements/CursorTick.tsx +2 -7
- package/app/components/.client/ImageViewer/components/Measurements/Ruler.tsx +1 -8
- package/app/components/.client/ImageViewer/components/Measurements/SlideCarrier.tsx +3 -13
- package/app/components/.client/ImageViewer/components/Measurements/Tick.tsx +1 -1
- package/app/components/.client/ImageViewer/components/Measurements/calculateViewStateToFit.ts +1 -1
- package/app/components/.client/ImageViewer/components/Measurements/useMeasurements.ts +9 -28
- package/app/components/.client/ImageViewer/components/OverlaysController/AddOverlay.tsx +4 -13
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlayPicker.modal.tsx +1 -6
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.Item.tsx +24 -54
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.tsx +1 -3
- package/app/components/.client/ImageViewer/components/SplitViewToggle.tsx +1 -3
- package/app/components/.client/ImageViewer/components/ViewerHeader.tsx +1 -3
- package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.d.ts +9 -11
- package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.js +11 -11
- package/app/components/.client/ImageViewer/state/decoders/decoder.worker.js +49 -49
- package/app/components/.client/ImageViewer/state/decoders/genericDecoder.ts +76 -81
- package/app/components/.client/ImageViewer/state/decoders/jp2k-decoder.ts +9 -9
- package/app/components/.client/ImageViewer/state/decoders/lzwDecoder.ts +9 -9
- package/app/components/.client/ImageViewer/state/loaders/loadBioformatsZarrWithCredentials.ts +10 -22
- package/app/components/.client/ImageViewer/state/store/ViewerStoreContext.tsx +4 -18
- package/app/components/.client/ImageViewer/state/store/createViewerStore.ts +110 -194
- package/app/components/.client/ImageViewer/state/store/getInitialChannelsState.ts +2 -6
- package/app/components/.client/ImageViewer/state/store/selectors.ts +9 -9
- package/app/components/.client/ImageViewer/state/store/types.ts +3 -12
- package/app/components/.client/ImageViewer/state/transport/CredentialedHTTPStore.ts +1 -5
- package/app/components/.client/ImageViewer/state/transport/SigV4TiffClient.ts +2 -9
- package/app/components/.client/ImageViewer/utils/getSelectionStats.ts +1 -4
- package/app/components/.client/ImageViewer/utils/handleImageViewerHover.ts +1 -1
- package/app/components/.client/ImageViewer/utils/mapChannelConfigsToState.ts +2 -4
- package/app/components/.client/ImageViewer/utils/useTilesLoading.ts +3 -3
- package/app/components/AppHeader.tsx +1 -4
- package/app/components/Breadcrumbs/Breadcrumbs.tsx +4 -13
- package/app/components/Breadcrumbs/getCrumbs.tsx +1 -1
- package/app/components/ClientOnly.tsx +1 -1
- package/app/components/Container.tsx +3 -15
- package/app/components/DataGrid/ConvertOverlay.modal.tsx +1 -6
- package/app/components/DataGrid/DataGrid.tsx +7 -27
- package/app/components/DataGrid/WktSvg.tsx +2 -4
- package/app/components/DataGrid/getParquetSchema.ts +1 -4
- package/app/components/DescriptionList.tsx +1 -3
- package/app/components/DirectoryView/ConnectionMenu.tsx +8 -22
- package/app/components/DirectoryView/DirectoryView.tsx +10 -46
- package/app/components/DirectoryView/DirectoryViewGrid.tsx +16 -49
- package/app/components/DirectoryView/DirectoryViewTableConnection.tsx +1 -4
- package/app/components/DirectoryView/DirectoryViewTableDirectory.tsx +2 -7
- package/app/components/DirectoryView/DirectoryViewTree.tsx +5 -21
- package/app/components/DirectoryView/FilterBar.tsx +9 -48
- package/app/components/DirectoryView/buildDirectoryTree.ts +6 -25
- package/app/components/DirectoryView/filterNodes.ts +4 -11
- package/app/components/DirectoryView/modals/Cyberduck.modal.tsx +6 -15
- package/app/components/DirectoryView/modals/FileInfo.modal.tsx +1 -5
- package/app/components/DirectoryView/useLayoutStore.ts +5 -25
- package/app/components/GlobalSearch/GlobalSearch.tsx +1 -4
- package/app/components/GlobalSearch/SearchBar.tsx +1 -7
- package/app/components/GlobalSearch/Suggestions.tsx +0 -1
- package/app/components/ImageViewer/state/formatRegistry.ts +5 -18
- package/app/components/LavaLoader.tsx +4 -11
- package/app/components/Layout/Footer.tsx +1 -4
- package/app/components/Pills/ScopePill.tsx +2 -8
- package/app/components/Table/ColumnFilterInput.tsx +5 -20
- package/app/components/Table/ColumnResizeHandle.tsx +1 -5
- package/app/components/Table/ColumnSortButton.tsx +1 -5
- package/app/components/Table/SelectionFooter.tsx +3 -5
- package/app/components/Table/Table.tsx +5 -21
- package/app/components/Table/TableBodyRow.tsx +19 -31
- package/app/components/Table/TableHeaderRow.tsx +7 -28
- package/app/components/Table/TableMenu.tsx +4 -20
- package/app/components/Table/state/createTableStore.ts +3 -9
- package/app/components/Table/state/useTableStore.ts +1 -3
- package/app/components/Table/types.ts +4 -10
- package/app/components/Table/useColumnFilters.ts +1 -4
- package/app/components/Table/useColumnVisibility.ts +4 -11
- package/app/components/Table/useColumnWidths.ts +1 -3
- package/app/components/Table/useTableSorting.ts +4 -12
- package/app/components/Tooltip/Tooltip.tsx +4 -17
- package/app/components/Tooltip/TooltipSpan.tsx +5 -28
- package/app/components/Tooltip/useCopyToClipboard.ts +1 -3
- package/app/components/Tooltip/useMiddleEllipsis.ts +2 -7
- package/app/components/Tooltip/useOverflowDetection.ts +2 -5
- package/app/components/UserMenu.tsx +2 -9
- package/app/entry.server.tsx +9 -19
- package/app/hooks/useSearchParam.ts +2 -4
- package/app/lib/bootstrapPluginsCore.ts +4 -9
- package/app/root.tsx +4 -15
- package/app/routes/admin/assertAdminScope.ts +1 -3
- package/app/routes/admin/assertGroupPathsInScope.ts +3 -11
- package/app/routes/admin/assertGroupsInScope.ts +3 -11
- package/app/routes/admin/assertUsersInScope.ts +2 -8
- package/app/routes/admin/bulkInvite/bulkInvite.action.ts +2 -13
- package/app/routes/admin/bulkInvite/bulkInvite.form.tsx +18 -35
- package/app/routes/admin/createGroup/createGroup.action.ts +3 -10
- package/app/routes/admin/createGroup/createGroup.form.tsx +2 -9
- package/app/routes/admin/createGroup/createGroup.modal.tsx +1 -5
- package/app/routes/admin/inviteUser/inviteUser.action.ts +1 -4
- package/app/routes/admin/inviteUser/inviteUser.form.tsx +2 -8
- package/app/routes/admin/inviteUser/inviteUser.loader.ts +1 -4
- package/app/routes/admin/inviteUser/inviteUser.modal.tsx +3 -16
- package/app/routes/admin/updateUser/updateUser.form.tsx +4 -15
- package/app/routes/admin/updateUser/updateUser.modal.tsx +5 -23
- package/app/routes/admin/updateUser/userDetail.action.ts +2 -10
- package/app/routes/admin/users/BulkActions.tsx +15 -38
- package/app/routes/admin/users/bulkUsers.action.ts +2 -9
- package/app/routes/admin/users/bulkUsers.schema.ts +1 -6
- package/app/routes/admin/users/users.route.tsx +14 -63
- package/app/routes/api/cyberduck-profile.$name.ts +6 -2
- package/app/routes/auth/callback.route.tsx +8 -33
- package/app/routes/auth/login.route.tsx +8 -11
- package/app/routes/auth/logout.route.tsx +4 -14
- package/app/routes/config.route.tsx +1 -5
- package/app/routes/connections/connection.form.tsx +5 -14
- package/app/routes/connections/connection.schema.ts +4 -16
- package/app/routes/connections/connections.loader.ts +11 -23
- package/app/routes/connections/connections.route.tsx +1 -5
- package/app/routes/connections/connections.server.ts +1 -3
- package/app/routes/connections/createConnection.action.ts +2 -8
- package/app/routes/connections/createConnection.modal.tsx +3 -13
- package/app/routes/connections/deleteConnection.action.ts +1 -4
- package/app/routes/connections/updateConnection.action.ts +2 -8
- package/app/routes/connections/updateConnection.modal.tsx +4 -14
- package/app/routes/home/home.route.tsx +8 -33
- package/app/routes/layouts/ModalOutlet.tsx +6 -18
- package/app/routes/objects/objects.loader.ts +5 -19
- package/app/routes/objects/objects.route.tsx +11 -30
- package/app/routes/presign.route.tsx +5 -18
- package/app/routes/recent.route.tsx +1 -4
- package/app/routes/search.route.tsx +4 -17
- package/app/routes.ts +1 -4
- package/app/tailwind.css +17 -12
- package/app/types/cornerstone-codecs.d.ts +25 -29
- package/app/utils/connectionsStore/selectors.ts +2 -6
- package/app/utils/connectionsStore/useConnectionsStore.ts +2 -6
- package/app/utils/db/convertCsvToParquet.ts +1 -3
- package/app/utils/db/createDatabase.ts +1 -3
- package/app/utils/db/createSingleton.ts +1 -1
- package/app/utils/db/getBlobFromObjectNode.ts +3 -9
- package/app/utils/db/getGeomQuery.ts +1 -1
- package/app/utils/db/getMarkerInfoWasm.ts +1 -3
- package/app/utils/db/getTileBoundingBox.ts +1 -4
- package/app/utils/db/sqlQueries.ts +1 -4
- package/app/utils/fileType.ts +80 -10
- package/app/utils/filterObjects.ts +2 -5
- package/app/utils/localFilesStore/useFileStore.ts +7 -7
- package/app/utils/recentlyViewed.server.ts +1 -4
- package/app/utils/resourceId.ts +3 -11
- package/app/utils/s3Provider.ts +3 -7
- package/app/utils/signedFetch.ts +4 -13
- package/bin-src/codegen.ts +4 -1
- package/package.json +5 -1
- package/prisma/seed.ts +1 -2
- package/public/favicon/site.webmanifest +1 -1
- package/server.js +1 -4
- package/server.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
const tr = event.currentTarget;
|
|
24
|
+
const handleKeyDown = useCallback((event: KeyboardEvent<HTMLTableRowElement>) => {
|
|
25
|
+
const tr = event.currentTarget;
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
|
};
|