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

sbin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 33441ccf3db feat: add formatting column and formatting object to 
conditional formating table (#35897)
33441ccf3db is described below

commit 33441ccf3dbe597576403436a9d8f46c0d2690d9
Author: SBIN2010 <[email protected]>
AuthorDate: Thu Feb 19 02:07:15 2026 +0300

    feat: add formatting column and formatting object to conditional formating 
table (#35897)
---
 .../packages/superset-core/src/ui/theme/types.ts   |   1 +
 .../superset-ui-chart-controls/src/types.ts        |  11 +
 .../src/utils/getColorFormatters.ts                |   2 +
 .../plugins/plugin-chart-table/src/TableChart.tsx  |  65 +++--
 .../plugin-chart-table/src/controlPanel.tsx        |  33 ++-
 .../plugin-chart-table/test/TableChart.test.tsx    |   9 +-
 .../ConditionalFormattingControl.tsx               |   5 +-
 .../FormattingPopover.tsx                          |   4 +-
 .../FormattingPopoverContent.test.tsx              | 137 +++++++----
 .../FormattingPopoverContent.tsx                   | 264 +++++++++++----------
 .../ConditionalFormattingControl/constants.ts      |  72 ++++++
 .../controls/ConditionalFormattingControl/types.ts |  25 +-
 superset/config.py                                 |   1 +
 13 files changed, 421 insertions(+), 208 deletions(-)

diff --git a/superset-frontend/packages/superset-core/src/ui/theme/types.ts 
b/superset-frontend/packages/superset-core/src/ui/theme/types.ts
index ac905393f9a..a0b30ca5956 100644
--- a/superset-frontend/packages/superset-core/src/ui/theme/types.ts
+++ b/superset-frontend/packages/superset-core/src/ui/theme/types.ts
@@ -116,6 +116,7 @@ export interface SupersetSpecificTokens {
   fontWeightNormal: string;
   fontWeightLight: string;
   fontWeightStrong: number;
+  fontWeightBold: string;
 
   // Brand-related
   brandIconMaxWidth: number;
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts 
b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
index 25aaa81b30b..b501b7c9bff 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -491,12 +491,16 @@ export type ConditionalFormattingConfig = {
   toAllRow?: boolean;
   toTextColor?: boolean;
   useGradient?: boolean;
+  columnFormatting?: string;
+  objectFormatting?: ObjectFormattingEnum;
 };
 
 export type ColorFormatters = {
   column: string;
   toAllRow?: boolean;
   toTextColor?: boolean;
+  columnFormatting?: string;
+  objectFormatting?: ObjectFormattingEnum;
   getColorFromValue: (
     value: number | string | boolean | null,
   ) => string | undefined;
@@ -614,6 +618,13 @@ export type ControlFormItemSpec<T extends ControlType = 
ControlType> = {
               }
             : {});
 
+export enum ObjectFormattingEnum {
+  BACKGROUND_COLOR = 'BACKGROUND_COLOR',
+  TEXT_COLOR = 'TEXT_COLOR',
+  CELL_BAR = 'CELL_BAR',
+  ENTIRE_ROW = 'ENTIRE_ROW',
+}
+
 export enum ColorSchemeEnum {
   Green = 'Green',
   Red = 'Red',
diff --git 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts
 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts
index dbfa4b28428..81d1ee40a42 100644
--- 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts
+++ 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts
@@ -311,6 +311,8 @@ export const getColorFormatters = memoizeOne(
             column: config?.column,
             toAllRow: config?.toAllRow,
             toTextColor: config?.toTextColor,
+            columnFormatting: config?.columnFormatting,
+            objectFormatting: config?.objectFormatting,
             getColorFromValue: getColorFunction(
               { ...config, colorScheme: resolvedColorScheme },
               data.map(row => row[config.column!] as number),
diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx 
b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
index 9aae0231f51..499ae52e952 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
@@ -74,7 +74,11 @@ import {
   TableOutlined,
 } from '@ant-design/icons';
 import { isEmpty, debounce, isEqual } from 'lodash';
-import { ColorFormatters, ColorSchemeEnum } from '@superset-ui/chart-controls';
+import {
+  ColorFormatters,
+  ObjectFormattingEnum,
+  ColorSchemeEnum,
+} from '@superset-ui/chart-controls';
 import {
   DataColumnMeta,
   SearchOption,
@@ -874,12 +878,11 @@ export default function TableChart<D extends DataRecord = 
DataRecord>(
         isUsingTimeComparison &&
         Array.isArray(basicColorFormatters) &&
         basicColorFormatters.length > 0;
+      const generalShowCellBars =
+        config.showCellBars === undefined ? showCellBars : config.showCellBars;
       const valueRange =
         !hasBasicColorFormatters &&
-        !hasColumnColorFormatters &&
-        (config.showCellBars === undefined
-          ? showCellBars
-          : config.showCellBars) &&
+        generalShowCellBars &&
         (isMetric || isRawRecords || isPercentMetric) &&
         getValueRange(key, alignPositiveNegative);
 
@@ -914,6 +917,8 @@ export default function TableChart<D extends DataRecord = 
DataRecord>(
 
           let backgroundColor;
           let color;
+          let backgroundColorCellBar;
+          let valueRangeFlag = true;
           let arrow = '';
           const originKey = column.key.substring(column.label.length).trim();
           if (!hasColumnColorFormatters && hasBasicColorFormatters) {
@@ -934,18 +939,43 @@ export default function TableChart<D extends DataRecord = 
DataRecord>(
                 formatter.getColorFromValue(valueToFormat);
               if (!formatterResult) return;
 
-              if (formatter.toTextColor) {
+              if (
+                formatter.objectFormatting === ObjectFormattingEnum.TEXT_COLOR
+              ) {
                 color = formatterResult.slice(0, -2);
+              } else if (
+                formatter.objectFormatting === ObjectFormattingEnum.CELL_BAR
+              ) {
+                if (generalShowCellBars)
+                  backgroundColorCellBar = formatterResult.slice(0, -2);
               } else {
                 backgroundColor = formatterResult;
+                valueRangeFlag = false;
               }
             };
             columnColorFormatters
-              .filter(formatter => formatter.column === column.key)
-              .forEach(formatter => applyFormatter(formatter, value));
+              .filter(formatter => {
+                if (formatter.columnFormatting) {
+                  return formatter.columnFormatting === column.key;
+                }
+                return formatter.column === column.key;
+              })
+              .forEach(formatter => {
+                let valueToFormat;
+                if (formatter.columnFormatting) {
+                  valueToFormat = row.original[formatter.column];
+                } else {
+                  valueToFormat = value;
+                }
+                applyFormatter(formatter, valueToFormat);
+              });
 
             columnColorFormatters
-              .filter(formatter => formatter.toAllRow)
+              .filter(
+                formatter =>
+                  formatter.columnFormatting ===
+                  ObjectFormattingEnum.ENTIRE_ROW,
+              )
               .forEach(formatter =>
                 applyFormatter(formatter, row.original[formatter.column]),
               );
@@ -968,6 +998,9 @@ export default function TableChart<D extends DataRecord = 
DataRecord>(
             text-align: ${sharedStyle.textAlign};
             white-space: ${value instanceof Date ? 'nowrap' : undefined};
             position: relative;
+            font-weight: ${color
+              ? `${theme.fontWeightBold}`
+              : `${theme.fontWeightNormal}`};
             background: ${backgroundColor || undefined};
             padding-left: ${column.isChildColumn
               ? `${theme.sizeUnit * 5}px`
@@ -981,6 +1014,7 @@ export default function TableChart<D extends DataRecord = 
DataRecord>(
             top: 0;
             ${valueRange &&
             typeof value === 'number' &&
+            valueRangeFlag &&
             `
                 width: ${`${cellWidth({
                   value: value as number,
@@ -992,11 +1026,14 @@ export default function TableChart<D extends DataRecord 
= DataRecord>(
                   valueRange,
                   alignPositiveNegative,
                 })}%`};
-                background-color: ${cellBackground({
-                  value: value as number,
-                  colorPositiveNegative,
-                  theme,
-                })};
+                background-color: ${
+                  (backgroundColorCellBar && `${backgroundColorCellBar}99`) ||
+                  cellBackground({
+                    value: value as number,
+                    colorPositiveNegative,
+                    theme,
+                  })
+                };
               `}
           `;
 
diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx 
b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
index 3084ce69a52..d7417ee78b4 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
@@ -40,6 +40,7 @@ import {
   isRegularMetric,
   isPercentMetric,
   ConditionalFormattingConfig,
+  ObjectFormattingEnum,
   ColorSchemeEnum,
 } from '@superset-ui/chart-controls';
 import { t } from '@apache-superset/core';
@@ -781,12 +782,16 @@ const config: ControlPanelConfig = {
                         item.colorScheme &&
                         !['Green', 'Red'].includes(item.colorScheme)
                       ) {
-                        if (!item.toAllRow || !item.toTextColor) {
+                        if (item.columnFormatting === undefined) {
                           // eslint-disable-next-line no-param-reassign
                           array[index] = {
                             ...item,
-                            toAllRow: item.toAllRow ?? false,
-                            toTextColor: item.toTextColor ?? false,
+                            ...(item.toTextColor === true && {
+                              objectFormatting: 
ObjectFormattingEnum.TEXT_COLOR,
+                            }),
+                            ...(item.toAllRow === true && {
+                              columnFormatting: 
ObjectFormattingEnum.ENTIRE_ROW,
+                            }),
                           };
                         }
                       }
@@ -795,6 +800,23 @@ const config: ControlPanelConfig = {
                 }
                 const { colnames, coltypes } =
                   chart?.queriesResponse?.[0] ?? {};
+                const allColumns =
+                  Array.isArray(colnames) && Array.isArray(coltypes)
+                    ? [
+                        {
+                          value: ObjectFormattingEnum.ENTIRE_ROW,
+                          label: t('entire row'),
+                          dataType: GenericDataType.String,
+                        },
+                        ...colnames.map((colname: string, index: number) => ({
+                          value: colname,
+                          label: Array.isArray(verboseMap)
+                            ? colname
+                            : (verboseMap[colname] ?? colname),
+                          dataType: coltypes[index],
+                        })),
+                      ]
+                    : [];
                 const numericColumns =
                   Array.isArray(colnames) && Array.isArray(coltypes)
                     ? colnames.reduce((acc, colname, index) => {
@@ -826,10 +848,7 @@ const config: ControlPanelConfig = {
                   removeIrrelevantConditions: chartStatus === 'success',
                   columnOptions,
                   verboseMap,
-                  conditionalFormattingFlag: {
-                    toAllRowCheck: true,
-                    toColorTextCheck: true,
-                  },
+                  allColumns,
                   extraColorChoices,
                 };
               },
diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx 
b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index 3d9da78b875..5183e5ab543 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import '@testing-library/jest-dom';
+import { ObjectFormattingEnum } from '@superset-ui/chart-controls';
 import {
   render,
   screen,
@@ -1250,7 +1251,7 @@ describe('plugin-chart-table', () => {
                         column: 'sum__num',
                         operator: '>',
                         targetValue: 2467,
-                        toAllRow: true,
+                        columnFormatting: ObjectFormattingEnum.ENTIRE_ROW,
                       },
                     ],
                   },
@@ -1286,7 +1287,7 @@ describe('plugin-chart-table', () => {
                         column: 'sum__num',
                         operator: '>',
                         targetValue: 2467,
-                        toTextColor: true,
+                        objectFormatting: ObjectFormattingEnum.TEXT_COLOR,
                       },
                     ],
                   },
@@ -1319,8 +1320,8 @@ describe('plugin-chart-table', () => {
                         column: 'sum__num',
                         operator: '>',
                         targetValue: 2467,
-                        toAllRow: true,
-                        toTextColor: true,
+                        columnFormatting: ObjectFormattingEnum.ENTIRE_ROW,
+                        objectFormatting: ObjectFormattingEnum.TEXT_COLOR,
                       },
                     ],
                   },
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
index 54adad53514..866e101699d 100644
--- 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
@@ -74,7 +74,7 @@ const ConditionalFormattingControl = ({
   verboseMap,
   removeIrrelevantConditions,
   extraColorChoices,
-  conditionalFormattingFlag,
+  allColumns,
   ...props
 }: ConditionalFormattingControlProps) => {
   const [conditionalFormattingConfigs, setConditionalFormattingConfigs] =
@@ -159,6 +159,7 @@ const ConditionalFormattingControl = ({
               }
               destroyTooltipOnHide
               extraColorChoices={extraColorChoices}
+              allColumns={allColumns}
             >
               <OptionControlContainer withCaret>
                 <Label>{createLabel(config)}</Label>
@@ -175,7 +176,7 @@ const ConditionalFormattingControl = ({
           onChange={onSave}
           destroyTooltipOnHide
           extraColorChoices={extraColorChoices}
-          conditionalFormattingFlag={conditionalFormattingFlag}
+          allColumns={allColumns}
         >
           <AddControlLabel>
             <Icons.PlusOutlined
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx
index 021fc5f8bcb..71de272fb43 100644
--- 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx
@@ -28,7 +28,7 @@ export const FormattingPopover = ({
   config,
   children,
   extraColorChoices,
-  conditionalFormattingFlag,
+  allColumns,
   ...props
 }: FormattingPopoverProps) => {
   const [visible, setVisible] = useState(false);
@@ -50,7 +50,7 @@ export const FormattingPopover = ({
           config={config}
           columns={columns}
           extraColorChoices={extraColorChoices}
-          conditionalFormattingFlag={conditionalFormattingFlag}
+          allColumns={allColumns}
         />
       }
       open={visible}
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.test.tsx
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.test.tsx
index 4957744b3b8..232661f627b 100644
--- 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.test.tsx
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.test.tsx
@@ -25,7 +25,6 @@ import {
 import { Comparator, ColorSchemeEnum } from '@superset-ui/chart-controls';
 import { GenericDataType } from '@apache-superset/core/api/core';
 import { FormattingPopoverContent } from './FormattingPopoverContent';
-import { ConditionalFormattingConfig } from './types';
 
 const mockOnChange = jest.fn();
 
@@ -44,6 +43,12 @@ const columnsBooleanType = [
   { label: 'Column 2', value: 'column2', dataType: GenericDataType.Boolean },
 ];
 
+const mixColumns = [
+  { label: 'Name', value: 'name', dataType: GenericDataType.String },
+  { label: 'Sales', value: 'sales', dataType: GenericDataType.Numeric },
+  { label: 'Active', value: 'active', dataType: GenericDataType.Boolean },
+];
+
 const extraColorChoices = [
   {
     value: ColorSchemeEnum.Green,
@@ -55,11 +60,6 @@ const extraColorChoices = [
   },
 ];
 
-const config: ConditionalFormattingConfig = {
-  toAllRow: true,
-  toTextColor: true,
-};
-
 test('renders FormattingPopoverContent component', () => {
   render(
     <FormattingPopoverContent
@@ -168,46 +168,15 @@ test('does not display the input fields when selected a 
boolean type operator',
   expect(await screen.queryByLabelText('Target value')).toBeNull();
 });
 
-test('displays the toAllRow and toTextColor flags based on the selected 
numeric type operator', () => {
+test('displays Use gradient checkbox', () => {
   render(
     <FormattingPopoverContent
       onChange={mockOnChange}
       columns={columns}
-      config={config}
-    />,
-  );
-
-  expect(screen.getByText('To entire row')).toBeInTheDocument();
-  expect(screen.getByText('To text color')).toBeInTheDocument();
-});
-
-test('displays the toAllRow and toTextColor flags based on the selected string 
type operator', () => {
-  render(
-    <FormattingPopoverContent
-      onChange={mockOnChange}
-      columns={columnsStringType}
-      config={config}
+      allColumns={columns}
     />,
   );
 
-  expect(screen.getByText('To entire row')).toBeInTheDocument();
-  expect(screen.getByText('To text color')).toBeInTheDocument();
-});
-
-test('Not displays the toAllRow and toTextColor flags', () => {
-  render(
-    <FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
-  );
-
-  expect(screen.queryByText('To entire row')).not.toBeInTheDocument();
-  expect(screen.queryByText('To text color')).not.toBeInTheDocument();
-});
-
-test('displays Use gradient checkbox', () => {
-  render(
-    <FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
-  );
-
   expect(screen.getByText('Use gradient')).toBeInTheDocument();
 });
 
@@ -229,7 +198,11 @@ const findUseGradientCheckbox = (): HTMLInputElement => {
 
 test('Use gradient checkbox defaults to checked', () => {
   render(
-    <FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
+    <FormattingPopoverContent
+      onChange={mockOnChange}
+      columns={columns}
+      allColumns={columns}
+    />,
   );
 
   const checkbox = findUseGradientCheckbox();
@@ -238,7 +211,11 @@ test('Use gradient checkbox defaults to checked', () => {
 
 test('Use gradient checkbox can be toggled', async () => {
   render(
-    <FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
+    <FormattingPopoverContent
+      onChange={mockOnChange}
+      columns={columns}
+      allColumns={columns}
+    />,
   );
 
   const checkbox = findUseGradientCheckbox();
@@ -252,3 +229,81 @@ test('Use gradient checkbox can be toggled', async () => {
   fireEvent.click(checkbox);
   expect(checkbox).toBeChecked();
 });
+
+test('The Use Gradient check box is not displayed for string and boolean and 
is displayed for numeric data types.', () => {
+  render(
+    <FormattingPopoverContent
+      onChange={mockOnChange}
+      columns={columnsStringType}
+      allColumns={columnsStringType}
+    />,
+  );
+
+  expect(screen.queryByText('Use gradient')).not.toBeInTheDocument();
+
+  render(
+    <FormattingPopoverContent
+      onChange={mockOnChange}
+      columns={columnsBooleanType}
+      allColumns={columnsBooleanType}
+    />,
+  );
+
+  expect(screen.queryByText('Use gradient')).not.toBeInTheDocument();
+
+  render(
+    <FormattingPopoverContent
+      onChange={mockOnChange}
+      columns={columns}
+      allColumns={columns}
+    />,
+  );
+
+  expect(screen.queryByText('Use gradient')).toBeInTheDocument();
+});
+
+test('should display formatting column and object fields when allColumns is 
provided and non-empty', async () => {
+  render(
+    <FormattingPopoverContent
+      columns={mixColumns}
+      allColumns={mixColumns}
+      onChange={mockOnChange}
+    />,
+  );
+
+  await waitFor(() => {
+    expect(screen.getByText('Formatting column')).toBeInTheDocument();
+    expect(screen.getByText('Formatting object')).toBeInTheDocument();
+  });
+});
+
+test('should hide formatting fields when allColumns is empty', async () => {
+  render(
+    <FormattingPopoverContent
+      columns={mixColumns}
+      allColumns={[]}
+      onChange={mockOnChange}
+    />,
+  );
+
+  await waitFor(() => {
+    expect(screen.queryByText('Formatting column')).not.toBeInTheDocument();
+    expect(screen.queryByText('Formatting object')).not.toBeInTheDocument();
+  });
+});
+
+test('should hide formatting fields when color scheme is Green', async () => {
+  render(
+    <FormattingPopoverContent
+      config={{ colorScheme: extraColorChoices[0].value }}
+      columns={mixColumns}
+      allColumns={mixColumns}
+      onChange={mockOnChange}
+    />,
+  );
+
+  await waitFor(() => {
+    expect(screen.queryByText('Formatting column')).not.toBeInTheDocument();
+    expect(screen.queryByText('Formatting object')).not.toBeInTheDocument();
+  });
+});
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
index 85e3624558d..7331cfbb9a4 100644
--- 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx
@@ -16,13 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useMemo, useState, useEffect } from 'react';
+import { useMemo, useState, useEffect, useCallback } from 'react';
 import { t } from '@apache-superset/core';
 import { styled } from '@apache-superset/core/ui';
 import { GenericDataType } from '@apache-superset/core/api/core';
 import {
   Comparator,
   MultipleValueComparators,
+  ObjectFormattingEnum,
   ColorSchemeEnum,
 } from '@superset-ui/chart-controls';
 import {
@@ -37,10 +38,14 @@ import {
   Checkbox,
   type FormProps,
 } from '@superset-ui/core/components';
+import { ConditionalFormattingConfig, ColumnOption } from './types';
 import {
-  ConditionalFormattingConfig,
-  ConditionalFormattingFlag,
-} from './types';
+  operatorOptions,
+  stringOperatorOptions,
+  booleanOperatorOptions,
+  formattingOptions,
+  colorSchemeOptions,
+} from './constants';
 
 const FullWidthInputNumber = styled(InputNumber)`
   width: 100%;
@@ -55,43 +60,6 @@ const JustifyEnd = styled.div`
   justify-content: flex-end;
 `;
 
-// Use theme token names instead of hex values to support theme switching
-const colorSchemeOptions = () => [
-  { value: 'colorSuccess', label: t('success') },
-  { value: 'colorWarning', label: t('alert') },
-  { value: 'colorError', label: t('error') },
-];
-
-const operatorOptions = [
-  { value: Comparator.None, label: t('None') },
-  { value: Comparator.GreaterThan, label: '>' },
-  { value: Comparator.LessThan, label: '<' },
-  { value: Comparator.GreaterOrEqual, label: '≥' },
-  { value: Comparator.LessOrEqual, label: '≤' },
-  { value: Comparator.Equal, label: '=' },
-  { value: Comparator.NotEqual, label: '≠' },
-  { value: Comparator.Between, label: '< x <' },
-  { value: Comparator.BetweenOrEqual, label: '≤ x ≤' },
-  { value: Comparator.BetweenOrLeftEqual, label: '≤ x <' },
-  { value: Comparator.BetweenOrRightEqual, label: '< x ≤' },
-];
-
-const stringOperatorOptions = [
-  { value: Comparator.None, label: t('None') },
-  { value: Comparator.Equal, label: '=' },
-  { value: Comparator.BeginsWith, label: t('begins with') },
-  { value: Comparator.EndsWith, label: t('ends with') },
-  { value: Comparator.Containing, label: t('containing') },
-  { value: Comparator.NotContaining, label: t('not containing') },
-];
-
-const booleanOperatorOptions = [
-  { value: Comparator.IsNull, label: t('is null') },
-  { value: Comparator.IsTrue, label: t('is true') },
-  { value: Comparator.IsFalse, label: t('is false') },
-  { value: Comparator.IsNotNull, label: t('is not null') },
-];
-
 const targetValueValidator =
   (
     compare: (targetValue: number, compareValue: number) => boolean,
@@ -263,16 +231,13 @@ export const FormattingPopoverContent = ({
   onChange,
   columns = [],
   extraColorChoices = [],
-  conditionalFormattingFlag = {
-    toAllRowCheck: false,
-    toColorTextCheck: false,
-  },
+  allColumns = [],
 }: {
   config?: ConditionalFormattingConfig;
   onChange: (config: ConditionalFormattingConfig) => void;
   columns: { label: string; value: string; dataType: GenericDataType }[];
   extraColorChoices?: { label: string; value: string }[];
-  conditionalFormattingFlag?: ConditionalFormattingFlag;
+  allColumns?: ColumnOption[];
 }) => {
   const [form] = Form.useForm();
   const colorScheme = colorSchemeOptions();
@@ -282,35 +247,10 @@ export const FormattingPopoverContent = ({
         config?.colorScheme !== ColorSchemeEnum.Red),
   );
 
-  const [toAllRow, setToAllRow] = useState(() => Boolean(config?.toAllRow));
-  const [toTextColor, setToTextColor] = useState(() =>
-    Boolean(config?.toTextColor),
-  );
   const [useGradient, setUseGradient] = useState(() =>
     config?.useGradient !== undefined ? config.useGradient : true,
   );
 
-  const useConditionalFormattingFlag = (
-    flagKey: 'toAllRowCheck' | 'toColorTextCheck',
-    configKey: 'toAllRow' | 'toTextColor',
-  ) =>
-    useMemo(
-      () =>
-        conditionalFormattingFlag && conditionalFormattingFlag[flagKey]
-          ? config?.[configKey] === undefined
-          : config?.[configKey] !== undefined,
-      [conditionalFormattingFlag], // oxlint-disable-line 
react-hooks/exhaustive-deps
-    );
-
-  const showToAllRow = useConditionalFormattingFlag(
-    'toAllRowCheck',
-    'toAllRow',
-  );
-  const showToColorText = useConditionalFormattingFlag(
-    'toColorTextCheck',
-    'toTextColor',
-  );
-
   const handleChange = (event: any) => {
     setShowOperatorFields(
       !(event === ColorSchemeEnum.Green || event === ColorSchemeEnum.Red),
@@ -320,6 +260,23 @@ export const FormattingPopoverContent = ({
   const [column, setColumn] = useState<string>(
     config?.column || columns[0]?.value,
   );
+  const visibleAllColumns = useMemo(
+    () => !!(allColumns && Array.isArray(allColumns) && allColumns.length),
+    [allColumns],
+  );
+
+  const [columnFormatting, setColumnFormatting] = useState<string | undefined>(
+    config?.columnFormatting ??
+      (Array.isArray(allColumns)
+        ? allColumns.find(item => item.value === column)?.value
+        : undefined),
+  );
+
+  const [objectFormatting, setObjectFormatting] =
+    useState<ObjectFormattingEnum>(
+      config?.objectFormatting || formattingOptions[0].value,
+    );
+
   const [previousColumnType, setPreviousColumnType] = useState<
     GenericDataType | undefined
   >();
@@ -355,6 +312,51 @@ export const FormattingPopoverContent = ({
     setPreviousColumnType(newColumnType);
   };
 
+  const handleAllColumnChange = (value: string | undefined) => {
+    setColumnFormatting(value);
+  };
+  const numericColumns = useMemo(
+    () => allColumns.filter(col => col.dataType === GenericDataType.Numeric),
+    [allColumns],
+  );
+
+  const visibleUseGradient = useMemo(
+    () =>
+      numericColumns.length > 0
+        ? numericColumns.some((col: ColumnOption) => col.value === column) &&
+          objectFormatting === ObjectFormattingEnum.BACKGROUND_COLOR
+        : false,
+    [column, numericColumns, objectFormatting],
+  );
+
+  const handleObjectChange = (value: ObjectFormattingEnum) => {
+    setObjectFormatting(value);
+
+    if (value === ObjectFormattingEnum.CELL_BAR) {
+      const currentColumnValue = form.getFieldValue('columnFormatting');
+
+      const isCurrentColumnNumeric = numericColumns.some(
+        col => col.value === currentColumnValue,
+      );
+
+      if (!isCurrentColumnNumeric && numericColumns.length > 0) {
+        const newValue = numericColumns[0]?.value || '';
+        form.setFieldsValue({
+          columnFormatting: newValue,
+        });
+        setColumnFormatting(newValue);
+      }
+    }
+  };
+
+  const getColumnOptions = useCallback(
+    () =>
+      objectFormatting === ObjectFormattingEnum.CELL_BAR
+        ? numericColumns
+        : allColumns,
+    [objectFormatting, numericColumns, allColumns],
+  );
+
   useEffect(() => {
     if (column && !previousColumnType) {
       setPreviousColumnType(
@@ -403,23 +405,68 @@ export const FormattingPopoverContent = ({
           </FormItem>
         </Col>
       </Row>
-      <Row gutter={20}>
-        <Col span={1}>
-          <FormItem
-            name="useGradient"
-            valuePropName="checked"
-            initialValue={useGradient}
-          >
-            <Checkbox
-              onChange={event => setUseGradient(event.target.checked)}
-              checked={useGradient}
-            />
-          </FormItem>
-        </Col>
-        <Col>
-          <FormItem required>{t('Use gradient')}</FormItem>
-        </Col>
-      </Row>
+      {visibleAllColumns && showOperatorFields ? (
+        <Row gutter={12}>
+          <Col span={12}>
+            <FormItem
+              name="columnFormatting"
+              label={t('Formatting column')}
+              rules={rulesRequired}
+              initialValue={columnFormatting}
+            >
+              <Select
+                ariaLabel={t('Select column name')}
+                options={getColumnOptions()}
+                onChange={(value: string | undefined) => {
+                  handleAllColumnChange(value as string);
+                }}
+              />
+            </FormItem>
+          </Col>
+          <Col span={12}>
+            <FormItem
+              name="objectFormatting"
+              label={t('Formatting object')}
+              rules={rulesRequired}
+              initialValue={objectFormatting}
+              tooltip={
+                objectFormatting === ObjectFormattingEnum.CELL_BAR
+                  ? t(
+                      'Applies only when "Cell bars" formatting is selected: 
the background of the histogram columns is displayed if the "Show cell bars" 
flag is enabled.',
+                    )
+                  : null
+              }
+            >
+              <Select
+                ariaLabel={t('Select object name')}
+                options={formattingOptions}
+                onChange={(value: ObjectFormattingEnum) => {
+                  handleObjectChange(value);
+                }}
+              />
+            </FormItem>
+          </Col>
+        </Row>
+      ) : null}
+      {visibleUseGradient && (
+        <Row gutter={20}>
+          <Col span={1}>
+            <FormItem
+              name="useGradient"
+              valuePropName="checked"
+              initialValue={useGradient}
+            >
+              <Checkbox
+                onChange={event => setUseGradient(event.target.checked)}
+                checked={useGradient}
+              />
+            </FormItem>
+          </Col>
+          <Col>
+            <FormItem required>{t('Use gradient')}</FormItem>
+          </Col>
+        </Row>
+      )}
       <FormItem noStyle shouldUpdate={shouldFormItemUpdate}>
         {showOperatorFields ? (
           (props: GetFieldValue) => renderOperatorFields(props, columnType)
@@ -431,47 +478,6 @@ export const FormattingPopoverContent = ({
           </Row>
         )}
       </FormItem>
-      <Row>
-        {showOperatorFields && showToAllRow && (
-          <Row gutter={20}>
-            <Col span={1}>
-              <FormItem
-                name="toAllRow"
-                valuePropName="checked"
-                initialValue={toAllRow}
-              >
-                <Checkbox
-                  onChange={event => setToAllRow(event.target.checked)}
-                  checked={toAllRow}
-                />
-              </FormItem>
-            </Col>
-            <Col>
-              <FormItem required>{t('To entire row')}</FormItem>
-            </Col>
-          </Row>
-        )}
-        {showOperatorFields && showToColorText && (
-          <Row gutter={20}>
-            <Col span={1}>
-              <FormItem
-                name="toTextColor"
-                valuePropName="checked"
-                initialValue={toTextColor}
-              >
-                <Checkbox
-                  onChange={event => setToTextColor(event.target.checked)}
-                  checked={toTextColor}
-                />
-              </FormItem>
-            </Col>
-            <Col>
-              <FormItem required>{t('To text color')}</FormItem>
-            </Col>
-          </Row>
-        )}
-      </Row>
-
       <FormItem>
         <JustifyEnd>
           <Button htmlType="submit" buttonStyle="primary">
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/constants.ts
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/constants.ts
new file mode 100644
index 00000000000..7365b147e2a
--- /dev/null
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/constants.ts
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Comparator, ObjectFormattingEnum } from '@superset-ui/chart-controls';
+import { t } from '@apache-superset/core';
+
+export const operatorOptions = [
+  { value: Comparator.None, label: t('None') },
+  { value: Comparator.GreaterThan, label: '>' },
+  { value: Comparator.LessThan, label: '<' },
+  { value: Comparator.GreaterOrEqual, label: '≥' },
+  { value: Comparator.LessOrEqual, label: '≤' },
+  { value: Comparator.Equal, label: '=' },
+  { value: Comparator.NotEqual, label: '≠' },
+  { value: Comparator.Between, label: '< x <' },
+  { value: Comparator.BetweenOrEqual, label: '≤ x ≤' },
+  { value: Comparator.BetweenOrLeftEqual, label: '≤ x <' },
+  { value: Comparator.BetweenOrRightEqual, label: '< x ≤' },
+];
+
+export const stringOperatorOptions = [
+  { value: Comparator.None, label: t('None') },
+  { value: Comparator.Equal, label: '=' },
+  { value: Comparator.BeginsWith, label: t('begins with') },
+  { value: Comparator.EndsWith, label: t('ends with') },
+  { value: Comparator.Containing, label: t('containing') },
+  { value: Comparator.NotContaining, label: t('not containing') },
+];
+
+export const booleanOperatorOptions = [
+  { value: Comparator.IsNull, label: t('is null') },
+  { value: Comparator.IsTrue, label: t('is true') },
+  { value: Comparator.IsFalse, label: t('is false') },
+  { value: Comparator.IsNotNull, label: t('is not null') },
+];
+
+export const formattingOptions = [
+  {
+    value: ObjectFormattingEnum.BACKGROUND_COLOR,
+    label: t('background color'),
+  },
+  {
+    value: ObjectFormattingEnum.TEXT_COLOR,
+    label: t('text color'),
+  },
+  {
+    value: ObjectFormattingEnum.CELL_BAR,
+    label: t('cell bar'),
+  },
+];
+
+// Use theme token names instead of hex values to support theme switching
+export const colorSchemeOptions = () => [
+  { value: 'colorSuccess', label: t('success') },
+  { value: 'colorWarning', label: t('alert') },
+  { value: 'colorError', label: t('error') },
+];
diff --git 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/types.ts
 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/types.ts
index e7de31a16b4..870a874c042 100644
--- 
a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/types.ts
+++ 
b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/types.ts
@@ -19,7 +19,11 @@
 
 import { ReactNode } from 'react';
 import { PopoverProps } from '@superset-ui/core/components/Popover';
-import { Comparator, ControlComponentProps } from 
'@superset-ui/chart-controls';
+import {
+  Comparator,
+  ControlComponentProps,
+  ObjectFormattingEnum,
+} from '@superset-ui/chart-controls';
 import { GenericDataType } from '@apache-superset/core/api/core';
 
 export type ConditionalFormattingConfig = {
@@ -32,31 +36,34 @@ export type ConditionalFormattingConfig = {
   toAllRow?: boolean;
   toTextColor?: boolean;
   useGradient?: boolean;
+  columnFormatting?: string;
+  objectFormatting?: ObjectFormattingEnum;
 };
 
 export type ConditionalFormattingControlProps = ControlComponentProps<
   ConditionalFormattingConfig[]
 > & {
-  columnOptions: { label: string; value: string; dataType: GenericDataType }[];
+  columnOptions: ColumnOption[];
   removeIrrelevantConditions: boolean;
   verboseMap: Record<string, string>;
   label: string;
   description: string;
   extraColorChoices?: { label: string; value: string }[];
-  conditionalFormattingFlag?: ConditionalFormattingFlag;
+  allColumns?: ColumnOption[];
 };
 
 export type FormattingPopoverProps = PopoverProps & {
-  columns: { label: string; value: string; dataType: GenericDataType }[];
+  columns: ColumnOption[];
   onChange: (value: ConditionalFormattingConfig) => void;
   config?: ConditionalFormattingConfig;
   title: string;
   children: ReactNode;
   extraColorChoices?: { label: string; value: string }[];
-  conditionalFormattingFlag?: ConditionalFormattingFlag;
+  allColumns?: ColumnOption[];
 };
 
-export type ConditionalFormattingFlag = {
-  toAllRowCheck?: boolean;
-  toColorTextCheck?: boolean;
-};
+export interface ColumnOption {
+  label: string;
+  value: string;
+  dataType: GenericDataType;
+}
diff --git a/superset/config.py b/superset/config.py
index ec5653f2360..f220f9b7fcd 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -945,6 +945,7 @@ THEME_DEFAULT: Theme = {
         "fontWeightNormal": "400",
         "fontWeightLight": "300",
         "fontWeightStrong": "500",
+        "fontWeightBold": "700",
         # Editor selection color (for SQL Lab text highlighting)
         "colorEditorSelection": "#fff5cf",
     },


Reply via email to