This is an automated email from the ASF dual-hosted git repository.

enzomartellucci pushed a commit to branch enxdev/fix/visual-regression-p7
in repository https://gitbox.apache.org/repos/asf/superset.git

commit dbbf959d73451af986da64219a96bae48e7a75f0
Author: Enzo Martellucci <enzomartellu...@gmail.com>
AuthorDate: Fri Jul 18 17:16:01 2025 +0200

    refactor(Modal titles): create reusable ModalTitleWithIcon component with 
edit mode support and customizable icon - Supports dynamic titles based on edit 
mode (add/edit) - Allows passing a custom icon with forced iconSize override - 
Applies
    consistent styling and spacing for title and icon - Adds data-test 
attribute for easier testing - Improves code clarity by handling icon rendering 
and translation internally
---
 .../SqlLab/components/SaveDatasetModal/index.tsx   | 23 +++---
 .../src/SqlLab/components/SaveQuery/index.tsx      | 11 ++-
 .../src/components/ModalTitleWithIcon/index.tsx    | 84 ++++++++++++++++++++++
 .../dashboard/components/PropertiesModal/index.tsx |  3 +-
 .../explore/components/PropertiesModal/index.tsx   | 21 +++---
 .../src/features/alerts/AlertReportModal.tsx       | 23 ++++--
 .../annotationLayers/AnnotationLayerModal.tsx      | 35 +++------
 .../src/features/cssTemplates/CssTemplateModal.tsx | 36 +++-------
 .../src/features/databases/DatabaseModal/index.tsx | 55 +++++++-------
 .../features/databases/UploadDataModel/index.tsx   |  3 +-
 .../features/datasets/DuplicateDatasetModal.tsx    | 10 ++-
 .../src/features/groups/GroupListModal.tsx         | 11 ++-
 .../src/features/rls/RowLevelSecurityModal.tsx     | 31 +++-----
 .../src/features/roles/RoleListAddModal.tsx        |  8 ++-
 .../src/features/tags/BulkTagModal.tsx             |  4 +-
 superset-frontend/src/features/tags/TagModal.tsx   | 12 +++-
 .../src/features/users/UserListModal.tsx           | 11 ++-
 superset-frontend/src/pages/GroupsList/index.tsx   |  2 -
 superset-frontend/src/pages/UserInfo/index.tsx     |  1 -
 19 files changed, 235 insertions(+), 149 deletions(-)

diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx 
b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
index f3d34d75c8..0fa9effb93 100644
--- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx
@@ -18,7 +18,7 @@
  */
 
 import { useCallback, useState, FormEvent } from 'react';
