Просмотр исходного кода

feat(dashboard): Add disabled option to exclude columns in ListPage (#4170)

Bibiana Sebestianova 1 день назад
Родитель
Сommit
3ad9b55543

+ 53 - 44
docs/docs/guides/extending-the-dashboard/creating-pages/list-pages.mdx

@@ -14,12 +14,12 @@ For example, the `articles` query of our `CmsPlugin` looks like this:
 
 ```graphql
 type ArticleList implements PaginatedList {
-  items: [Article!]!
-  totalItems: Int!
+    items: [Article!]!
+    totalItems: Int!
 }
 
 extend type Query {
-  articles(options: ArticleListOptions): ArticleList!
+    articles(options: ArticleListOptions): ArticleList!
 }
 ```
 
@@ -135,6 +135,7 @@ files to be picked up by Vite:
 q # to stop the running dev server
 npx vite # to restart
 ```
+
 :::
 
 ## The ListPage Component
@@ -158,46 +159,54 @@ more control over how the column data is rendered.
 
 ```tsx
 <ListPage
-  pageId="collection-list"
-  listQuery={collectionListDocument}
-  customizeColumns={{
-      // The key "name" matches one of the top-level fields of the
-      // list query type (Collection, in this example)
-      name: {
-          meta: {
-              // The Dashboard optimizes the list query `collectionListDocument` to
-              // only select field that are actually visible in the ListPage table.
-              // However, sometimes you want to render data from other fields, i.e.
-              // this column has a data dependency on the "children" and "breadcrumbs"
-              // fields in order to correctly render the "name" field.
-              // In this case, we can declare those data dependencies which means whenever
-              // the "name" column is visible, it will ensure the "children" and "breadcrumbs"
-              // fields are also selected in the query.
-              dependencies: ['children', 'breadcrumbs'],
-          },
-          header: 'Collection Name',
-          cell: ({ row }) => {
-              const isExpanded = row.getIsExpanded();
-              const hasChildren = !!row.original.children?.length;
-              return (
-                  <div
-                      style={{ marginLeft: (row.original.breadcrumbs?.length - 2) * 20 + 'px' }}
-                      className="flex gap-2 items-center"
-                  >
-                      <Button
-                          size="icon"
-                          variant="secondary"
-                          onClick={row.getToggleExpandedHandler()}
-                          disabled={!hasChildren}
-                          className={!hasChildren ? 'opacity-20' : ''}
-                      >
-                          {isExpanded ? <FolderOpen /> : <Folder />}
-                      </Button>
-                      <DetailPageButton id={row.original.id} label={row.original.name} />
-                  </div>
-              );
-          },
-      },
-  }}
+    pageId="collection-list"
+    listQuery={collectionListDocument}
+    customizeColumns={{
+        // Use `meta.disabled` to completely exclude a column from the table,
+        // including the column visibility toggle. This is useful when you need
+        // to fetch certain fields for use in custom cell renderers, but don't
+        // want those fields to appear as their own columns.
+        productVariantCount: {
+            meta: { disabled: true },
+        },
+        // The key "name" matches one of the top-level fields of the
+        // list query type (Collection, in this example)
+        name: {
+            meta: {
+                // The Dashboard optimizes the list query `collectionListDocument` to
+                // only select field that are actually visible in the ListPage table.
+                // However, sometimes you want to render data from other fields, i.e.
+                // this column has a data dependency on the "children" and "breadcrumbs"
+                // fields in order to correctly render the "name" field.
+                // In this case, we can declare those data dependencies which means whenever
+                // the "name" column is visible, it will ensure the "children" and "breadcrumbs"
+                // fields are also selected in the query.
+                dependencies: ['children', 'breadcrumbs'],
+            },
+
+            header: 'Collection Name',
+            cell: ({ row }) => {
+                const isExpanded = row.getIsExpanded();
+                const hasChildren = !!row.original.children?.length;
+                return (
+                    <div
+                        style={{ marginLeft: (row.original.breadcrumbs?.length - 2) * 20 + 'px' }}
+                        className="flex gap-2 items-center"
+                    >
+                        <Button
+                            size="icon"
+                            variant="secondary"
+                            onClick={row.getToggleExpandedHandler()}
+                            disabled={!hasChildren}
+                            className={!hasChildren ? 'opacity-20' : ''}
+                        >
+                            {isExpanded ? <FolderOpen /> : <Folder />}
+                        </Button>
+                        <DetailPageButton id={row.original.id} label={row.original.name} />
+                    </div>
+                );
+            },
+        },
+    }}
 />
 ```

+ 9 - 2
packages/dashboard/src/lib/components/data-table/use-generated-columns.tsx

@@ -13,6 +13,7 @@ import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
 import { api } from '@/vdb/graphql/api.js';
 import { usePageBlock } from '@/vdb/hooks/use-page-block.js';
 import { usePage } from '@/vdb/hooks/use-page.js';
+import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
 import { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
@@ -34,7 +35,6 @@ import {
     PaginatedListItemFields,
     RowAction,
 } from '../shared/paginated-list-data-table.js';
-import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
 import {
     AlertDialog,
     AlertDialogAction,
@@ -120,6 +120,13 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
 
         const queryBasedColumns = columnConfigs.map(({ fieldInfo, isCustomField }) => {
             const customConfig = customizeColumns?.[fieldInfo.name as unknown as AllItemFieldKeys<T>] ?? {};
+
+            const disabled = customConfig.meta?.disabled ?? false;
+
+            if (disabled) {
+                return null;
+            }
+
             const { header, meta, cell: customCell, ...customConfigRest } = customConfig;
             const enableColumnFilter = fieldInfo.isScalar && !facetedFilters?.[fieldInfo.name];
             const displayComponentId =
@@ -161,7 +168,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
             });
         });
 
-        let finalColumns = [...queryBasedColumns];
+        let finalColumns = queryBasedColumns.filter(column => column !== null);
 
         for (const [id, column] of Object.entries(additionalColumns ?? {})) {
             if (!id) {

+ 6 - 2
packages/dashboard/src/lib/components/shared/paginated-list-data-table.tsx

@@ -14,9 +14,9 @@ import { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { ColumnFiltersState, ColumnSort, SortingState, Table } from '@tanstack/react-table';
 import { ColumnDef, Row, TableOptions, VisibilityState } from '@tanstack/table-core';
 import React from 'react';
-import { PaginatedListContext } from './paginated-list-context.js';
 import { getColumnVisibility, getStandardizedDefaultColumnOrder } from '../data-table/data-table-utils.js';
 import { useGeneratedColumns } from '../data-table/use-generated-columns.js';
+import { PaginatedListContext } from './paginated-list-context.js';
 
 // Type that identifies a paginated list structure (has items array and totalItems)
 type IsPaginatedList<T> = T extends { items: any[]; totalItems: number } ? true : false;
@@ -94,6 +94,11 @@ export type ColumnDefWithMetaDependencies<T extends TypedDocumentNode<any, any>>
     ColumnDef<T, any>
 > & {
     meta?: {
+        /**
+         * @description
+         * If true, the column will not be displayed in the table.
+         */
+        disabled?: boolean;
         /**
          * @description
          * Columns that rely on _other_ columns in order to correctly render,
@@ -153,7 +158,6 @@ export type AdditionalColumns<T extends TypedDocumentNode<any, any>> = {
     [key: string]: ColumnDefWithMetaDependencies<PaginatedListItemFields<T>>;
 };
 
-
 export interface RowAction<T> {
     label: React.ReactNode;
     onClick?: (row: Row<T>) => void;

+ 41 - 2
packages/dashboard/src/lib/framework/page/list-page.stories.tsx

@@ -5,6 +5,7 @@ import { ListPage, ListPageProps } from '@/vdb/framework/page/list-page.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
 import type { Meta, StoryObj } from '@storybook/react';
 import { PlusIcon } from 'lucide-react';
+import { expect } from 'storybook/test';
 
 import { DemoRouterProvider } from '../../../../.storybook/providers.js';
 
@@ -72,8 +73,9 @@ const meta = {
             description: 'GraphQL mutation document for deleting items',
         },
         customizeColumns: {
-            control: false,
-            description: 'Customize column rendering and behavior',
+            control: 'object',
+            description:
+                'Customize column rendering and behavior. Use `meta.disabled: true` to exclude columns from the table.',
         },
         defaultVisibility: {
             control: 'object',
@@ -99,6 +101,11 @@ export const BasicList: Story = {
             code: true,
             enabled: true,
         },
+        customizeColumns: {
+            updatedAt: {
+                meta: { disabled: true },
+            },
+        },
     },
 };
 
@@ -166,6 +173,38 @@ export const WithSearch: Story = {
     },
 };
 
+/**
+ * ListPage with disabled columns.
+ * Shows how to use `meta.disabled` to completely exclude columns from the table
+ * and the column visibility toggle. The disabled columns' data can still be
+ * accessed in other column renderers.
+ */
+export const WithDisabledColumns: Story = {
+    args: {
+        pageId: 'country-list-disabled',
+        listQuery: countriesListQuery,
+        title: 'Countries',
+        defaultVisibility: {
+            name: true,
+            code: true,
+            enabled: true,
+        },
+        customizeColumns: {
+            name: {
+                cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
+            },
+            // The createdAt and updatedAt columns are disabled and won't appear
+            // in the table or in the column visibility toggle
+            updatedAt: {
+                meta: { disabled: true },
+            },
+            createdAt: {
+                meta: { disabled: true },
+            },
+        },
+    },
+};
+
 /**
  * Complete example with action bar and all features.
  * Shows the full ListPage configuration including custom action buttons.

+ 8 - 0
packages/dashboard/src/lib/framework/page/list-page.tsx

@@ -154,6 +154,7 @@ export interface ListPageProps<
      *      };
      *    }}
      *  />
+     * ```
      * @param searchTerm
      */
     onSearchTermChange?: (searchTerm: string) => NonNullable<V['options']>['filter'];
@@ -208,6 +209,13 @@ export interface ListPageProps<
      *           );
      *       },
      *     },
+     *     // Use `meta.disabled` to completely exclude a column from the table,
+     *     // including the column visibility toggle. This is useful when you need
+     *     // to fetch certain fields for use in custom cell renderers, but don't
+     *     // want those fields to appear as their own columns.
+     *     productVariantCount: {
+     *       meta: { disabled: true },
+     *     },
      * ```
      */
     customizeColumns?: CustomizeColumnConfig<T>;