diff --git a/.changeset/stale-pants-wash.md b/.changeset/stale-pants-wash.md new file mode 100644 index 00000000..9ee5c9f1 --- /dev/null +++ b/.changeset/stale-pants-wash.md @@ -0,0 +1,5 @@ +--- +"@premieroctet/next-admin": patch +--- + +feat: add grid layout diff --git a/apps/docs/pages/docs/api/model-configuration.mdx b/apps/docs/pages/docs/api/model-configuration.mdx index b1e32e86..67a04e4b 100644 --- a/apps/docs/pages/docs/api/model-configuration.mdx +++ b/apps/docs/pages/docs/api/model-configuration.mdx @@ -233,6 +233,51 @@ This property determines how your data is displayed in the [list View](/docs/glo ), }, + { + name: "layout", + type: "Object", + description: ( + <> + layout config for the list page. Useful to configure a grid view. + + ), + }, + { + name: "layout.default", + type: "String", + description: ( + <> + the default layout to use for the list view. It is optional. Either "table" or "grid". Defaults to "table". + + ), + }, + { + name: "layout.grid", + type: "Object", + description: ( + <> + an object to configure each items of the grid layout + + ), + }, + { + name: "layout.grid.thumbnail", + type: "Function", + description: ( + <> + A function that takes a NextAdmin context as parameter and returns a string representing the thumbnail URL. Can be a promise. + + ), + }, + { + name: "layout.grid.title", + type: "Function", + description: ( + <> + A function that takes a NextAdmin context as parameter and returns a string representing the title of the item. + + ), + }, ]} /> diff --git a/packages/database/package.json b/packages/database/package.json index 37f39072..534b1fcb 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -47,7 +47,6 @@ "devDependencies": { "@rslib/core": "^0.9.2", "@types/node": "^22.14.1", - "glob": "^11.0.0", "prisma": "catalog:prisma", "tsconfig": "workspace:*", "tsx": "^4.19.4", diff --git a/packages/database/rslib.config.ts b/packages/database/rslib.config.ts index 5a42322f..f3100d05 100644 --- a/packages/database/rslib.config.ts +++ b/packages/database/rslib.config.ts @@ -31,9 +31,7 @@ export default defineConfig({ }, source: { entry: { - index: glob.sync("**/*.{ts,tsx}", { - ignore: ["prisma/**", "rslib.config.ts"], - }), + index: ["**/*.{ts,tsx}", "!prisma/**", "!rslib.config.ts"], }, }, }); diff --git a/packages/examples-common/options.tsx b/packages/examples-common/options.tsx index 6a822c09..9b13fe76 100644 --- a/packages/examples-common/options.tsx +++ b/packages/examples-common/options.tsx @@ -38,6 +38,13 @@ export const createOptions = ( display: ["id", "name", "email", "posts", "role", "birthDate"], search: ["name", "email", "role"], copy: ["email"], + layout: { + default: "table", + grid: { + title: ({ row }) => `${row?.name} (${row?.email})`, + thumbnail: ({ row }) => "https://picsum.photos/200", + }, + }, filters: [ { name: "is Admin", diff --git a/packages/next-admin/package.json b/packages/next-admin/package.json index c977a49d..a6fd029b 100644 --- a/packages/next-admin/package.json +++ b/packages/next-admin/package.json @@ -173,6 +173,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-visually-hidden": "^1.1.1", "@rjsf/core": "6.0.0-beta.11", @@ -230,7 +231,6 @@ "concurrently": "^8.0.1", "eslint": "^7.32.0", "eslint-config-custom": "workspace:*", - "glob": "^11.0.0", "jsdom": "^26.1.0", "lodash.debounce": "^4.0.8", "next": "^15.3.1", diff --git a/packages/next-admin/rslib.config.ts b/packages/next-admin/rslib.config.ts index 1a0f4043..71de1ed0 100644 --- a/packages/next-admin/rslib.config.ts +++ b/packages/next-admin/rslib.config.ts @@ -1,7 +1,6 @@ import { RsbuildPluginAPI } from "@rsbuild/core"; import { pluginReact } from "@rsbuild/plugin-react"; import { defineConfig } from "@rslib/core"; -import { glob } from "glob"; import { rmSync } from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; @@ -40,12 +39,18 @@ export default defineConfig({ to: path.resolve(basePath, "dist/theme.css"), }, ], + cleanDistPath: { + keep: [/schema\.cjs/, /schema\.mjs/], + }, }, source: { entry: { - index: glob.sync("src/**/*.{ts,tsx}", { - ignore: ["**/tests/*", "**/*.test.{ts,tsx}", "**/generated/*"], - }), + index: [ + "src/**/*.{ts,tsx}", + "!**/tests/*", + "!**/*.test.{ts,tsx}", + "!**/generated/*", + ], }, /** * TODO: try to get rid of this at some point. diff --git a/packages/next-admin/src/components/DataGrid.tsx b/packages/next-admin/src/components/DataGrid.tsx new file mode 100644 index 00000000..8d8435c3 --- /dev/null +++ b/packages/next-admin/src/components/DataGrid.tsx @@ -0,0 +1,58 @@ +import { RowSelectionState } from "@tanstack/react-table"; +import { AdminComponentProps, GridData, ModelName } from "../types"; +import DataGridItem from "./DataGridItem"; +import { Dispatch, SetStateAction } from "react"; + +type Props = { + data: GridData[]; + resource: ModelName; + actions: AdminComponentProps["actions"]; + onDelete: (id: string) => void; + canDelete: boolean; + selectedItems: RowSelectionState; + setSelectedItems: Dispatch>; +}; + +const DataGrid = ({ + data, + resource, + actions, + onDelete, + canDelete, + selectedItems, + setSelectedItems, +}: Props) => { + const hasSelection = Object.keys(selectedItems).length > 0; + + return ( +
+ {data.map((item) => { + return ( + + setSelectedItems((old) => { + const newSelected = { ...old }; + if (newSelected[item.id]) { + delete newSelected[item.id]; + } else { + newSelected[item.id] = true; + } + return newSelected; + }) + } + /> + ); + })} +
+ ); +}; + +export default DataGrid; diff --git a/packages/next-admin/src/components/DataGridItem.tsx b/packages/next-admin/src/components/DataGridItem.tsx new file mode 100644 index 00000000..d1ab8e66 --- /dev/null +++ b/packages/next-admin/src/components/DataGridItem.tsx @@ -0,0 +1,94 @@ +import { DocumentIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; +import { useState } from "react"; +import { AdminComponentProps, GridData, ModelName } from "../types"; +import Link from "./common/Link"; +import { useConfig } from "../context/ConfigContext"; +import { slugify } from "../utils/tools"; +import ListActionsDropdown from "./ListActionsDropdown"; +import { twMerge } from "tailwind-merge"; +import Checkbox from "./radix/Checkbox"; + +type Props = { + item: GridData; + resource: ModelName; + actions: AdminComponentProps["actions"]; + onDelete: (id: string) => void; + canDelete: boolean; + selectionVisible: boolean; + selected: boolean; + onSelect: (id: string) => void; +}; + +const DataGridItem = ({ + item, + resource, + actions, + onDelete, + canDelete, + selectionVisible, + selected, + onSelect, +}: Props) => { + const [imageIsLoaded, setImageIsLoaded] = useState(false); + const { basePath } = useConfig(); + + return ( + +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {item.title} { + setImageIsLoaded(true); + }} + /> + {!imageIsLoaded && ( +
+ +
+ )} +
+
{ + e.preventDefault(); + onSelect(item.id as string); + }} + > + +
+
+ onDelete(item.id as string)} + resource={resource} + canDelete={canDelete} + id={item.id} + /> +
+
+
+ + {item.title} + +
+ + ); +}; + +export default DataGridItem; diff --git a/packages/next-admin/src/components/LayoutSwitch.tsx b/packages/next-admin/src/components/LayoutSwitch.tsx new file mode 100644 index 00000000..1cea0e77 --- /dev/null +++ b/packages/next-admin/src/components/LayoutSwitch.tsx @@ -0,0 +1,41 @@ +import { Squares2X2Icon, TableCellsIcon } from "@heroicons/react/24/outline"; +import { useRouterInternal } from "../hooks/useRouterInternal"; +import { LayoutType } from "../types"; +import { ToggleGroupItem, ToggleGroupRoot } from "./radix/ToggleGroup"; + +type Props = { + selectedLayout: LayoutType; +}; + +const LayoutSwitch = ({ selectedLayout }: Props) => { + const { router } = useRouterInternal(); + + return ( +
+ { + if (!val) { + return; + } + router.setQuery( + { + layout: val, + }, + true + ); + }} + > + + + + + + + +
+ ); +}; + +export default LayoutSwitch; diff --git a/packages/next-admin/src/components/List.tsx b/packages/next-admin/src/components/List.tsx index eb10f237..ea7a48ff 100644 --- a/packages/next-admin/src/components/List.tsx +++ b/packages/next-admin/src/components/List.tsx @@ -22,6 +22,8 @@ import { useRouterInternal } from "../hooks/useRouterInternal"; import { AdminComponentProps, FilterWrapper, + GridData, + LayoutType, ListData, ListDataItem, ModelIcon, @@ -51,10 +53,13 @@ import { SelectItem, SelectTrigger, } from "./radix/Select"; +import DataGrid from "./DataGrid"; +import LayoutSwitch from "./LayoutSwitch"; +import ListActionsDropdown from "./ListActionsDropdown"; export type ListProps = { resource: ModelName; - data: ListData; + data: ListData | GridData[]; total: number; resourcesIdProperty: Record; title: string; @@ -64,6 +69,7 @@ export type ListProps = { clientActionsComponents?: AdminComponentProps["dialogComponents"]; rawData: any[]; listFilterOptions?: Array>; + layout: LayoutType; }; const itemsPerPageSizes = [10, 25, 50, 100]; @@ -80,6 +86,7 @@ function List({ clientActionsComponents, rawData, listFilterOptions, + layout, }: ListProps) { const { router, query } = useRouterInternal(); const [isPending, startTransition] = useTransition(); @@ -109,6 +116,7 @@ function List({ sortDirection, rawData, }); + const supportsGridLayout = modelOptions?.list?.layout?.grid; const allListSizes = useMemo(() => { if (modelDefaultListSize) { @@ -187,53 +195,16 @@ function List({ cell: ({ row }) => { const idProperty = resourcesIdProperty[resource]; - if (!hasDeletePermission) return; - return ( - - - - - - - {actions?.map((action) => { - return ( - - ); - })} - { - evt.stopPropagation(); - deleteItems([row.original[idProperty].value as string]); - }} - > - - {t("list.row.actions.delete.label")} - - - - + + deleteItems([row.original[idProperty].value as string]) + } + resource={resource} + canDelete={hasDeletePermission} + id={row.original[idProperty].value as string} + /> ); }, }; @@ -251,9 +222,13 @@ function List({ const idField = resourcesIdProperty[resource]; - return selectedRows.map((row) => row[idField].value as string | number) as - | string[] - | number[]; + return selectedRows.map((row) => + layout === "grid" + ? row.id + : ((row as ListData[number])[idField].value as + | string + | number) + ) as string[] | number[]; }; const handleOrderChange = modelOptions?.list?.orderField @@ -261,7 +236,7 @@ function List({ startOrderTransition(async () => { const idField = resourcesIdProperty[resource]; const newData = reorderData( - optimisticData, + optimisticData as ListData, value.currentId, value.moveOverId, modelOptions?.list?.orderField!, @@ -299,19 +274,35 @@ function List({
- +
+ + {supportsGridLayout && } +
- + {layout === "table" && ( + } + columns={[checkboxColumn, ...columns, actionsColumn]} + resourcesIdProperty={resourcesIdProperty} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + icon={icon} + onOrderChange={handleOrderChange!} + orderField={modelOptions?.list?.orderField!} + /> + )} + {layout === "grid" && ( + deleteItems([id])} + selectedItems={rowSelection} + setSelectedItems={setRowSelection} + /> + )} {optimisticData.length ? (
diff --git a/packages/next-admin/src/components/ListActionsDropdown.tsx b/packages/next-admin/src/components/ListActionsDropdown.tsx new file mode 100644 index 00000000..9f49722e --- /dev/null +++ b/packages/next-admin/src/components/ListActionsDropdown.tsx @@ -0,0 +1,83 @@ +import { EllipsisVerticalIcon, TrashIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; +import { useI18n } from "../context/I18nContext"; +import { AdminComponentProps, ModelName } from "../types"; +import ActionDropdownItem from "./ActionDropdownItem"; +import Button from "./radix/Button"; +import { + Dropdown, + DropdownBody, + DropdownContent, + DropdownItem, + DropdownTrigger, +} from "./radix/Dropdown"; + +type Props = { + actions?: AdminComponentProps["actions"]; + id?: string | number; + onDelete: () => void; + canDelete?: boolean; + resource: ModelName; +}; + +const ListActionsDropdown = ({ + actions, + id, + onDelete, + canDelete, + resource, +}: Props) => { + const { t } = useI18n(); + + return ( + + + + + + + {actions?.map((action) => { + return ( + + ); + })} + {canDelete && ( + { + evt.stopPropagation(); + onDelete(); + }} + > + + {t("list.row.actions.delete.label")} + + )} + + + + ); +}; + +export default ListActionsDropdown; diff --git a/packages/next-admin/src/components/NextAdmin.tsx b/packages/next-admin/src/components/NextAdmin.tsx index 82e85add..68b5a0c7 100644 --- a/packages/next-admin/src/components/NextAdmin.tsx +++ b/packages/next-admin/src/components/NextAdmin.tsx @@ -38,6 +38,7 @@ export function NextAdmin({ rawData, relationshipsRawData, listFilterOptions, + layout, }: AdminComponentProps & CustomUIProps) { if (!isAppDir && !options) { throw new Error( @@ -77,6 +78,7 @@ export function NextAdmin({ clientActionsComponents={dialogComponents} rawData={rawData!} listFilterOptions={listFilterOptions} + layout={layout!} /> ); } diff --git a/packages/next-admin/src/components/common/Link.tsx b/packages/next-admin/src/components/common/Link.tsx index 0e91b6a3..3fa47eea 100644 --- a/packages/next-admin/src/components/common/Link.tsx +++ b/packages/next-admin/src/components/common/Link.tsx @@ -11,7 +11,6 @@ const Link = ({ children, onClick, ...props }: Props) => { return ( { - e.persist(); onClick?.(e); if ((e.target as HTMLAnchorElement).target === "_blank") { @@ -21,6 +20,7 @@ const Link = ({ children, onClick, ...props }: Props) => { if (e.defaultPrevented) return; + e.preventDefault(); router.push({ pathname: props.href, }); diff --git a/packages/next-admin/src/components/radix/ToggleGroup.tsx b/packages/next-admin/src/components/radix/ToggleGroup.tsx new file mode 100644 index 00000000..421aac0b --- /dev/null +++ b/packages/next-admin/src/components/radix/ToggleGroup.tsx @@ -0,0 +1,38 @@ +import * as ToggleGroup from "@radix-ui/react-toggle-group"; +import clsx from "clsx"; +import { ComponentProps, ComponentRef, forwardRef } from "react"; +import { twMerge } from "tailwind-merge"; + +export const ToggleGroupRoot = forwardRef< + ComponentRef, + ComponentProps +>(({ className, ...props }, ref) => ( + +)); + +export const ToggleGroupItem = forwardRef< + ComponentRef, + ToggleGroup.ToggleGroupItemProps +>(({ className, ...props }, ref) => ( + +)); diff --git a/packages/next-admin/src/tests/prismaUtils.test.ts b/packages/next-admin/src/tests/prismaUtils.test.ts index e833a6b2..0d142d08 100644 --- a/packages/next-admin/src/tests/prismaUtils.test.ts +++ b/packages/next-admin/src/tests/prismaUtils.test.ts @@ -2,7 +2,7 @@ import { Decimal } from "@prisma/client/runtime/library"; import cloneDeep from "lodash.clonedeep"; import { afterEach, describe, expect, it } from "vitest"; import { mockReset } from "vitest-mock-extended"; -import { getMappedDataList, optionsFromResource } from "../utils/prisma"; +import { getMappedData, optionsFromResource } from "../utils/prisma"; import { extractSerializable } from "../utils/tools"; import { options, prismaMock } from "./singleton"; @@ -40,13 +40,14 @@ describe("getMappedDataList", () => { prismaMock.post.count.mockResolvedValue(2); - const result = await getMappedDataList({ + const result = await getMappedData({ prisma: prismaMock, resource: "Post", options, searchParams: new URLSearchParams(), context: {}, appDir: false, + layout: "table", }); expect(result).toEqual({ diff --git a/packages/next-admin/src/types.ts b/packages/next-admin/src/types.ts index a6a5c033..08dc949f 100644 --- a/packages/next-admin/src/types.ts +++ b/packages/next-admin/src/types.ts @@ -455,6 +455,38 @@ export type VirtualField = { } ); +export type GridLayoutConfig = { + /** + * the item's thumbnail. If it's not a valid image, a fallback icon will be used. + */ + thumbnail: (context: NextAdminContext>) => string | Promise; + /** + * the item's title + */ + title?: (context: NextAdminContext>) => string; +}; + +export type LayoutType = "table" | "grid"; + +export type LayoutConfig = { + /** + * sets the default layout that is displayed when accessing the list page. + * + * @default table + */ + default?: LayoutType; + /** + * an object to configure the items in grid layout + */ + grid?: GridLayoutConfig; +}; + +export type GridData = { + id: string | number; + thumbnail: string; + title?: string; +}; + export type ListOptions = { /** * an url to export the list data as CSV. @@ -501,6 +533,10 @@ export type ListOptions = { * an optional number indicating the default amount of items in the list */ defaultListSize?: number; + /** + * an optional object to configure the list layouts + */ + layout?: LayoutConfig; }; export type RelationshipsRawData = Record; @@ -935,7 +971,7 @@ export type AdminComponentProps = { basePath: string; apiBasePath: string; schema: Schema; - data?: ListData; + data?: ListData | GridData[]; rawData?: any[]; relationshipsRawData?: RelationshipsRawData; listFilterOptions?: Array>; @@ -985,6 +1021,7 @@ export type AdminComponentProps = { string, React.ReactElement> > | null; + layout?: LayoutType; }; export type AppRouterComponentProps = AdminComponentProps; diff --git a/packages/next-admin/src/utils/prisma.ts b/packages/next-admin/src/utils/prisma.ts index 055b9f28..1cfd2022 100644 --- a/packages/next-admin/src/utils/prisma.ts +++ b/packages/next-admin/src/utils/prisma.ts @@ -7,6 +7,9 @@ import type { Field, Filter, FilterWrapper, + GridData, + LayoutConfig, + LayoutType, ListOptions, Model, ModelName, @@ -432,6 +435,7 @@ type GetMappedDataListParams = { searchParams: URLSearchParams; context: NextAdminContext; appDir?: boolean; + layout?: LayoutType; }; type OptionsFromResourceParams = { @@ -689,9 +693,38 @@ export const mapDataList = ({ return data; }; -export const getMappedDataList = async ({ +export const mapDataGrid = async ({ + context, + rawData, + model, + options, +}: { + context: NextAdminContext; + rawData: any[]; + model: ModelName; + options?: NextAdminOptions; +}): Promise => { + const listOptions = options?.model?.[model]?.list; + return Promise.all( + rawData.map(async (data) => { + return { + id: data[getModelIdProperty(model)], + thumbnail: + (await listOptions?.layout?.grid?.thumbnail({ + row: data, + ...context, + })) ?? "", + title: + listOptions?.layout?.grid?.title?.({ row: data, ...context }) ?? "", + }; + }) + ); +}; + +export const getMappedData = async ({ context, appDir = false, + layout, ...args }: GetMappedDataListParams) => { const { data: fetchData, total, error } = await fetchDataList(args); @@ -699,7 +732,15 @@ export const getMappedDataList = async ({ const rawData = cloneDeep(fetchData); return { - data: mapDataList({ context, appDir, fetchData, ...args }), + data: + layout === "grid" + ? await mapDataGrid({ + context, + rawData, + model: args.resource, + options: args.options, + }) + : mapDataList({ context, appDir, fetchData, ...args }), total, error, rawData: extractSerializable(rawData), diff --git a/packages/next-admin/src/utils/props.ts b/packages/next-admin/src/utils/props.ts index 77a007b4..6e122854 100644 --- a/packages/next-admin/src/utils/props.ts +++ b/packages/next-admin/src/utils/props.ts @@ -5,6 +5,8 @@ import { FilterWrapper, GetMainLayoutPropsParams, GetNextAdminPropsParams, + LayoutConfig, + LayoutType, MainLayoutProps, ModelAction, ModelIcon, @@ -15,7 +17,7 @@ import { import type { Prisma, PrismaClient } from "../types-prisma"; import { getSchema, initGlobals } from "./globals"; import { getClientActionsComponents, getCustomInputs } from "./options"; -import { getDataItem, getMappedDataList, mapModelFilters } from "./prisma"; +import { getDataItem, getMappedData, mapModelFilters } from "./prisma"; import { applyVisiblePropertiesInSchema, getEnableToExecuteActions, @@ -117,15 +119,33 @@ export async function getPropsFromParams({ switch (params.length) { case Page.LIST: { - const { data, total, error, rawData } = await getMappedDataList({ + const query = new URLSearchParams(searchParams as Record); + let queryLayout = query?.get("layout") as LayoutType; + + if (queryLayout && queryLayout !== "grid" && queryLayout !== "table") { + queryLayout = "table"; + } + + if ( + queryLayout === "grid" && + !options?.model?.[resource]?.list?.layout?.grid + ) { + queryLayout = "table"; + } + + const layout = + queryLayout ?? + options?.model?.[resource]?.list?.layout?.default ?? + "table"; + + const { data, total, error, rawData } = await getMappedData({ prisma, resource, options, - searchParams: new URLSearchParams( - searchParams as Record - ), + searchParams: query, context: { locale }, appDir: isAppDir, + layout, }); if (options?.model?.[resource]?.list?.filters) { @@ -135,8 +155,8 @@ export async function getPropsFromParams({ ); } - const dataIds = data.map( - (item) => item[getModelIdProperty(resource)].value + const dataIds = data.map((item) => + layout === "grid" ? item.id : item[getModelIdProperty(resource)].value ); const fullfilledAction = await getEnableToExecuteActions( @@ -167,6 +187,7 @@ export async function getPropsFromParams({ listFilterOptions: (clientOptions?.model?.[resource]?.list ?.filters as FilterWrapper[]) ?? null, + layout, }; } case Page.EDIT: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cfa0b09..43b7f73f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,7 +88,7 @@ importers: version: 3.6.2 turbo: specifier: latest - version: 2.5.4 + version: 2.5.5 apps/docs: dependencies: @@ -473,9 +473,6 @@ importers: '@types/node': specifier: ^22.14.1 version: 22.15.34 - glob: - specifier: ^11.0.0 - version: 11.0.3 prisma: specifier: catalog:prisma version: 6.12.0(typescript@5.8.3) @@ -502,7 +499,7 @@ importers: version: 8.10.0(eslint@7.32.0) eslint-config-turbo: specifier: latest - version: 2.5.4(eslint@7.32.0)(turbo@2.5.4) + version: 2.5.5(eslint@7.32.0)(turbo@2.5.5) eslint-plugin-react: specifier: 7.31.8 version: 7.31.8(eslint@7.32.0) @@ -659,6 +656,9 @@ importers: '@radix-ui/react-switch': specifier: ^1.0.3 version: 1.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle-group': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-tooltip': specifier: ^1.0.7 version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -844,9 +844,6 @@ importers: eslint-config-custom: specifier: workspace:* version: link:../eslint-config-custom - glob: - specifier: ^11.0.0 - version: 11.0.3 jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -2952,6 +2949,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle-group@1.1.10': + resolution: {integrity: sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: 19.0.0 + react-dom: 19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.9': + resolution: {integrity: sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: 19.0.0 + react-dom: 19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.7': resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==} peerDependencies: @@ -5920,8 +5943,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-turbo@2.5.4: - resolution: {integrity: sha512-OpjpDLXIaus0N/Y+pMj17K430xjpd6WTo0xPUESqYZ9BkMngv2n0ZdjktgJTbJVnDmK7gHrXgJAljtdIMcYBIg==} + eslint-config-turbo@2.5.5: + resolution: {integrity: sha512-23lKrCr66HQR62aa94n2dBGUUFz0GzM6N1KwmcREZHxomZ5Ik2rDm00wAz95lOEkhzSt6IaxW00Uz0az/Fcq5Q==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -6004,8 +6027,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-turbo@2.5.4: - resolution: {integrity: sha512-IZsW61DFj5mLMMaCJxhh1VE4HvNhfdnHnAaXajgne+LUzdyHk2NvYT0ECSa/1SssArcqgTvV74MrLL68hWLLFw==} + eslint-plugin-turbo@2.5.5: + resolution: {integrity: sha512-IlN65X6W7rgK88u5xl1xC+7FIGKA7eyaca0yxZQ9CBNV6keAaqtjZQLw8ZfXdv7T+MzTLYkYOeOHAv8yCRUx4Q==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -9960,41 +9983,41 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.5.4: - resolution: {integrity: sha512-ah6YnH2dErojhFooxEzmvsoZQTMImaruZhFPfMKPBq8sb+hALRdvBNLqfc8NWlZq576FkfRZ/MSi4SHvVFT9PQ==} + turbo-darwin-64@2.5.5: + resolution: {integrity: sha512-RYnTz49u4F5tDD2SUwwtlynABNBAfbyT2uU/brJcyh5k6lDLyNfYKdKmqd3K2ls4AaiALWrFKVSBsiVwhdFNzQ==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.5.4: - resolution: {integrity: sha512-2+Nx6LAyuXw2MdXb7pxqle3MYignLvS7OwtsP9SgtSBaMlnNlxl9BovzqdYAgkUW3AsYiQMJ/wBRb7d+xemM5A==} + turbo-darwin-arm64@2.5.5: + resolution: {integrity: sha512-Tk+ZeSNdBobZiMw9aFypQt0DlLsWSFWu1ymqsAdJLuPoAH05qCfYtRxE1pJuYHcJB5pqI+/HOxtJoQ40726Btw==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.5.4: - resolution: {integrity: sha512-5May2kjWbc8w4XxswGAl74GZ5eM4Gr6IiroqdLhXeXyfvWEdm2mFYCSWOzz0/z5cAgqyGidF1jt1qzUR8hTmOA==} + turbo-linux-64@2.5.5: + resolution: {integrity: sha512-2/XvMGykD7VgsvWesZZYIIVXMlgBcQy+ZAryjugoTcvJv8TZzSU/B1nShcA7IAjZ0q7OsZ45uP2cOb8EgKT30w==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.5.4: - resolution: {integrity: sha512-/2yqFaS3TbfxV3P5yG2JUI79P7OUQKOUvAnx4MV9Bdz6jqHsHwc9WZPpO4QseQm+NvmgY6ICORnoVPODxGUiJg==} + turbo-linux-arm64@2.5.5: + resolution: {integrity: sha512-DW+8CjCjybu0d7TFm9dovTTVg1VRnlkZ1rceO4zqsaLrit3DgHnN4to4uwyuf9s2V/BwS3IYcRy+HG9BL596Iw==} cpu: [arm64] os: [linux] turbo-stream@2.4.1: resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} - turbo-windows-64@2.5.4: - resolution: {integrity: sha512-EQUO4SmaCDhO6zYohxIjJpOKRN3wlfU7jMAj3CgcyTPvQR/UFLEKAYHqJOnJtymbQmiiM/ihX6c6W6Uq0yC7mA==} + turbo-windows-64@2.5.5: + resolution: {integrity: sha512-q5p1BOy8ChtSZfULuF1BhFMYIx6bevXu4fJ+TE/hyNfyHJIfjl90Z6jWdqAlyaFLmn99X/uw+7d6T/Y/dr5JwQ==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.5.4: - resolution: {integrity: sha512-oQ8RrK1VS8lrxkLriotFq+PiF7iiGgkZtfLKF4DDKsmdbPo0O9R2mQxm7jHLuXraRCuIQDWMIw6dpcr7Iykf4A==} + turbo-windows-arm64@2.5.5: + resolution: {integrity: sha512-AXbF1KmpHUq3PKQwddMGoKMYhHsy5t1YBQO8HZ04HLMR0rWv9adYlQ8kaeQJTko1Ay1anOBFTqaxfVOOsu7+1Q==} cpu: [arm64] os: [win32] - turbo@2.5.4: - resolution: {integrity: sha512-kc8ZibdRcuWUG1pbYSBFWqmIjynlD8Lp7IB6U3vIzvOv9VG+6Sp8bzyeBWE3Oi8XV5KsQrznyRTBPvrf99E4mA==} + turbo@2.5.5: + resolution: {integrity: sha512-eZ7wI6KjtT1eBqCnh2JPXWNUAxtoxxfi6VdBdZFvil0ychCOTxbm7YLRBi1JSt7U3c+u3CLxpoPxLdvr/Npr3A==} hasBin: true twoslash-protocol@0.2.12: @@ -12893,6 +12916,32 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-toggle-group@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-toggle@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -16429,11 +16478,11 @@ snapshots: dependencies: eslint: 7.32.0 - eslint-config-turbo@2.5.4(eslint@7.32.0)(turbo@2.5.4): + eslint-config-turbo@2.5.5(eslint@7.32.0)(turbo@2.5.5): dependencies: eslint: 7.32.0 - eslint-plugin-turbo: 2.5.4(eslint@7.32.0)(turbo@2.5.4) - turbo: 2.5.4 + eslint-plugin-turbo: 2.5.5(eslint@7.32.0)(turbo@2.5.5) + turbo: 2.5.5 eslint-import-resolver-node@0.3.9: dependencies: @@ -16657,11 +16706,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.5.4(eslint@7.32.0)(turbo@2.5.4): + eslint-plugin-turbo@2.5.5(eslint@7.32.0)(turbo@2.5.5): dependencies: dotenv: 16.0.3 eslint: 7.32.0 - turbo: 2.5.4 + turbo: 2.5.5 eslint-scope@5.1.1: dependencies: @@ -21766,34 +21815,34 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo-darwin-64@2.5.4: + turbo-darwin-64@2.5.5: optional: true - turbo-darwin-arm64@2.5.4: + turbo-darwin-arm64@2.5.5: optional: true - turbo-linux-64@2.5.4: + turbo-linux-64@2.5.5: optional: true - turbo-linux-arm64@2.5.4: + turbo-linux-arm64@2.5.5: optional: true turbo-stream@2.4.1: {} - turbo-windows-64@2.5.4: + turbo-windows-64@2.5.5: optional: true - turbo-windows-arm64@2.5.4: + turbo-windows-arm64@2.5.5: optional: true - turbo@2.5.4: + turbo@2.5.5: optionalDependencies: - turbo-darwin-64: 2.5.4 - turbo-darwin-arm64: 2.5.4 - turbo-linux-64: 2.5.4 - turbo-linux-arm64: 2.5.4 - turbo-windows-64: 2.5.4 - turbo-windows-arm64: 2.5.4 + turbo-darwin-64: 2.5.5 + turbo-darwin-arm64: 2.5.5 + turbo-linux-64: 2.5.5 + turbo-linux-arm64: 2.5.5 + turbo-windows-64: 2.5.5 + turbo-windows-arm64: 2.5.5 twoslash-protocol@0.2.12: {}