Browse Source

feat: show function output column in data view (#930)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 2 weeks ago
parent
commit
b812676045

+ 6 - 3
client/src/components/grid/Grid.tsx

@@ -32,6 +32,7 @@ import type { AttuGridType } from './Types';
  * @param handlesort how to sort table, if it's undefined, then you can not sort table
  * @param order 'desc' | 'asc'. sort direction
  * @param order order by which table field
+ * @param addSpacerColumn control spacer column display. default is false
  * @returns
  */
 const AttuGrid: FC<AttuGridType> = props => {
@@ -44,8 +45,8 @@ const AttuGrid: FC<AttuGridType> = props => {
   const {
     rowCount = 20,
     rowsPerPage = 10,
-    tableHeaderHeight = 43.5,
-    rowHeight = 41,
+    tableHeaderHeight = 44,
+    rowHeight = 42,
     pagerHeight = 52,
     primaryKey = 'id',
     showToolbar = false,
@@ -79,6 +80,7 @@ const AttuGrid: FC<AttuGridType> = props => {
     hideOnDisable,
     rowDecorator = () => ({}),
     sx = {},
+    addSpacerColumn = false,
   } = props;
 
   const _isSelected = (row: { [x: string]: any }) => {
@@ -131,7 +133,7 @@ const AttuGrid: FC<AttuGridType> = props => {
         containerHeight -
         tableHeaderHeight -
         (showPagination ? pagerHeight : 0) -
-        (hasToolbar ? 47 : 0);
+        (hasToolbar ? 42 : 0);
 
       const rowCount = Math.floor(totalHeight / rowHeight);
 
@@ -226,6 +228,7 @@ const AttuGrid: FC<AttuGridType> = props => {
           orderBy={orderBy}
           rowHeight={rowHeight}
           rowDecorator={rowDecorator}
+          addSpacerColumn={addSpacerColumn}
         ></Table>
         {rowCount && showPagination ? (
           <TablePagination

+ 64 - 6
client/src/components/grid/Table.tsx

@@ -16,6 +16,26 @@ import CopyButton from '../advancedSearch/CopyButton';
 import type { Theme } from '@mui/material/styles';
 import type { TableType } from './Types';
 
+/**
+ * Enhanced Table Component
+ *
+ * Usage example with spacer column:
+ * <AttuGrid
+ *   colDefinitions={[
+ *     { id: 'name', label: 'Name', disablePadding: false },
+ *     { id: 'age', label: 'Age', disablePadding: false }
+ *   ]}
+ *   addSpacerColumn={true} // This will add a spacer column that absorbs remaining space
+ *   // ... other props
+ * />
+ *
+ * The spacer column will:
+ * - Have width: 'auto' and minWidth: 'auto'
+ * - Display no content
+ * - Naturally absorb remaining horizontal space without forcing table width
+ * - Prevent unnecessary horizontal scrollbars
+ */
+
 const EnhancedTable: FC<TableType> = props => {
   let {
     selected,
@@ -44,10 +64,30 @@ const EnhancedTable: FC<TableType> = props => {
     orderBy,
     rowDecorator = () => ({}),
     rowHeight,
+    // whether to add a spacer column to control space distribution
+    addSpacerColumn = false,
   } = props;
 
   const hasData = rows && rows.length > 0;
 
+  // Add spacer column definition if needed
+  const finalColDefinitions = addSpacerColumn
+    ? [
+        ...colDefinitions,
+        {
+          id: '__spacer__',
+          align: 'left' as const,
+          disablePadding: false,
+          label: '',
+          getStyle: () => ({
+            width: '66.7%',
+            minWidth: 'auto',
+          }),
+          formatter: () => null,
+        },
+      ]
+    : colDefinitions;
+
   return (
     <TableContainer
       sx={theme => ({
@@ -70,7 +110,7 @@ const EnhancedTable: FC<TableType> = props => {
         >
           {!headEditable ? (
             <EnhancedTableHead
-              colDefinitions={colDefinitions}
+              colDefinitions={finalColDefinitions}
               numSelected={selected.length}
               order={order}
               orderBy={orderBy}
@@ -144,12 +184,30 @@ const EnhancedTable: FC<TableType> = props => {
                         </TableCell>
                       )}
 
-                      {colDefinitions.map((colDef, i) => {
+                      {finalColDefinitions.map((colDef, i) => {
                         const { actionBarConfigs = [], needCopy = false } =
                           colDef;
                         const cellStyleFromDef = colDef.getStyle
                           ? colDef.getStyle(row[colDef.id])
                           : {};
+
+                        // Skip rendering for spacer column
+                        if (colDef.id === '__spacer__') {
+                          return (
+                            <TableCell
+                              key={colDef.id}
+                              sx={[
+                                (theme: Theme) => ({
+                                  borderBottom: `1px solid ${theme.palette.divider}`,
+                                }),
+                                cellStyleFromDef,
+                              ]}
+                            >
+                              {/* Empty content for spacer column */}
+                            </TableCell>
+                          );
+                        }
+
                         return colDef.showActionCell ? (
                           <TableCell
                             sx={
@@ -323,8 +381,8 @@ const EnhancedTable: FC<TableType> = props => {
                     })}
                     colSpan={
                       openCheckBox
-                        ? colDefinitions.length + 1
-                        : colDefinitions.length
+                        ? finalColDefinitions.length + 1
+                        : finalColDefinitions.length
                     }
                   >
                     {noData}
@@ -344,8 +402,8 @@ const EnhancedTable: FC<TableType> = props => {
                   })}
                   colSpan={
                     openCheckBox
-                      ? colDefinitions.length + 1
-                      : colDefinitions.length
+                      ? finalColDefinitions.length + 1
+                      : finalColDefinitions.length
                   }
                 >
                   <LoadingTable />

+ 4 - 0
client/src/components/grid/Types.ts

@@ -99,6 +99,8 @@ export type TableType = {
   order?: SortDirection;
   orderBy?: string;
   ref?: Ref<HTMLDivElement>;
+  // whether to add a spacer column to control space distribution
+  addSpacerColumn?: boolean;
 };
 
 export type ColDefinitionsType = {
@@ -159,6 +161,8 @@ export type AttuGridType = ToolBarType & {
   pagerHeight?: number;
   rowDecorator?: (row: any) => SxProps<Theme> | React.CSSProperties;
   sx?: SxProps<Theme>;
+  // whether to add a spacer column to control space distribution
+  addSpacerColumn?: boolean;
 };
 
 export type ActionBarType = {

+ 29 - 18
client/src/hooks/Query.ts

@@ -59,23 +59,24 @@ export const useQuery = (params: {
   };
 
   // query function
-  const query = async (
-    page: number = currentPage,
-    consistency_level = queryState.consistencyLevel,
-    _outputFields = queryState.outputFields,
-    expr = queryState.expr
-  ) => {
+  const query = async (page: number = currentPage, queryState: QueryState) => {
     if (!collection || !collection.loaded) return;
-    const _expr = getPageExpr(page, expr);
+    const _expr = getPageExpr(page, queryState.expr);
 
     onQueryStart(_expr);
 
+    // each queryState.outputFields can not be a function output
+    const outputFields = queryState.outputFields.filter(
+      f =>
+        !collection.schema.fields.find(ff => ff.name === f)?.is_function_output
+    );
+
     try {
       const queryParams = {
         expr: _expr,
-        output_fields: _outputFields,
+        output_fields: outputFields,
         limit: pageSize || 10,
-        consistency_level,
+        consistency_level: queryState.consistencyLevel,
         // travel_timestamp: timeTravelInfo.timestamp,
       };
 
@@ -111,7 +112,7 @@ export const useQuery = (params: {
 
       onQueryEnd?.(res);
     } catch (e: any) {
-      reset();
+      reset(true);
     } finally {
       onQueryFinally?.();
     }
@@ -142,9 +143,11 @@ export const useQuery = (params: {
   };
 
   // reset
-  const reset = () => {
+  const reset = (clearData = false) => {
     setCurrentPage(0);
-    setQueryResult({ data: [], latency: 0 });
+    if (clearData) {
+      setQueryResult({ data: [], latency: 0 });
+    }
 
     pageCache.current.clear();
   };
@@ -166,12 +169,7 @@ export const useQuery = (params: {
     count(queryState.consistencyLevel, queryState.expr);
 
     // Then fetch actual data
-    query(
-      currentPage,
-      queryState.consistencyLevel,
-      queryState.outputFields,
-      queryState.expr
-    );
+    query(currentPage, queryState);
   }, [
     pageSize,
     queryState.outputFields,
@@ -181,6 +179,19 @@ export const useQuery = (params: {
     queryState.tick,
   ]);
 
+  // Reset state when collection changes
+  useEffect(() => {
+    // Immediately reset when collection changes to avoid showing stale data
+    setQueryResult({ data: [], latency: 0 });
+    setCurrentPage(0);
+    pageCache.current.clear();
+
+    // Set total to collection row count as fallback
+    if (collection) {
+      setTotal(collection.rowCount || 0);
+    }
+  }, [collection.collection_name]);
+
   return {
     // total query count
     total,

+ 1 - 3
client/src/pages/databases/Databases.tsx

@@ -227,9 +227,7 @@ const Databases = () => {
       // if query state not found, and the schema is ready, create new query state
       if (!query && c.schema) {
         setQueryState(prevState => {
-          const fields = [...c.schema.fields].filter(
-            f => !f.is_function_output
-          );
+          const fields = c.schema.fields;
           return [
             ...prevState,
             {

+ 2 - 2
client/src/pages/databases/collections/Collections.tsx

@@ -468,8 +468,8 @@ const Collections = () => {
           page={currentPage}
           onPageChange={handlePageChange}
           rowsPerPage={pageSize}
-          tableHeaderHeight={46}
-          rowHeight={41}
+          tableHeaderHeight={44}
+          rowHeight={42}
           setRowsPerPage={handlePageSize}
           isLoading={loading}
           handleSort={handleGridSort}

+ 52 - 17
client/src/pages/databases/collections/data/CollectionData.tsx

@@ -33,11 +33,13 @@ const CollectionData = (props: CollectionDataProps) => {
   }
 
   // UI state
-  const [tableLoading, setTableLoading] = useState<boolean>();
+  const [tableLoading, setTableLoading] = useState<boolean>(false);
   const [selectedData, setSelectedData] = useState<any[]>([]);
   const exprInputRef = useRef<string>(queryState.expr);
   const [, forceUpdate] = useState({});
   const loadingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const [isCollectionSwitching, setIsCollectionSwitching] =
+    useState<boolean>(false);
 
   // UI functions
   const { fetchCollection } = useContext(dataContext);
@@ -68,10 +70,10 @@ const CollectionData = (props: CollectionDataProps) => {
         clearTimeout(loadingTimeoutRef.current);
       }
 
-      // Set a timeout to show loading after 100ms
+      // Set a timeout to show loading after 200ms to avoid flickering for fast queries
       loadingTimeoutRef.current = setTimeout(() => {
         setTableLoading(true);
-      }, 100);
+      }, 200);
 
       if (expr === '') {
         handleFilterReset();
@@ -85,6 +87,7 @@ const CollectionData = (props: CollectionDataProps) => {
         loadingTimeoutRef.current = null;
       }
       setTableLoading(false);
+      setIsCollectionSwitching(false);
     },
     queryState: queryState,
     setQueryState: setQueryState,
@@ -143,15 +146,12 @@ const CollectionData = (props: CollectionDataProps) => {
       tick: queryState.tick + 1,
     });
     forceUpdate({});
-
-    // ensure not loading
-    setTableLoading(false);
   }, [collection.schema.fields, queryState, setQueryState]);
 
   const handlePageChange = useCallback(
     async (e: any, page: number) => {
       // do the query
-      await query(page, queryState.consistencyLevel);
+      await query(page, queryState);
       // update page number
       setCurrentPage(page);
     },
@@ -170,7 +170,7 @@ const CollectionData = (props: CollectionDataProps) => {
     // update count
     count(ConsistencyLevelEnum.Strong);
     // update query
-    query(0, ConsistencyLevelEnum.Strong);
+    query(0, { ...queryState, consistencyLevel: ConsistencyLevelEnum.Strong });
   }, [count, query, reset]);
 
   const onInsert = useCallback(
@@ -227,6 +227,9 @@ const CollectionData = (props: CollectionDataProps) => {
     exprInputRef.current = queryState.expr;
     forceUpdate({});
 
+    // Set collection switching state when collection changes
+    setIsCollectionSwitching(true);
+
     // Clean up timeout on unmount or when collection changes
     return () => {
       if (loadingTimeoutRef.current) {
@@ -261,20 +264,50 @@ const CollectionData = (props: CollectionDataProps) => {
             handleFilterSubmit={handleFilterSubmit}
             handleFilterReset={handleFilterReset}
             setCurrentPage={setCurrentPage}
+            forceDisabled={isCollectionSwitching}
           />
           <AttuGrid
             toolbarConfigs={[]}
+            addSpacerColumn={true}
+            rowHeight={43}
+            tableHeaderHeight={44}
             colDefinitions={queryState.outputFields.map(i => {
+              const field = collection.schema.fields.find(f => f.name === i);
+
+              // if field is function output, return a special col definition
+              if (field?.is_function_output) {
+                const inputFields = collection.schema.functions.find(fn =>
+                  fn.output_field_names?.includes(i)
+                )?.input_field_names;
+
+                return {
+                  id: collection.schema.primaryField.name,
+                  align: 'left',
+                  disablePadding: false,
+                  needCopy: false,
+                  label: i,
+                  formatter: () => (
+                    <Typography
+                      variant="body2"
+                      sx={{ color: theme => theme.palette.text.disabled }}
+                    >
+                      auto-generated from {inputFields?.join(', ')}
+                    </Typography>
+                  ),
+                  headerFormatter: v => {
+                    return (
+                      <CollectionColHeader def={v} collection={collection} />
+                    );
+                  },
+                };
+              }
+
               return {
                 id: i,
                 align: 'left',
                 disablePadding: false,
                 needCopy: true,
                 formatter(_: any, cellData: any) {
-                  const field = collection.schema.fields.find(
-                    f => f.name === i
-                  );
-
                   const fieldType = field?.data_type || 'JSON'; // dynamic
 
                   return <DataView type={fieldType} value={cellData} />;
@@ -289,18 +322,20 @@ const CollectionData = (props: CollectionDataProps) => {
             })}
             primaryKey={collection.schema.primaryField.name}
             openCheckBox={true}
-            isLoading={tableLoading}
+            isLoading={
+              tableLoading || (isCollectionSwitching && collection.loaded)
+            }
             rows={queryResult.data}
             rowCount={total}
             selected={selectedData}
-            tableHeaderHeight={43.5}
-            rowHeight={43}
             setSelected={onSelectChange}
             page={currentPage}
             onPageChange={handlePageChange}
             setRowsPerPage={setPageSize}
             rowsPerPage={pageSize}
-            showPagination={!tableLoading && collection.loaded}
+            showPagination={
+              !tableLoading && !isCollectionSwitching && collection.loaded
+            }
             labelDisplayedRows={getLabelDisplayedRows(
               commonTrans(
                 queryResult.data.length > 1 ? 'grid.entities' : 'grid.entity'
@@ -343,7 +378,7 @@ const CollectionData = (props: CollectionDataProps) => {
               </>
             )}
             noData={searchTrans(
-              `${collection.loaded ? 'empty' : 'collectionNotLoaded'}`
+              !collection.loaded ? 'collectionNotLoaded' : 'empty'
             )}
           />
         </>

+ 7 - 5
client/src/pages/databases/collections/data/QueryToolbar.tsx

@@ -19,6 +19,7 @@ interface QueryToolbarProps {
   handleFilterSubmit: (expression: string) => Promise<void>;
   handleFilterReset: () => Promise<void>;
   setCurrentPage: (page: number) => void;
+  forceDisabled?: boolean;
 }
 
 const QueryToolbar = (props: QueryToolbarProps) => {
@@ -32,6 +33,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
     handleFilterSubmit,
     handleFilterReset,
     setCurrentPage,
+    forceDisabled = false,
   } = props;
 
   // translations
@@ -50,7 +52,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
           value={exprInputRef.current}
           onChange={handleExprChange}
           onKeyDown={handleExprKeyDown}
-          disabled={!collection.loaded}
+          disabled={!collection.loaded || forceDisabled}
           fields={collection.schema.scalarFields}
           onSubmit={handleFilterSubmit}
         />
@@ -58,7 +60,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
         <FormControl
           variant="filled"
           className="selector"
-          disabled={!collection.loaded}
+          disabled={!collection.loaded || forceDisabled}
           sx={{ minWidth: 120 }}
         >
           <InputLabel>{collectionTrans('consistency')}</InputLabel>
@@ -121,7 +123,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
               )}`}</span>
             );
           }}
-          disabled={!collection.loaded}
+          disabled={!collection.loaded || forceDisabled}
           sx={{
             width: '120px',
             marginTop: '1px',
@@ -148,7 +150,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
         <CustomButton
           className="btn"
           onClick={handleFilterReset}
-          disabled={!collection.loaded}
+          disabled={!collection.loaded || forceDisabled}
           startIcon={<ResetIcon classes={{ root: 'icon' }} />}
         >
           {btnTrans('reset')}
@@ -165,7 +167,7 @@ const QueryToolbar = (props: QueryToolbarProps) => {
               tick: queryState.tick + 1,
             });
           }}
-          disabled={!collection.loaded}
+          disabled={!collection.loaded || forceDisabled}
         >
           {btnTrans('query')}
         </CustomButton>

+ 11 - 6
client/src/pages/databases/collections/partitions/Partitions.tsx

@@ -181,11 +181,6 @@ const Partitions = () => {
       needCopy: true,
       disablePadding: false,
       label: t('id'),
-      getStyle: () => {
-        return {
-          width: 120,
-        };
-      },
     },
     {
       id: 'name',
@@ -256,13 +251,23 @@ const Partitions = () => {
   };
 
   return (
-    <Box sx={{ height: '100%' }}>
+    <Box
+      sx={{
+        width: '100%',
+        height: 'calc(100vh - 128px)',
+        display: 'flex',
+        flexDirection: 'column',
+      }}
+    >
+      {' '}
       <AttuGrid
         toolbarConfigs={toolbarConfigs}
         colDefinitions={colDefinitions}
         rows={partitionList}
         rowCount={total}
         primaryKey="id"
+        rowHeight={43}
+        tableHeaderHeight={44}
         selected={selectedPartitions}
         setSelected={handleSelectChange}
         page={currentPage}

+ 0 - 10
client/src/pages/databases/collections/properties/Properties.tsx

@@ -150,11 +150,6 @@ const Properties = (props: PropertiesProps) => {
       disablePadding: false,
       label: t('property'),
       needCopy: true,
-      getStyle: () => {
-        return {
-          minWidth: 150,
-        };
-      },
     },
     {
       id: 'value',
@@ -168,11 +163,6 @@ const Properties = (props: PropertiesProps) => {
           return obj.type === 'number' ? formatNumber(obj.value) : obj.value;
         }
       },
-      getStyle: () => {
-        return {
-          minWidth: 450,
-        };
-      },
     },
   ];
 

+ 2 - 0
client/src/pages/databases/collections/schema/Schema.tsx

@@ -622,6 +622,8 @@ const Overview = () => {
           primaryKey="fieldID"
           showHoverStyle={false}
           isLoading={loading}
+          rowHeight={44}
+          tableHeaderHeight={44}
           openCheckBox={false}
           showPagination={false}
           labelDisplayedRows={getLabelDisplayedRows(

+ 2 - 13
client/src/pages/databases/collections/search/Search.tsx

@@ -341,17 +341,6 @@ const Search = (props: CollectionDataProps) => {
                   />
                 );
               },
-              getStyle: d => {
-                const field = collection.schema.fields.find(
-                  f => f.name === key
-                );
-                if (!d || !field) {
-                  return {};
-                }
-                return {
-                  minWidth: getColumnWidth(field),
-                };
-              },
             };
           })
       : [];
@@ -645,8 +634,8 @@ const Search = (props: CollectionDataProps) => {
                 rowCount={total}
                 primaryKey="rank"
                 page={currentPage}
-                tableHeaderHeight={46}
-                rowHeight={41}
+                tableHeaderHeight={45}
+                rowHeight={42}
                 openCheckBox={false}
                 onPageChange={handlePageChange}
                 rowsPerPage={pageSize}

+ 2 - 35
client/src/pages/databases/collections/segments/Segments.tsx

@@ -109,22 +109,12 @@ const Segments = () => {
       disablePadding: false,
       needCopy: true,
       label: 'ID',
-      getStyle: () => {
-        return {
-          minWidth: 155,
-        };
-      },
     },
     {
       id: 'level',
       align: 'left',
       disablePadding: false,
       label: 'Level',
-      getStyle: () => {
-        return {
-          minWidth: 15,
-        };
-      },
     },
     {
       id: 'partitionID',
@@ -132,33 +122,18 @@ const Segments = () => {
       disablePadding: false,
       needCopy: true,
       label: collectionTrans('partitionID'),
-      getStyle: () => {
-        return {
-          minWidth: 160,
-        };
-      },
     },
     {
       id: 'state',
       align: 'left',
       disablePadding: false,
       label: collectionTrans('segPState'),
-      getStyle: () => {
-        return {
-          minWidth: 160,
-        };
-      },
     },
     {
       id: 'num_rows',
       align: 'left',
       disablePadding: false,
       label: collectionTrans('num_rows'),
-      getStyle: () => {
-        return {
-          minWidth: 70,
-        };
-      },
     },
     {
       id: 'q_nodeIds',
@@ -168,22 +143,12 @@ const Segments = () => {
       formatter(data, cellData, cellIndex) {
         return cellData.join(',');
       },
-      getStyle: () => {
-        return {
-          minWidth: 35,
-        };
-      },
     },
     {
       id: 'q_state',
       align: 'left',
       disablePadding: false,
       label: collectionTrans('q_state'),
-      getStyle: () => {
-        return {
-          minWidth: 70,
-        };
-      },
     },
     // {
     //   id: 'q_index_name',
@@ -228,6 +193,8 @@ const Segments = () => {
         toolbarConfigs={[]}
         colDefinitions={colDefinitions}
         rows={data}
+        rowHeight={43}
+        tableHeaderHeight={45}
         rowCount={total}
         primaryKey="name"
         showPagination={true}

+ 2 - 5
client/src/pages/user/User.tsx

@@ -231,9 +231,6 @@ const Users = () => {
       formatter(rowData, cellData) {
         return rowData.username === 'root' ? 'admin' : cellData.join(', ');
       },
-      getStyle: () => {
-        return { width: '80%', maxWidth: '80%' };
-      },
     },
   ];
 
@@ -259,8 +256,8 @@ const Users = () => {
         primaryKey="username"
         showPagination={true}
         selected={selectedUser}
-        tableHeaderHeight={46}
-        rowHeight={39}
+        tableHeaderHeight={44}
+        rowHeight={40}
         tableCellMaxWidth="100%"
         setSelected={handleSelectChange}
         page={currentPage}

+ 22 - 1
client/src/styles/theme.ts

@@ -292,7 +292,7 @@ export const getAttuTheme = (mode: PaletteMode) => {
       },
       MuiButton: {
         defaultProps: {
-          disableRipple: true,
+          disableRipple: false,
         },
         styleOverrides: {
           root: ({
@@ -305,6 +305,7 @@ export const getAttuTheme = (mode: PaletteMode) => {
             padding: theme.spacing(1, 3),
             textTransform: 'initial',
             fontWeight: 'bold',
+            transition: 'all 0.2s ease-in-out',
             ...(ownerState.variant === 'text' && {
               padding: theme.spacing(1),
               color: theme.palette.primary.main,
@@ -312,6 +313,9 @@ export const getAttuTheme = (mode: PaletteMode) => {
                 backgroundColor: theme.palette.primary.main,
                 color: theme.palette.background.paper,
               },
+              '&:active': {
+                backgroundColor: theme.palette.primary.dark,
+              },
             }),
             ...(ownerState.variant === 'contained' && {
               boxShadow: 'none',
@@ -322,10 +326,27 @@ export const getAttuTheme = (mode: PaletteMode) => {
                     ? theme.palette.secondary.dark
                     : theme.palette.primary.dark,
               },
+              '&:active': {
+                backgroundColor:
+                  ownerState.color === 'secondary'
+                    ? theme.palette.secondary.dark
+                    : theme.palette.primary.dark,
+                boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
+              },
+            }),
+            ...(ownerState.variant === 'outlined' && {
+              '&:hover': {
+                backgroundColor: theme.palette.primary.main,
+                color: theme.palette.background.paper,
+              },
+              '&:active': {
+                backgroundColor: theme.palette.primary.dark,
+              },
             }),
             ...(ownerState.disabled && {
               pointerEvents: 'none',
               opacity: 0.6,
+              transform: 'none !important',
             }),
           }),
         },