From 43b2ee0c680a984cba8742a992545526321253e6 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Mon, 12 May 2025 23:31:05 +0200 Subject: [PATCH] feat(studio): db --- frontend/apps/studio/package.json | 3 + frontend/apps/studio/public/logo.svg | 12 +- frontend/apps/studio/src/app.tsx | 36 +- .../studio/src/components/cell-expanding.tsx | 158 +++++++ .../studio/src/components/database-table.tsx | 423 ++++++++++++++++++ .../apps/studio/src/components/layout.tsx | 12 +- frontend/apps/studio/src/queries/global.ts | 19 +- frontend/apps/studio/src/routeTree.gen.ts | 30 +- .../apps/studio/src/routes/_layout/db.tsx | 105 +++++ frontend/apps/studio/src/stores/db.ts | 80 ++++ .../packages/components/src/ui/checkbox.tsx | 12 +- frontend/packages/components/src/ui/table.tsx | 6 +- yarn.lock | 50 +++ 13 files changed, 900 insertions(+), 46 deletions(-) create mode 100644 frontend/apps/studio/src/components/cell-expanding.tsx create mode 100644 frontend/apps/studio/src/components/database-table.tsx create mode 100644 frontend/apps/studio/src/routes/_layout/db.tsx create mode 100644 frontend/apps/studio/src/stores/db.ts diff --git a/frontend/apps/studio/package.json b/frontend/apps/studio/package.json index 4a9698a70a..683ee69244 100644 --- a/frontend/apps/studio/package.json +++ b/frontend/apps/studio/package.json @@ -20,6 +20,8 @@ "@rivet-gg/icons": "workspace:*", "@sentry/react": "^8.26.0", "@sentry/vite-plugin": "^2.22.2", + "@tanstack/react-query": "^5.76.0", + "@tanstack/react-query-devtools": "^5.76.0", "@tanstack/react-router": "^1.114.25", "@tanstack/react-table": "^8.20.6", "@tanstack/router-devtools": "^1.114.25", @@ -43,6 +45,7 @@ "jotai": "^2.12.2", "jotai-devtools": "^0.11.0", "jotai-effect": "^2.0.2", + "jotai-tanstack-query": "^0.9.0", "postcss": "^8.4.38", "posthog-js": "^1.144.2", "react": "^19.0.0", diff --git a/frontend/apps/studio/public/logo.svg b/frontend/apps/studio/public/logo.svg index 9086ec836c..81337354dd 100644 --- a/frontend/apps/studio/public/logo.svg +++ b/frontend/apps/studio/public/logo.svg @@ -1,3 +1,11 @@ - - + + + + + + + + + + diff --git a/frontend/apps/studio/src/app.tsx b/frontend/apps/studio/src/app.tsx index 248da09a8d..572a8b5d22 100644 --- a/frontend/apps/studio/src/app.tsx +++ b/frontend/apps/studio/src/app.tsx @@ -17,7 +17,12 @@ import { currentActorIdAtom, pickActorListFilters, } from "@rivet-gg/components/actors"; -import { useAtom } from "jotai"; +import { Provider, useAtom } from "jotai"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { queryClient } from "./queries/global"; +import { useHydrateAtoms } from "jotai/utils"; +import { queryClientAtom } from "jotai-tanstack-query"; declare module "@tanstack/react-router" { interface Register { @@ -60,18 +65,29 @@ function InnerApp() { return ; } +const HydrateAtoms = ({ children }) => { + useHydrateAtoms(new Map([[queryClientAtom, queryClient]])); + return children; +}; + export function App() { return ( - - }> - - - - - - - + + + + + }> + + + + + + + + + {/* */} + ); } diff --git a/frontend/apps/studio/src/components/cell-expanding.tsx b/frontend/apps/studio/src/components/cell-expanding.tsx new file mode 100644 index 0000000000..97997b2779 --- /dev/null +++ b/frontend/apps/studio/src/components/cell-expanding.tsx @@ -0,0 +1,158 @@ +import { + type Cell, + type Column, + functionalUpdate, + makeStateUpdater, + type OnChangeFn, + type Row, + type RowData, + type Table, + type TableFeature, + type Updater, +} from "@tanstack/react-table"; + +export type ExpandedCellState = Record>; +export interface ExpandedCellTableState { + expandedCells: ExpandedCellState; +} +export interface ExpandedCellOptions { + onExpandedCellsChange?: OnChangeFn; + enableCellExpanding?: boolean; + getCellCanExpand?: (row: Row, cell: Cell) => boolean; +} +export interface ExpandedCellInstance { + setCellExpanded: (updater: Updater) => void; +} +export interface ExpandedCellRow { + getIsSomeCellExpanded: () => boolean; + getExpandedCell: () => Cell | undefined; +} +export interface ExpandedCell { + getToggleExpandedHandler: () => () => void; + getIsExpanded: () => boolean; + getCanExpand: () => boolean; + toggleExpanded: (expanded?: boolean) => void; +} + +declare module "@tanstack/react-table" { + //merge our new feature's state with the existing table state + interface TableState extends ExpandedCellTableState {} + //merge our new feature's options with the existing table options + interface TableOptionsResolved + extends ExpandedCellOptions {} + //merge our new feature's instance APIs with the existing table instance APIs + interface Table + extends ExpandedCellInstance {} + // if you need to add cell instance APIs... + interface Cell extends ExpandedCell {} + // if you need to add row instance APIs... + interface Row extends ExpandedCellRow {} + // if you need to add column instance APIs... + // interface Column extends DensityColumn + // if you need to add header instance APIs... + // interface Header extends DensityHeader + + // Note: declaration merging on `ColumnDef` is not possible because it is a type, not an interface. + // But you can still use declaration merging on `ColumnDef.meta` +} + +// Here is all of the actual javascript code for our new feature +export const CellExpanding: TableFeature = { + // define the new feature's initial state + getInitialState: (state): ExpandedCellTableState => { + return { + expandedCells: {}, + ...state, + }; + }, + + // define the new feature's default options + getDefaultOptions: ( + table: Table, + ): ExpandedCellOptions => { + return { + enableCellExpanding: true, + onExpandedCellsChange: makeStateUpdater("expandedCells", table), + }; + }, + + createTable: (table: Table): void => { + table.setCellExpanded = (updater) => { + const safeUpdater: Updater = (old) => { + const newState = functionalUpdate(updater, old); + return newState; + }; + return table.options.onExpandedCellsChange?.(safeUpdater); + }; + }, + + createRow: ( + row: Row, + table: Table, + ): void => { + row.getIsSomeCellExpanded = () => { + const expanded = table.getState().expandedCells; + return !!expanded?.[row.id]; + }; + row.getExpandedCell = (() => { + const expanded = table.getState().expandedCells; + const cellIds = Object.keys(expanded?.[row.id]); + return row.getAllCells().find((cell) => cellIds.includes(cell.id)); + }) as ExpandedCellRow["getExpandedCell"]; + }, + + createCell: ( + cell: Cell, + column: Column, + row: Row, + table: Table, + ): void => { + cell.getIsExpanded = () => { + return !!table.getState().expandedCells[row.id]?.[cell.id]; + }; + cell.toggleExpanded = (expanded) => { + row.toggleExpanded(expanded); + table.setCellExpanded((old) => { + const exists = !!old?.[row.id]?.[cell.id]; + + const oldExpanded: ExpandedCellState = old; + const newValue = expanded ?? !exists; + + if (!exists && newValue) { + return { + ...oldExpanded, + [row.id]: { + [cell.id]: true, + }, + }; + } + + if (exists && !newValue) { + return { + ...oldExpanded, + [row.id]: { + ...(oldExpanded?.[row.id] ?? {}), + [cell.id]: false, + }, + }; + } + + return old; + }); + }; + cell.getToggleExpandedHandler = () => { + return () => cell.toggleExpanded(); + }; + cell.getCanExpand = () => { + return ( + table.options.getCellCanExpand?.(row, cell) ?? + table.options.enableCellExpanding ?? + true + ); + }; + }, + // if you need to add column instance APIs... + // createColumn: (column, table): void => {}, + // if you need to add header instance APIs... + // createHeader: (header, table): void => {}, +}; diff --git a/frontend/apps/studio/src/components/database-table.tsx b/frontend/apps/studio/src/components/database-table.tsx new file mode 100644 index 0000000000..befe1a8428 --- /dev/null +++ b/frontend/apps/studio/src/components/database-table.tsx @@ -0,0 +1,423 @@ +import { + ScrollArea, + Table, + TableHeader, + TableRow, + TableHead, + Button, + cn, + TableBody, + TableCell, + Checkbox, +} from "@rivet-gg/components"; +import { + Icon, + faArrowUpWideShort, + faArrowDownWideShort, + faAnglesUpDown, + faChevronUp, + faChevronDown, +} from "@rivet-gg/icons"; +import { + type RowSelectionState, + // SortingState, + type ExpandedState, + getCoreRowModel, + getExpandedRowModel, + getSortedRowModel, + flexRender, + type SortingState, + useReactTable as useTable, + createColumnHelper, +} from "@tanstack/react-table"; +import { useState, useMemo, useCallback } from "react"; +import { Fragment } from "react"; +import { type ExpandedCellState, CellExpanding } from "./cell-expanding"; +import { useQuery } from "@tanstack/react-query"; +import { + dbInfoAtom, + tableInfo, + tableReferenceRowsQueryOptions, +} from "@/stores/db"; +import { useAtom, useAtomValue } from "jotai"; +import { selectAtom } from "jotai/utils"; + +interface DatabaseTableProps { + columns: any[]; + data: any[]; + references?: any[]; + className?: string; + + enableRowSelection?: boolean; + enableSorting?: boolean; + enableCellExpanding?: boolean; + enableColumnResizing?: boolean; +} + +export function DatabaseTable({ + columns: dbCols, + data, + references, + className, + + enableRowSelection = true, + enableSorting = true, + enableCellExpanding = true, + enableColumnResizing = true, +}: DatabaseTableProps) { + const columns = useMemo(() => { + return createColumns(dbCols, { enableRowSelection }); + }, [dbCols, enableRowSelection]); + + const [rowSelection, setRowSelection] = useState({}); + const [sorting, setSorting] = useState([]); + const [expanded, setExpanded] = useState({}); + const [expandedCells, setCellExpanded] = useState({}); + + const table = useTable({ + _features: [CellExpanding], + columns, + data, + enableRowSelection, + enableSorting, + enableCellExpanding, + enableColumnResizing, + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), + getSortedRowModel: getSortedRowModel(), + defaultColumn: {}, + columnResizeMode: "onChange", + onSortingChange: setSorting, + onRowSelectionChange: setRowSelection, + onExpandedChange: setExpanded, + onExpandedCellsChange: setCellExpanded, + getCellCanExpand: (row, cell) => { + return !!references.find((ref) => ref.from === cell.column.id); + }, + paginateExpandedRows: false, + state: { + sorting, + rowSelection, + expanded, + expandedCells, + }, + }); + + function calculateColumnSizes() { + const headers = table.getFlatHeaders(); + const colSizes: { [key: string]: number } = {}; + for (let i = 0; i < headers.length; i++) { + const header = headers[i]!; + colSizes[`--header-${header.id}-size`] = header.getSize(); + colSizes[`--col-${header.column.id}-size`] = + header.column.getSize(); + } + return colSizes; + } + + const columnSizeVars = useMemo(() => { + return calculateColumnSizes(); + }, [table.getState().columnSizingInfo, table.getState().columnSizing]); + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : header.column.getCanSort() ? ( + + ) : ( +
+ {flexRender( + header.column.columnDef.header, + header.getContext(), + )} +
+ )} + {header.column.getCanResize() ? ( +
+
+
+ ) : null} + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + + {row.getVisibleCells().map((cell) => ( + +
+
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} +
+ {cell.getCanExpand() ? ( + + ) : null} +
+
+ ))} +
+ {row.getIsExpanded() && row.getIsSomeCellExpanded() ? ( + + + +
+ {(() => { + const cell = row.getExpandedCell(); + const column = cell?.column; + const columnId = column?.id; + const reference = references?.find( + (ref) => ref.from === columnId, + ); + if (reference) { + return ( + <> +

+ {reference.table} +

+ + + + + + ); + } + return null; + })()} +
+
+
+ ) : null} +
+ ))} +
+
+ ); +} + +const ch = createColumnHelper(); + +function createColumns( + columns: any[], + { enableRowSelection }: { enableRowSelection?: boolean } = {}, +) { + return [ + ...[ + enableRowSelection + ? ch.display({ + id: "select", + enableResizing: false, + header: ({ table }) => ( + { + if (value === "indeterminate") { + table.toggleAllRowsSelected(true); + return; + } + table.toggleAllRowsSelected(value); + }} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + { + if (value === "indeterminate") { + row.toggleSelected(true); + return; + } + row.toggleSelected(); + }} + /> + ), + }) + : null, + ].filter(Boolean), + ...columns.map((col) => + ch.accessor(col.name, { + header: (info) => ( + + {col.name}{" "} + + {col.type} + + + ), + cell: (info) => { + if (col.type === "blob") { + return ( + + BINARY + + ); + } + const value = info.getValue(); + if (value === null) { + return ( + + NULL + + ); + } + return info.getValue(); + }, + meta: { + type: col.type, + notNull: col.notnull, + default: col.dflt_value, + }, + }), + ), + ]; +} + +export function ReferenceTable({ + table, + values, + property, + className, +}: { table: string; values: string[]; property: string; className?: string }) { + const info = useAtomValue( + selectAtom( + dbInfoAtom, + useCallback( + (query) => + query.data.find(({ table: { name } }) => name === table), + [table], + ), + ), + ); + const { data } = useQuery( + tableReferenceRowsQueryOptions(table, { values, property }), + ); + return ( + + ); +} diff --git a/frontend/apps/studio/src/components/layout.tsx b/frontend/apps/studio/src/components/layout.tsx index 14f6d40ca4..b58357c7c9 100644 --- a/frontend/apps/studio/src/components/layout.tsx +++ b/frontend/apps/studio/src/components/layout.tsx @@ -16,7 +16,7 @@ const Root = ({ children }: RootProps) => { const Main = ({ children }: RootProps) => { return ( -
+
{children}
); @@ -35,11 +35,11 @@ const Header = () => { return ( } - addons={ - connectionStatus !== "connected" ? ( - - ) : null - } + // addons={ + // connectionStatus !== "connected" ? ( + // + // ) : null + // } links={ <> diff --git a/frontend/apps/studio/src/queries/global.ts b/frontend/apps/studio/src/queries/global.ts index 27336afba4..00c15ebab6 100644 --- a/frontend/apps/studio/src/queries/global.ts +++ b/frontend/apps/studio/src/queries/global.ts @@ -1,13 +1,9 @@ -import { getConfig, timing, toast } from "@rivet-gg/components"; -import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental"; -import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; +import { toast } from "@rivet-gg/components"; import { MutationCache, - MutationObserver, QueryCache, QueryClient, } from "@tanstack/react-query"; -import superjson from "superjson"; const queryCache = new QueryCache(); @@ -26,7 +22,7 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 1000, - gcTime: 1000 * 60 * 60 * 24, + gcTime: 1000 * 60 * 60 * 1, retry: 2, refetchOnWindowFocus: false, refetchOnReconnect: false, @@ -35,14 +31,3 @@ export const queryClient = new QueryClient({ queryCache, mutationCache, }); - -export const queryClientPersister = createSyncStoragePersister({ - storage: window.localStorage, - serialize: superjson.stringify, - deserialize: superjson.parse, -}); - -broadcastQueryClient({ - queryClient, - broadcastChannel: "rivet-gg-hub", -}); diff --git a/frontend/apps/studio/src/routeTree.gen.ts b/frontend/apps/studio/src/routeTree.gen.ts index 51c6349f4a..005913bb25 100644 --- a/frontend/apps/studio/src/routeTree.gen.ts +++ b/frontend/apps/studio/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as rootRoute } from './routes/__root' import { Route as LayoutImport } from './routes/_layout' import { Route as LayoutIndexImport } from './routes/_layout/index' +import { Route as LayoutDbImport } from './routes/_layout/db' // Create/Update Routes @@ -27,6 +28,12 @@ const LayoutIndexRoute = LayoutIndexImport.update({ getParentRoute: () => LayoutRoute, } as any) +const LayoutDbRoute = LayoutDbImport.update({ + id: '/db', + path: '/db', + getParentRoute: () => LayoutRoute, +} as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -38,6 +45,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutImport parentRoute: typeof rootRoute } + '/_layout/db': { + id: '/_layout/db' + path: '/db' + fullPath: '/db' + preLoaderRoute: typeof LayoutDbImport + parentRoute: typeof LayoutImport + } '/_layout/': { id: '/_layout/' path: '/' @@ -51,10 +65,12 @@ declare module '@tanstack/react-router' { // Create and export the route tree interface LayoutRouteChildren { + LayoutDbRoute: typeof LayoutDbRoute LayoutIndexRoute: typeof LayoutIndexRoute } const LayoutRouteChildren: LayoutRouteChildren = { + LayoutDbRoute: LayoutDbRoute, LayoutIndexRoute: LayoutIndexRoute, } @@ -63,25 +79,28 @@ const LayoutRouteWithChildren = export interface FileRoutesByFullPath { '': typeof LayoutRouteWithChildren + '/db': typeof LayoutDbRoute '/': typeof LayoutIndexRoute } export interface FileRoutesByTo { + '/db': typeof LayoutDbRoute '/': typeof LayoutIndexRoute } export interface FileRoutesById { __root__: typeof rootRoute '/_layout': typeof LayoutRouteWithChildren + '/_layout/db': typeof LayoutDbRoute '/_layout/': typeof LayoutIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '' | '/' + fullPaths: '' | '/db' | '/' fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/_layout' | '/_layout/' + to: '/db' | '/' + id: '__root__' | '/_layout' | '/_layout/db' | '/_layout/' fileRoutesById: FileRoutesById } @@ -109,9 +128,14 @@ export const routeTree = rootRoute "/_layout": { "filePath": "_layout.tsx", "children": [ + "/_layout/db", "/_layout/" ] }, + "/_layout/db": { + "filePath": "_layout/db.tsx", + "parent": "/_layout" + }, "/_layout/": { "filePath": "_layout/index.tsx", "parent": "/_layout" diff --git a/frontend/apps/studio/src/routes/_layout/db.tsx b/frontend/apps/studio/src/routes/_layout/db.tsx new file mode 100644 index 0000000000..30ee30e5e4 --- /dev/null +++ b/frontend/apps/studio/src/routes/_layout/db.tsx @@ -0,0 +1,105 @@ +import { DatabaseTable } from "@/components/database-table"; +import { + currentTableAtom, + currentTableColumnsAtom, + currentTableInfo, + currentTableRowsAtom, + dbInfoAtom, +} from "@/stores/db"; +import { Button, Checkbox, ScrollArea } from "@rivet-gg/components"; +import { ActorsLayout } from "@rivet-gg/components/actors"; +import { faTable, Icon } from "@rivet-gg/icons"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { createColumnHelper } from "@tanstack/react-table"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { useAtomValue, useSetAtom } from "jotai"; +import { useEffect, useMemo } from "react"; +import { z } from "zod"; + +export const Route = createFileRoute("/_layout/db")({ + validateSearch: zodValidator( + z.object({ + table: z.string().optional(), + }), + ), + component: RouteComponent, +}); + +function RouteComponent() { + return ( + } + right={ +
+ +
+ } + /> + ); +} + +function Content() { + const info = useAtomValue(currentTableInfo); + const remoteColumns = useAtomValue(currentTableColumnsAtom) ?? []; + + const { data: remoteData } = useAtomValue(currentTableRowsAtom); + + return ( + + + + ); +} + +function Sidebar() { + const { data } = useAtomValue(dbInfoAtom); + + const { table } = Route.useSearch(); + + const setCurrentTable = useSetAtom(currentTableAtom); + + useEffect(() => { + setCurrentTable(table); + }, [table, setCurrentTable]); + + return ( +
+
+ {/*
*/} +

+ Tables +

+
    + {data?.map(({ records, table }) => { + return ( +
  • + +
  • + ); + })} +
+
+
+ ); +} diff --git a/frontend/apps/studio/src/stores/db.ts b/frontend/apps/studio/src/stores/db.ts new file mode 100644 index 0000000000..cfcc8ee538 --- /dev/null +++ b/frontend/apps/studio/src/stores/db.ts @@ -0,0 +1,80 @@ +import { queryOptions } from "@tanstack/react-query"; +import { atom } from "jotai"; +import { atomWithQuery } from "jotai-tanstack-query"; +import { atomFamily, splitAtom } from "jotai/utils"; + +export const dbInfoAtom = atomWithQuery(() => ({ + queryKey: ["db", "info"], + queryFn: async () => { + const res = await fetch( + "http://localhost:3456/tables" + ); + if(!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }, +})); + +export const currentTableAtom = atom(null); +export const currentTableInfo = atom((get) => { + const dbInfo = get(dbInfoAtom); + const currentTable = get(currentTableAtom); + return dbInfo?.data?.find(({table}) => table.name === currentTable); +}) +export const currentTableColumnsAtom = atom((get) => { + const dbInfo = get(dbInfoAtom); + const currentTable = get(currentTableAtom); + return dbInfo?.data?.find(({table}) => table.name === currentTable)?.columns; +}) + +export const tablesAtom = atom((get) => get(dbInfoAtom).data) +export const tableInfo = splitAtom(tablesAtom, (table) => table.name); + +export const currentTableRowsAtom = atomWithQuery((get) => ({ + queryKey: ["db", get(currentTableAtom), "rows"], + queryFn: async ({queryKey: [,currentTable]}) => { + const res = await fetch( + "http://localhost:3456/query", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + // FIXME + sql: `SELECT * FROM ${currentTable} LIMIT 500`, + args: [], + // args: [get(currentTableAtom)], + }), + } + ); + if(!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }, +})); + +export const tableReferenceRowsQueryOptions = (table: string, opts: {values: any[], property: string}) => queryOptions({ + queryKey: ["db", table, "rows", opts] as const, + queryFn: async ({queryKey: [,table, ,opts]}) => { + const res = await fetch( + "http://localhost:3456/query", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sql: `SELECT * FROM ${table} WHERE (${opts.property}) IN (${opts.values.join(",")})`, + args: [], + }), + } + ); + if(!res.ok) { + throw new Error("Network response was not ok"); + } + return res.json(); + }, +}); \ No newline at end of file diff --git a/frontend/packages/components/src/ui/checkbox.tsx b/frontend/packages/components/src/ui/checkbox.tsx index cb3e0a554c..75837ff19b 100644 --- a/frontend/packages/components/src/ui/checkbox.tsx +++ b/frontend/packages/components/src/ui/checkbox.tsx @@ -1,26 +1,32 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as React from "react"; -import { faCheck } from "@rivet-gg/icons"; +import { faCheck, faHorizontalRule } from "@rivet-gg/icons"; import { Icon } from "@rivet-gg/icons"; import { cn } from "../lib/utils"; const Checkbox = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( +>(({ className, checked, ...props }, ref) => ( - + {checked === true ? ( + + ) : null} + {checked === "indeterminate" ? ( + + ) : null} )); diff --git a/frontend/packages/components/src/ui/table.tsx b/frontend/packages/components/src/ui/table.tsx index d27ad12808..e7855647e1 100644 --- a/frontend/packages/components/src/ui/table.tsx +++ b/frontend/packages/components/src/ui/table.tsx @@ -35,11 +35,7 @@ const TableBody = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )); TableBody.displayName = "TableBody"; diff --git a/yarn.lock b/yarn.lock index 57ac5b7a19..de449d11f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4208,6 +4208,8 @@ __metadata: "@rivet-gg/icons": "workspace:*" "@sentry/react": "npm:^8.26.0" "@sentry/vite-plugin": "npm:^2.22.2" + "@tanstack/react-query": "npm:^5.76.0" + "@tanstack/react-query-devtools": "npm:^5.76.0" "@tanstack/react-router": "npm:^1.114.25" "@tanstack/react-table": "npm:^8.20.6" "@tanstack/router-devtools": "npm:^1.114.25" @@ -4231,6 +4233,7 @@ __metadata: jotai: "npm:^2.12.2" jotai-devtools: "npm:^0.11.0" jotai-effect: "npm:^2.0.2" + jotai-tanstack-query: "npm:^0.9.0" postcss: "npm:^8.4.38" posthog-js: "npm:^1.144.2" react: "npm:^19.0.0" @@ -5592,6 +5595,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:5.76.0": + version: 5.76.0 + resolution: "@tanstack/query-core@npm:5.76.0" + checksum: 10c0/970cc783838bf7e30acbad476c72e4bb3b302c15525152cfcf5ffef8b185537e780b608e3f77793cb355b49cc98a9de597d978d400df44dbd550cba78742d67c + languageName: node + linkType: hard + "@tanstack/query-devtools@npm:5.72.1": version: 5.72.1 resolution: "@tanstack/query-devtools@npm:5.72.1" @@ -5599,6 +5609,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-devtools@npm:5.76.0": + version: 5.76.0 + resolution: "@tanstack/query-devtools@npm:5.76.0" + checksum: 10c0/2bee4a39c083a0260bd15e98f02d39f078d363430b74da313260ff37deb4b416d874e3a58f4691698f848076532eaf104ff2843a016aec6186dd1c39c5dd3d6f + languageName: node + linkType: hard + "@tanstack/query-persist-client-core@npm:5.72.1": version: 5.72.1 resolution: "@tanstack/query-persist-client-core@npm:5.72.1" @@ -5630,6 +5647,18 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-query-devtools@npm:^5.76.0": + version: 5.76.0 + resolution: "@tanstack/react-query-devtools@npm:5.76.0" + dependencies: + "@tanstack/query-devtools": "npm:5.76.0" + peerDependencies: + "@tanstack/react-query": ^5.76.0 + react: ^18 || ^19 + checksum: 10c0/2ddbc85aa0909fd6da6f0729fe24eb8c3e54d516ee5f8f2371bd4b1ba9b9c00315ba52d6a2fe993b5b57c68d972d2a667651def45d41acd0479a360e9ae0d098 + languageName: node + linkType: hard + "@tanstack/react-query-persist-client@npm:^5.56.2": version: 5.72.1 resolution: "@tanstack/react-query-persist-client@npm:5.72.1" @@ -5653,6 +5682,17 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-query@npm:^5.76.0": + version: 5.76.0 + resolution: "@tanstack/react-query@npm:5.76.0" + dependencies: + "@tanstack/query-core": "npm:5.76.0" + peerDependencies: + react: ^18 || ^19 + checksum: 10c0/d17b7840f00a6f6d6253f436f9d32d63d255e6945e81930a36b58deb39a5fb63f6911f4ccdf393570ccfc8675cf0439c1db1d8c87532fd09d93807aa9771f819 + languageName: node + linkType: hard + "@tanstack/react-router-devtools@npm:^1.115.2": version: 1.115.2 resolution: "@tanstack/react-router-devtools@npm:1.115.2" @@ -10887,6 +10927,16 @@ __metadata: languageName: node linkType: hard +"jotai-tanstack-query@npm:^0.9.0": + version: 0.9.0 + resolution: "jotai-tanstack-query@npm:0.9.0" + peerDependencies: + "@tanstack/query-core": "*" + jotai: ">=2.0.0" + checksum: 10c0/5bacdf976947b219f75c9de00eebbc8b8dd4ff949f386cf8ca16d0c581d743d1280979d5ff19e4f95ea63cef8a19c1c4276ae012c8b59febec6800107583d46a + languageName: node + linkType: hard + "jotai@npm:^2.12.2": version: 2.12.2 resolution: "jotai@npm:2.12.2"