-
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { Radio, RadioChangeEvent } from '@superset-ui/core/components/Radio';
 import {
   AsyncSelect,
@@ -27,6 +27,8 @@ import {
   Modal,
   Input,
   type SelectValue,
+  Icons,
+  Flex,
 } from '@superset-ui/core/components';
 import {
   styled,
@@ -372,17 +374,16 @@ export const SaveDatasetModal = ({
   return (
     <Modal
       show={visible}
-      title={t('Save or Overwrite Dataset')}
+      title={
+        <ModalTitleWithIcon
+          title={t('Save or Overwrite Dataset')}
+          icon={<Icons.SaveOutlined />}
+          dataTestId="save-or-overwrite-dataset-title"
+        />
+      }
       onHide={onHide}
       footer={
-        <div
-          style={{
-            display: 'flex',
-            alignItems: 'center',
-            justifyContent: 'flex-end',
-            gap: '8px',
-          }}
-        >
+        <Flex align="center" justify="flex-end" gap="8px">
           {isFeatureEnabled(FeatureFlag.EnableTemplateProcessing) && (
             <div style={{ display: 'flex', alignItems: 'center' }}>
               <Checkbox
@@ -422,7 +423,7 @@ export const SaveDatasetModal = ({
               </Button>
             </>
           )}
-        </div>
+        </Flex>
       }
     >
       <Styles>
diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx 
b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
index c5918d1b7c..9e1e52e909 100644
--- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
@@ -17,7 +17,6 @@
  * under the License.
  */
 import { useState, useEffect, useMemo, ChangeEvent } from 'react';
-
 import type { DatabaseObject } from 'src/features/databases/types';
 import { t, styled } from '@superset-ui/core';
 import {
@@ -28,6 +27,7 @@ import {
   Modal,
   Row,
   Col,
+  Icons,
 } from '@superset-ui/core/components';
 import { Menu } from '@superset-ui/core/components/Menu';
 import SaveDatasetActionButton from 
'src/SqlLab/components/SaveDatasetActionButton';
@@ -43,6 +43,7 @@ import {
   LOG_ACTIONS_SQLLAB_CREATE_CHART,
   LOG_ACTIONS_SQLLAB_SAVE_QUERY,
 } from 'src/logger/LogUtils';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 
 interface SaveQueryProps {
   queryEditorId: string;
@@ -224,7 +225,13 @@ const SaveQuery = ({
         primaryButtonName={isSaved ? t('Save') : t('Save as')}
         width="620px"
         show={showSave}
-        title={<h4>{t('Save query')}</h4>}
+        title={
+          <ModalTitleWithIcon
+            title={t('Save query')}
+            icon={<Icons.SaveOutlined />}
+            dataTestId="save-query-modal-title"
+          />
+        }
         footer={
           <>
             <Button
diff --git a/superset-frontend/src/components/ModalTitleWithIcon/index.tsx 
b/superset-frontend/src/components/ModalTitleWithIcon/index.tsx
new file mode 100644
index 0000000000..2d3dd4145c
--- /dev/null
+++ b/superset-frontend/src/components/ModalTitleWithIcon/index.tsx
@@ -0,0 +1,84 @@
+import { isValidElement, cloneElement } from 'react';
+import { css, useTheme, t } from '@superset-ui/core';
+import { Typography, Icons } from '@superset-ui/core/components';
+
+type EditModeTitleConfig = {
+  /** Indicates whether the component is in edit mode */
+  isEditMode: boolean;
+  /** Title shown when not in edit mode */
+  titleAdd: string;
+  /** Title shown when in edit mode */
+  titleEdit: string;
+};
+
+type ModalTitleWithIconProps = {
+  /**
+   * Optional configuration for dynamic titles based on edit mode.
+   * If provided, it overrides the static `title` prop.
+   */
+  editModeConfig?: EditModeTitleConfig;
+
+  /**
+   * Static title used when `editModeConfig` is not provided.
+   */
+  title?: string;
+
+  /**
+   * Optional icon displayed before the title.
+   * If not provided and `editModeConfig` is set, a default icon is used:
+   * - `EditOutlined` if `isEditMode === true`
+   * - `PlusOutlined` if `isEditMode === false`
+   */
+  icon?: React.ReactNode;
+
+  /**
+   * Test ID used for end-to-end or unit testing (e.g. Cypress, Testing 
Library).
+   */
+  dataTestId?: string;
+
+  /**
+   * Ant Design Typography title level (default: 5)
+   */
+  level?: 1 | 2 | 3 | 4 | 5;
+};
+
+export const ModalTitleWithIcon = ({
+  editModeConfig,
+  title,
+  icon,
+  dataTestId = 'css-template-modal-title',
+  level = 5,
+}: ModalTitleWithIconProps) => {
+  const theme = useTheme();
+  const iconStyles = css`
+    margin: auto ${theme.sizeUnit * 2}px auto 0;
+  `;
+  const titleStyles = css`
+    && {
+      margin: 0;
+      margin-bottom: 0;
+    }
+  `;
+  const renderedTitle = editModeConfig
+    ? editModeConfig.isEditMode
+      ? editModeConfig.titleEdit
+      : editModeConfig.titleAdd
+    : title;
+
+  const renderedIcon = isValidElement(icon) ? (
+    cloneElement(icon, { iconSize: 'l', css: iconStyles })
+  ) : editModeConfig ? (
+    editModeConfig.isEditMode ? (
+      <Icons.EditOutlined iconSize="l" css={iconStyles} />
+    ) : (
+      <Icons.PlusOutlined iconSize="l" css={iconStyles} />
+    )
+  ) : null;
+
+  return (
+    <Typography.Title level={level} css={titleStyles} data-test={dataTestId}>
+      {renderedIcon}
+      {t(renderedTitle || '')}
+    </Typography.Title>
+  );
+};
diff --git 
a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx 
b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index 0871b63b05..2b4ea97f88 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -64,6 +64,7 @@ import {
   setDashboardMetadata,
 } from 'src/dashboard/actions/dashboardState';
 import { areObjectsEqual } from 'src/reduxUtils';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 
 const StyledJsonEditor = styled(JsonEditor)`
   border-radius: ${({ theme }) => theme.borderRadius}px;
@@ -609,7 +610,7 @@ const PropertiesModal = ({
     <Modal
       show={show}
       onHide={handleOnCancel}
-      title={t('Dashboard properties')}
+      title={<ModalTitleWithIcon title="Dashboard properties" />}
       footer={
         <>
           <Button
diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx 
b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
index 63d7824c90..8926a74ed8 100644
--- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
@@ -40,13 +40,12 @@ import {
   getClientErrorObject,
   ensureIsArray,
   useTheme,
-  css,
 } from '@superset-ui/core';
-import { Icons } from '@superset-ui/core/components/Icons';
 import Chart, { Slice } from 'src/types/Chart';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import { type TagType } from 'src/components';
 import { loadTags } from 'src/components/Tag/utils';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 
 export type PropertiesModalProps = {
   slice: Slice;
@@ -69,7 +68,6 @@ function PropertiesModal({
   show,
   addSuccessToast,
 }: PropertiesModalProps) {
-  const theme = useTheme();
   const [submitting, setSubmitting] = useState(false);
   const [form] = Form.useForm();
   // values of form inputs
@@ -246,15 +244,14 @@ function PropertiesModal({
       show={show}
       onHide={onHide}
       title={
-        <span>
-          <Icons.EditOutlined
-            css={css`
-              margin: auto ${theme.sizeUnit * 2}px auto 0;
-            `}
-            data-test="edit-alt"
-          />
-          {t('Edit Chart Properties')}
-        </span>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode: true,
+            titleAdd: '',
+            titleEdit: 'Edit Chart Properties',
+          }}
+          dataTestId="edit-alt"
+        />
       }
       footer={
         <>
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx 
b/superset-frontend/src/features/alerts/AlertReportModal.tsx
index 9376949415..2d797196d7 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx
@@ -35,6 +35,7 @@ import {
   SupersetTheme,
   t,
   VizType,
+  useTheme,
 } from '@superset-ui/core';
 import rison from 'rison';
 import { useSingleViewResource } from 'src/views/CRUD/hooks';
@@ -51,7 +52,6 @@ import {
   Switch,
   TreeSelect,
   type CheckboxChangeEvent,
-  Typography,
 } from '@superset-ui/core/components';
 import TimezoneSelector from '@superset-ui/core/components/TimezoneSelector';
 import { propertyComparator } from '@superset-ui/core/components/Select/utils';
@@ -81,6 +81,7 @@ import { useSelector } from 'react-redux';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { Icons } from '@superset-ui/core/components/Icons';
 import { useOpenerRef } from 'src/hooks/useOpenerRef';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import NumberInput from './components/NumberInput';
 import { AlertReportCronScheduler } from 
'./components/AlertReportCronScheduler';
 import { NotificationMethod } from './components/NotificationMethod';
@@ -250,7 +251,6 @@ export const StyledInputContainer = styled.div`
     flex: 1;
     margin-top: 0px;
     margin-bottom: ${theme.sizeUnit * 4}px;
-
     input::-webkit-outer-spin-button,
     input::-webkit-inner-spin-button {
       -webkit-appearance: none;
@@ -422,6 +422,7 @@ const AlertReportModal: 
FunctionComponent<AlertReportModalProps> = ({
   isReport = false,
   addSuccessToast,
 }) => {
+  const theme = useTheme();
   const openerRef = useOpenerRef(show);
   const currentUser = useSelector<any, UserWithPermissionsAndRoles>(
     state => state.user,
@@ -1455,9 +1456,14 @@ const AlertReportModal: 
FunctionComponent<AlertReportModalProps> = ({
       centered
       openerRef={openerRef}
       title={
-        <Typography.Title level={4} data-test="alert-report-modal-title">
-          {getTitleText()}
-        </Typography.Title>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: getTitleText(),
+            titleEdit: getTitleText(),
+          }}
+          dataTestId="alert-report-modal-title"
+        />
       }
     >
       <Collapse
@@ -1614,7 +1620,12 @@ const AlertReportModal: 
FunctionComponent<AlertReportModalProps> = ({
                           key={currentAlert?.id}
                         />
                       </StyledInputContainer>
-                      <div className="inline-container wrap">
+                      <div
+                        className="inline-container wrap"
+                        css={css`
+                          gap: ${theme.sizeUnit}px;
+                        `}
+                      >
                         <StyledInputContainer css={noMarginBottom}>
                           <div className="control-label" css={inputSpacer}>
                             {t('Trigger Alert If...')}
diff --git 
a/superset-frontend/src/features/annotationLayers/AnnotationLayerModal.tsx 
b/superset-frontend/src/features/annotationLayers/AnnotationLayerModal.tsx
index 2495e520c0..e9871eaeb3 100644
--- a/superset-frontend/src/features/annotationLayers/AnnotationLayerModal.tsx
+++ b/superset-frontend/src/features/annotationLayers/AnnotationLayerModal.tsx
@@ -18,10 +18,9 @@
  */
 import { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
 
-import { css, styled, t, useTheme } from '@superset-ui/core';
+import { styled, t } from '@superset-ui/core';
 import { useSingleViewResource } from 'src/views/CRUD/hooks';
-
-import { Icons } from '@superset-ui/core/components/Icons';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { Typography } from '@superset-ui/core/components/Typography';
 import { Input, Modal } from '@superset-ui/core/components';
 import withToasts from 'src/components/MessageToasts/withToasts';
@@ -94,7 +93,6 @@ const AnnotationLayerModal: 
FunctionComponent<AnnotationLayerModalProps> = ({
   show,
   layer = null,
 }) => {
-  const theme = useTheme();
   const [disableSave, setDisableSave] = useState<boolean>(true);
   const [currentLayer, setCurrentLayer] =
     useState<AnnotationLayerObject | null>();
@@ -236,27 +234,14 @@ const AnnotationLayerModal: 
FunctionComponent<AnnotationLayerModalProps> = ({
       show={show}
       width="55%"
       title={
-        <Typography.Title level={4} data-test="annotation-layer-modal-title">
-          {isEditMode ? (
-            <Icons.EditOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          ) : (
-            <Icons.PlusOutlined
-              iconSize="m"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-                vertical-align: text-top;
-              `}
-            />
-          )}
-          {isEditMode
-            ? t('Edit annotation layer properties')
-            : t('Add annotation layer')}
-        </Typography.Title>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Add annotation layer',
+            titleEdit: 'Edit annotation layer properties',
+          }}
+          dataTestId="annotation-layer-modal-title"
+        />
       }
     >
       <StyledAnnotationLayerTitle>
diff --git a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx 
b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
index abe5085800..8a6d6f5712 100644
--- a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
+++ b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
@@ -17,15 +17,12 @@
  * under the License.
  */
 import { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
-
-import { css, styled, t, useTheme } from '@superset-ui/core';
+import { css, styled, t } from '@superset-ui/core';
 import { useSingleViewResource } from 'src/views/CRUD/hooks';
-
-import { Icons } from '@superset-ui/core/components/Icons';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import { Input, CssEditor, Modal } from '@superset-ui/core/components';
 import { Typography } from '@superset-ui/core/components/Typography';
-
 import { OnlyKeyWithType } from 'src/utils/types';
 import { TemplateObject } from './types';
 
@@ -84,7 +81,6 @@ const CssTemplateModal: 
FunctionComponent<CssTemplateModalProps> = ({
   show,
   cssTemplate = null,
 }) => {
-  const theme = useTheme();
   const [disableSave, setDisableSave] = useState<boolean>(true);
   const [currentCssTemplate, setCurrentCssTemplate] =
     useState<TemplateObject | null>(null);
@@ -232,26 +228,14 @@ const CssTemplateModal: 
FunctionComponent<CssTemplateModalProps> = ({
       show={show}
       width="55%"
       title={
-        <Typography.Title level={4} data-test="css-template-modal-title">
-          {isEditMode ? (
-            <Icons.EditOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          ) : (
-            <Icons.PlusOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          )}
-          {isEditMode
-            ? t('Edit CSS template properties')
-            : t('Add CSS template')}
-        </Typography.Title>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Add CSS template',
+            titleEdit: 'Edit CSS template properties',
+          }}
+          dataTestId="css-template-modal-title"
+        />
       }
     >
       <StyledCssTemplateTitle>
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx 
b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index 70484205aa..1f9c04b4bb 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -21,7 +21,6 @@ import {
   styled,
   SupersetTheme,
   getExtensionsRegistry,
-  css,
   useTheme,
 } from '@superset-ui/core';
 
@@ -69,6 +68,7 @@ import {
 import { useCommonConf } from 'src/features/databases/state';
 import { isEmpty, pick } from 'lodash';
 import { OnlyKeyWithType } from 'src/utils/types';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import {
   DatabaseObject,
   DatabaseForm,
@@ -575,7 +575,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
   databaseId,
   dbEngine,
 }) => {
-  const theme = useTheme();
   const [db, setDB] = useReducer<
     Reducer<Partial<DatabaseObject> | null, DBReducerActionType>
   >(dbReducer, null);
@@ -1837,7 +1836,12 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
         onHandledPrimaryAction={onSave}
         primaryButtonName={t('Connect')}
         show={show}
-        title={<h4>{t('Connect a database')}</h4>}
+        title={
+          <ModalTitleWithIcon
+            title="Connect a database"
+            icon={<Icons.InsertRowAboveOutlined />}
+          />
+        }
         width="500px"
       >
         <ModalHeader
@@ -1877,24 +1881,20 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
       centered
       show={show}
       title={
-        <h4>
-          {isEditMode ? (
-            <Icons.EditOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          ) : (
-            <Icons.InsertRowAboveOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          )}
-          {isEditMode ? t('Edit database') : t('Connect a database')}
-        </h4>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Connect a database',
+            titleEdit: 'Edit database',
+          }}
+          icon={
+            isEditMode ? (
+              <Icons.EditOutlined iconSize="l" />
+            ) : (
+              <Icons.InsertRowAboveOutlined iconSize="l" />
+            )
+          }
+        />
       }
       footer={modalFooter}
       maskClosable={false}
@@ -2078,15 +2078,10 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
       centered
       show={show}
       title={
-        <h4>
-          <Icons.InsertRowAboveOutlined
-            iconSize="l"
-            css={css`
-              margin: auto ${theme.sizeUnit * 2}px auto 0;
-            `}
-          />
-          {t('Connect a database')}
-        </h4>
+        <ModalTitleWithIcon
+          title="Connect a database"
+          icon={<Icons.InsertRowAboveOutlined />}
+        />
       }
       footer={renderModalFooter()}
       maskClosable={false}
diff --git a/superset-frontend/src/features/databases/UploadDataModel/index.tsx 
b/superset-frontend/src/features/databases/UploadDataModel/index.tsx
index 284d837dc7..0564ebb495 100644
--- a/superset-frontend/src/features/databases/UploadDataModel/index.tsx
+++ b/superset-frontend/src/features/databases/UploadDataModel/index.tsx
@@ -50,6 +50,7 @@ import { Switch, SwitchProps } from 
'@superset-ui/core/components/Switch';
 import { Icons } from '@superset-ui/core/components/Icons';
 import rison from 'rison';
 import withToasts from 'src/components/MessageToasts/withToasts';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import {
   antDModalNoPaddingStyles,
   antDModalStyles,
@@ -576,7 +577,7 @@ const UploadDataModal: 
FunctionComponent<UploadDataModalProps> = ({
 
   const UploadTitle: FC = () => {
     const title = uploadTitles[type] || t('Upload');
-    return <h4>{title}</h4>;
+    return <ModalTitleWithIcon title={title} />;
   };
 
   return (
diff --git a/superset-frontend/src/features/datasets/DuplicateDatasetModal.tsx 
b/superset-frontend/src/features/datasets/DuplicateDatasetModal.tsx
index f8225ad9cc..9431061b25 100644
--- a/superset-frontend/src/features/datasets/DuplicateDatasetModal.tsx
+++ b/superset-frontend/src/features/datasets/DuplicateDatasetModal.tsx
@@ -18,7 +18,8 @@
  */
 import { t } from '@superset-ui/core';
 import { FunctionComponent, useEffect, useState, ChangeEvent } from 'react';
-import { Input, FormLabel, Modal } from '@superset-ui/core/components';
+import { Input, FormLabel, Modal, Icons } from '@superset-ui/core/components';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import Dataset from 'src/types/Dataset';
 
 interface DuplicateDatasetModalProps {
@@ -56,7 +57,12 @@ const DuplicateDatasetModal: 
FunctionComponent<DuplicateDatasetModalProps> = ({
     <Modal
       show={show}
       onHide={onHide}
-      title={t('Duplicate dataset')}
+      title={
+        <ModalTitleWithIcon
+          title="Duplicate dataset"
+          icon={<Icons.CopyOutlined />}
+        />
+      }
       disablePrimaryButton={disableSave}
       onHandledPrimaryAction={duplicateDataset}
       primaryButtonName={t('Duplicate')}
diff --git a/superset-frontend/src/features/groups/GroupListModal.tsx 
b/superset-frontend/src/features/groups/GroupListModal.tsx
index baf728f5b8..46aa81cf84 100644
--- a/superset-frontend/src/features/groups/GroupListModal.tsx
+++ b/superset-frontend/src/features/groups/GroupListModal.tsx
@@ -18,6 +18,7 @@
  */
 import { t } from '@superset-ui/core';
 import { useToasts } from 'src/components/MessageToasts/withToasts';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { Actions } from 'src/constants';
 import { GroupObject } from 'src/pages/GroupsList';
 import {
@@ -101,7 +102,15 @@ function GroupListModal({
     <FormModal
       show={show}
       onHide={onHide}
-      title={isEditMode ? t('Edit Group') : t('Add Group')}
+      title={
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Add Group',
+            titleEdit: 'Edit Group',
+          }}
+        />
+      }
       onSave={onSave}
       formSubmitHandler={handleFormSubmit}
       requiredFields={requiredFields}
diff --git a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx 
b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
index 83bd11754e..5b68ac8b3d 100644
--- a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
+++ b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
@@ -17,9 +17,9 @@
  * under the License.
  */
 
-import { css, styled, SupersetClient, useTheme, t } from '@superset-ui/core';
+import { css, styled, SupersetClient, t } from '@superset-ui/core';
 import { useCallback, useEffect, useMemo, useState } from 'react';
-import { Icons } from '@superset-ui/core/components/Icons';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import {
   Modal,
   Select,
@@ -28,7 +28,6 @@ import {
   LabeledErrorBoundInput,
   Input,
 } from '@superset-ui/core/components';
-import { Typography } from '@superset-ui/core/components/Typography';
 import rison from 'rison';
 import { useSingleViewResource } from 'src/views/CRUD/hooks';
 import { FILTER_OPTIONS } from './constants';
@@ -125,7 +124,6 @@ const DEFAULT_RULE = {
 };
 
 function RowLevelSecurityModal(props: RowLevelSecurityModalProps) {
-  const theme = useTheme();
   const { rule, addDangerToast, addSuccessToast, onHide, show } = props;
 
   const [currentRule, setCurrentRule] = useState<RLSObject>({
@@ -336,23 +334,14 @@ function RowLevelSecurityModal(props: 
RowLevelSecurityModalProps) {
       width="30%"
       maxWidth="1450px"
       title={
-        <Typography.Title level={4} data-test="rls-modal-title">
-          {isEditMode ? (
-            <Icons.EditOutlined
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          ) : (
-            <Icons.PlusOutlined
-              iconSize="l"
-              css={css`
-                margin: auto ${theme.sizeUnit * 2}px auto 0;
-              `}
-            />
-          )}
-          {isEditMode ? t('Edit Rule') : t('Add Rule')}
-        </Typography.Title>
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Add Rule',
+            titleEdit: 'Edit Rule',
+          }}
+          dataTestId="rls-modal-title"
+        />
       }
     >
       <StyledSectionContainer>
diff --git a/superset-frontend/src/features/roles/RoleListAddModal.tsx 
b/superset-frontend/src/features/roles/RoleListAddModal.tsx
index 15f51385c3..6191a3ebcb 100644
--- a/superset-frontend/src/features/roles/RoleListAddModal.tsx
+++ b/superset-frontend/src/features/roles/RoleListAddModal.tsx
@@ -17,8 +17,9 @@
  * under the License.
  */
 import { t } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { useToasts } from 'src/components/MessageToasts/withToasts';
-import { FormModal } from '@superset-ui/core/components';
+import { FormModal, Icons } from '@superset-ui/core/components';
 import { createRole, updateRolePermissions } from './utils';
 import { PermissionsField, RoleNameField } from './RoleFormItems';
 import { BaseModalProps, FormattedPermission, RoleForm } from './types';
@@ -34,7 +35,6 @@ function RoleListAddModal({
   permissions,
 }: RoleListAddModalProps) {
   const { addDangerToast, addSuccessToast } = useToasts();
-
   const handleFormSubmit = async (values: RoleForm) => {
     try {
       const { json: roleResponse } = await createRole(values.roleName);
@@ -56,7 +56,9 @@ function RoleListAddModal({
     <FormModal
       show={show}
       onHide={onHide}
-      title={t('Add Role')}
+      title={
+        <ModalTitleWithIcon title="Add Role" icon={<Icons.PlusOutlined />} />
+      }
       onSave={onSave}
       formSubmitHandler={handleFormSubmit}
       requiredFields={['roleName']}
diff --git a/superset-frontend/src/features/tags/BulkTagModal.tsx 
b/superset-frontend/src/features/tags/BulkTagModal.tsx
index 08830dd586..2d24227b14 100644
--- a/superset-frontend/src/features/tags/BulkTagModal.tsx
+++ b/superset-frontend/src/features/tags/BulkTagModal.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { useState, useEffect, FC } from 'react';
-
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { t, styled, SupersetClient } from '@superset-ui/core';
 import {
   FormLabel,
@@ -94,7 +94,7 @@ const BulkTagModal: FC<BulkTagModalProps> = ({
 
   return (
     <Modal
-      title={t('Bulk tag')}
+      title={<ModalTitleWithIcon title="Bulk tag" />}
       show={show}
       onHide={() => {
         setTags([]);
diff --git a/superset-frontend/src/features/tags/TagModal.tsx 
b/superset-frontend/src/features/tags/TagModal.tsx
index 329f45f281..bb1a52422a 100644
--- a/superset-frontend/src/features/tags/TagModal.tsx
+++ b/superset-frontend/src/features/tags/TagModal.tsx
@@ -30,6 +30,7 @@ import {
 import { t, styled, SupersetClient } from '@superset-ui/core';
 import { Tag } from 'src/views/CRUD/types';
 import { fetchObjectsByTagIds } from 'src/features/tags/tags';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 
 const StyledModalBody = styled.div`
   .ant-select-dropdown {
@@ -83,7 +84,6 @@ const TagModal: FC<TagModalProps> = ({
   const [description, setDescription] = useState<string>('');
 
   const isEditMode = !!editTag;
-  const modalTitle = isEditMode ? 'Edit Tag' : 'Create Tag';
 
   const clearResources = () => {
     setDashboardsToTag([]);
@@ -264,7 +264,15 @@ const TagModal: FC<TagModalProps> = ({
 
   return (
     <Modal
-      title={modalTitle}
+      title={
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Create Tag',
+            titleEdit: 'Edit Tag',
+          }}
+        />
+      }
       onHide={() => {
         if (clearOnHide) clearTagForm();
         onHide();
diff --git a/superset-frontend/src/features/users/UserListModal.tsx 
b/superset-frontend/src/features/users/UserListModal.tsx
index f0a104ba3d..916625e448 100644
--- a/superset-frontend/src/features/users/UserListModal.tsx
+++ b/superset-frontend/src/features/users/UserListModal.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { t } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 import { useToasts } from 'src/components/MessageToasts/withToasts';
 import {
   Checkbox,
@@ -120,7 +121,15 @@ function UserListModal({
     <FormModal
       show={show}
       onHide={onHide}
-      title={isEditMode ? t('Edit User') : t('Add User')}
+      title={
+        <ModalTitleWithIcon
+          editModeConfig={{
+            isEditMode,
+            titleAdd: 'Add User',
+            titleEdit: 'Edit User',
+          }}
+        />
+      }
       onSave={onSave}
       formSubmitHandler={handleFormSubmit}
       requiredFields={requiredFields}
diff --git a/superset-frontend/src/pages/GroupsList/index.tsx 
b/superset-frontend/src/pages/GroupsList/index.tsx
index f497e34330..ef770660f5 100644
--- a/superset-frontend/src/pages/GroupsList/index.tsx
+++ b/superset-frontend/src/pages/GroupsList/index.tsx
@@ -277,7 +277,6 @@ function GroupsList({ user }: GroupsListProps) {
         name: (
           <>
             <Icons.PlusOutlined
-              iconColor={theme.colorText}
               iconSize="m"
               css={css`
                 margin: auto ${theme.sizeUnit * 2}px auto 0;
@@ -364,7 +363,6 @@ function GroupsList({ user }: GroupsListProps) {
       buttonText: (
         <>
           <Icons.PlusOutlined
-            iconColor={theme.colorText}
             iconSize="m"
             css={css`
               margin: auto ${theme.sizeUnit * 2}px auto 0;
diff --git a/superset-frontend/src/pages/UserInfo/index.tsx 
b/superset-frontend/src/pages/UserInfo/index.tsx
index c02cc0f2a1..061cba8ce0 100644
--- a/superset-frontend/src/pages/UserInfo/index.tsx
+++ b/superset-frontend/src/pages/UserInfo/index.tsx
@@ -136,7 +136,6 @@ export function UserInfo({ user }: { user: 
UserWithPermissionsAndRoles }) {
       name: (
         <>
           <Icons.FormOutlined
-            iconColor={theme.colorIcon}
             iconSize="m"
             css={css`
               margin: auto ${theme.sizeUnit * 2}px auto 0;

Reply via email to